c++ lambda表达式
lambda表达式是C++11新特性之一,我们先来看一下lambda表达式的语法形式:
[ capture ] ( params ) opt -> ret { body; };
其中carpture是捕获列表,params是参数,opt是选项,ret则是返回值的类型,body则是函数的具体实现。
- 捕获列表描述了lambda表达式可以访问上下文中的哪些变量。
[] :表示不捕获任何变量
[=]:表示按值捕获变量
[&]:表示按引用捕获变量
[this]:值传递捕获当前的this
但是捕获列表不允许变量的重复传递:例如 [ =, x ] 这种捕获是不允许的,=表示按值的方式捕获所有的变量,x相当于被重复捕获了。 - params表示lambda的参数,用在{}中。
- opt表示lambda的选项,例如mutable,后面会介绍一下mutable的用法。
- ret表示lambda的返回值,也可以显示指明返回值,lambda会自动推断返回值,但是值得注意的是只有当lambda的表达式仅有一条return语句时,自动推断才是有效的。像下面这种的表达式就需要加上返回类型。
[](double x )->double{int y = x ;return x - y;};
虽然lambda表达式是匿名函数,但是实际上也可以给lambda表达式指定一个名称,如下表示:
auto fun = [](int x ){return x % 3 ==0;};
此后再需要使用该lambda表达式,就可以使用f()来代替。
按值捕获和按引用捕获
按值捕获和按引用捕获的用法通过下面这个例子来看一下。
#include <iostream>
int main()
{
int a = 5;
auto fun1 = [=] {return a + 1; };//按值捕获a
auto fun2 = [&] {return a + 1; };//按引用捕获a
std::cout << fun1() << std::endl;
std::cout << fun2() << std::endl;
a++;
std::cout << fun1() << std::endl;
std::cout << fun2() << std::endl;
return 0;
}
程序运行结果如下:
6
6
6
7
先看第一次f1和f2的输出,都是6,这没有什么问题。再执行a++,输出f1和f2,f2的结果是7 也没有问题,为什么f1还是输出6呢?答案就是按值捕获可以理解为一旦lambda按值捕获某个变量相当于在表达式内部已经生成了一个被捕获变量的副本,而lambda表达式使用的就是这个副本,原本的变量再怎么变化都不会影响到副本的值,所以f1 lambda表达式中的值一直都是捕获时a 的值 也就是5,后续a++的操作和f1表达式没有关系。简单可以理解为f1表达式赋值了一个和a同名的const变量。从这我们也可以得出一个结论:如果希望lambda函数在调用时访问的外部变量是最新的,我们就需要使用按引用捕获。
下面可以看一下这个例子:
#include <iostream>
int main()
{
int a = 5;
auto f = [=] {return a *= 5; };//按值捕获a
std::cout << f() << std::endl;
return 0;
}
运行程序,有如下编译报错:
In Lambda function : [Error assignment of read - only variable 'a']
提示a是一个只读的,不允许修改,这就验证上面例子中说明的按值捕获实际上是lambda拷贝了一个与被捕获变量同名的const 副本并进行操作。
如果实在需要改变lambda中的值,这时就需要使用上文提到过的选项mutable。
默认情况下,lambda函数是一个const函数,而mutable也可以取消常量性。例如有如下代码:
#include <iostream>
int main()
{
int a = 5;
auto fun = [=]()mutable {return a *= 5; };//取消常量性
std::cout << fun() << std::endl;
return 0;
}
这里需要注意,被 mutable 修饰的 lambda 表达式就算没有参数也要写明参数列表。
运行程序结果为:
25
可以看到原先lambda函数中的a是只读的,加上mutable就可以修改lambda函数中a的值了。