搞懂C++11中的匿名函数
By 青衣极客 Blue Geek In 2020-05-15
相比于之前的版本,C++11的一些新特性能够让开发者工作的效率极大提升,匿名函数就是其中之一。官方称这种语法为lambda表达式,在Python中也有类似的语法。开发过程中,有时需要封装一些小函数,并不希望被其他人广泛地调用,只是为了在自己的局部代码中理顺逻辑和减少重复,那就可以考虑使用匿名函数。
1. 匿名函数的基本语法
匿名函数就是没有名字的函数,那么该如何调用呢?函数指针。 在定义函数时不设置函数名,可以使用函数指针来存储函数入口,也可以在定义时就完成调用。关于这些都会在后续讨论到。这里还是先来看一看匿名函数的完整语法解释。

从图中看起来似乎非常复杂,但在实际使用中常常只需要关注前两项即可。
2. 函数外值传递
这里还是先来演示简单的代码,以说明匿名函数的基本使用方法。首先来看一段代码
int a = 1, b= 2;
// 以值传递的方式利用外部值
auto func = [=]() mutable throw() -> int {
int ret = a + b;
a += 1;
return ret;
};
int ret = func();
cout << "a=" << a << ", ret=" << ret << endl;
ret = func();
cout << "a=" << a << ", ret=" << ret << endl;
大家可以猜测一下这段代码会输出什么?下面给出答案,你可以看看是否与你的预期一致。
a=1, ret=3
a=1, ret=4
这个匿名函数中显然有一行代码对变量a的值进行了修改,那为什么 a 的值并没有变化呢?这就是匿名函数语法中 [=] 这个配置所起的作用,也就是说,[=] 说明当前匿名函数在使用函数外的变量时,是先将该变量在函数内的命名空间中复制一份。 所以匿名函数内部对变量a 的修改只是影响内部的复制品,而外部的变量 a 并没有改变。
既然如此,那么为什么两次调用的函数的返回值不一样呢?因为函数并没有被销毁,其内部的变量在多次的调用中具有记忆性,所以第二次调用时,其内部的变量 a 的值已经被修改了。
3. 函数外引用传递
上面的问题搞清楚之后,我们再来看看下面一段代码
int a = 1, b= 2;
// 以引用的方式利用外部值
auto func2 = [&]() mutable throw() -> int {
int ret = a + b;
a += 1;
return ret;
};
ret = func2();
cout << "a=" << a << ", ret=" << ret << endl;
注意这段代码中的匿名函数与上一段代码中唯一的区别是用 [&] 替换了 [=]。按照语法中的解释可以知道,这段代码中的匿名函数在使用函数外变量时采用的是“引用赋值”,也就是说,在函数内部可以对函数外变量进行修改。 所以输出结果也就明朗了
a=2, ret=3
4. 常用语法
可以感受到上面那种完整的写法实在是太麻烦,而在实际使用中很少写得那么完整。我们首先来看一段常用形式的匿名函数代码
int ret = [](int x, int y) {return x + y;}(1, 2);
在这段代码中,[] 符号的内部是空的,这意思就是说,当前匿名函数不使用函数外的变量。 然后设置函数的参数列表,内部函数实现只是进行简单的计算然后返回,并不需要设置返回值类型。更有意思的是在定义匿名函数的时候,就直接传递参数进行调用了,因此整体形式非常简洁。但是这样做也有个问题,那就是这个匿名函数只能调用一次。
5. 匿名函数与STL配合使用
常用STL的朋友大概会知道,STL中的很多算法都是使用模版实现,因此常常依赖一个可调用对象以完成内部计算,通常我们可以使用“仿函数”,但是有了匿名函数之后,就又多了一种更加方便的方式了。我们来看一个简单的例子
vector<int> arr {5,3,2,6,8,9,7};
std::sort(arr.begin(), arr.end());
print(arr);
std::sort(arr.begin(), arr.end(),
[](int x, int y) {return x > y;});
print(arr);
STL中的 sort 函数需要一个用于比较大小的辅助函数来完成排序。默认地,这个比较函数会用于从小打大的排序,但是如果我们想要从大到小排序,那么又该怎么办呢?其中一个解决方案就是设置第三个参数来传递一个自定义的比较函数进去,如果这个比较函数的逻辑不是太复杂,使用匿名函数是首选。
还是需要说明一下,这里只是演示STL与匿名函数的配合使用。事实上,这种需要排序的情况,直接使用反转迭代器会更加方便一些。
6. 匿名函数嵌套
我们知道分支语句、循环语句可以嵌套,也知道复合类型可以嵌套,更知道普通的函数可以嵌套,那么匿名函数的嵌套就理所当然了。下面就看一段匿名函数嵌套的代码
int a = 1, b = 2;
int ret = [=]() {
return a + [=](){
return b * b;
}();
}();
cout << ret << endl;
大家可以根据自己的理解猜测一下这段程序的输出是什么,如果没猜错的话,输出结果应该是 5。第一层匿名函数实现的功能是 函数外变量 a 加上一个嵌套匿名函数的返回值;而里面的匿名函数返回外部变量 b 的平方,因此最终结果就是5。这就已经是匿名函数最复杂的用法了,但是日常开发中并不建议这样使用,因为会导致逻辑不清晰,违背了使用匿名函数的初衷。

COMMENT
博客评论区功能由Github Issue提供,提交Issue时请以本文标题为话题。
"BG103-搞懂C++11中的匿名函数"