C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式). Lambda表达式具体形式如下:
[capture](parameters)->return-type{body}
如果没有参数,空的圆括号()可以省略.返回值也可以省略,如果函数体只由一条return语句组成或返回类型为void的话.形如:
[capture](parameters){body}
下面举了几个Lambda函数的例子:
[](int x, int y) { return x + y; } // 隐式返回类型
[](int& x) { ++x; } // 没有return语句 -> lambda 函数的返回类型是'void'
[]() { ++global_x; } // 没有参数,仅访问某个全局变量
[]{ ++global_x; } // 与上一个相同,省略了()
可以像下面这样显示指定返回类型:
[](int x, int y) -> int { int z = x + y; return z; }
在这个例子中创建了一个临时变量z来存储中间值. 和普通函数一样,这个中间值不会保存到下次调用. 什么也不返回的Lambda函数可以省略返回类型, 而不需要使用 -> void 形式.
Lambda函数可以引用在它之外声明的变量. 这些变量的集合叫做一个闭包. 闭包被定义在Lambda表达式声明中的方括号[]内. 这个机制允许这些变量被按值或按引用捕获.下面这些例子就是:
[] //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
[x, &y] //x 按值捕获, y 按引用捕获.
[&] //用到的任何外部变量都隐式按引用捕获
[=] //用到的任何外部变量都隐式按值捕获
[&, x] //x显式地按值捕获. 其它变量按引用捕获
[=, &z] //z按引用捕获. 其它变量按值捕获
接下来的两个例子演示了Lambda表达式的用法.
std::vector some_list;
int total = 0;
for (int i=0;i<5;++i) some_list.push_back(i);
std::for_each(begin(some_list), end(some_list), [&total](int x)
{
total += x;
});
此例计算list中所有元素的总和. 变量total被存为lambda函数闭包的一部分. 因为它是栈变量(局部变量)total的引用,所以可以改变它的值.
std::vector some_list;
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list), [&, value, this](int x)
{
total += x * value * this->some_func();
});
此例中total会存为引用, value则会存一份值拷贝. 对this的捕获比较特殊, 它只能按值捕获. this只有当包含它的最靠近它的函数不是静态成员函数时才能被捕获.对protect和priviate成员来说, 这个lambda函数与创建它的成员函数有相同的访问控制. 如果this被捕获了,不管是显式还隐式的,那么它的类的作用域对Lambda函数就是可见的. 访问this的成员不必使用this->语法,可以直接访问.
不同编译器的具体实现可以有所不同,但期望的结果是:按引用捕获的任何变量,lambda函数实际存储的应该是这些变量在创建这个lambda函数的函数的栈指针,而不是lambda函数本身栈变量的引用. 不管怎样, 因为大数lambda函数都很小且在局部作用中, 与候选的内联函数很类似, 所以按引用捕获的那些变量不需要额外的存储空间.
如果一个闭包含有局部变量的引用,在超出创建它的作用域之外的地方被使用的话,这种行为是未定义的!
lambda函数是一个依赖于实现的函数对象类型,这个类型的名字只有编译器知道. 如果用户想把lambda函数做为一个参数来传递, 那么形参的类型必须是模板类型或者必须能创建一个std::function类似的对象去捕获lambda函数.使用 auto关键字可以帮助存储lambda函数,
auto my_lambda_func = [&](int x) { /*...*/ };
auto my_onheap_lambda_func = new auto([=](int x) { /*...*/ });
这里有一个例子, 把匿名函数存储在变量,数组或vector中,并把它们当做命名参数来传递
#include
#include
#include
double eval(std::function f, double x = 2.0){return f(x);}
int main()
{
std::function f0 = [](double x){return 1;};
auto f1 = [](double x){return x;};
decltype(f0) fa[3] = {f0,f1,[](double x){return x*x;}};
std::vector fv = {f0,f1};
fv.push_back ([](double x){return x*x;});
for(int i=0;i
一个没有指定任何捕获的lambda函数,可以显式转换成一个具有相同声明形式函数指针.所以,像下面这样做是合法的:
auto a_lambda_func = [](int x) { /*...*/ };
void(*func_ptr)(int) = a_lambda_func;
func_ptr(4); //calls the lambda.
C++11中的lambda函数,其中的“捕捉列表”是由0个或多个“捕捉项”组成,并以逗号“,”分隔。捕捉列表有如下几种形式:
(1)[var] 表示值传递方式捕捉变量var。
#include
#include
#include
using namespace std;
int main()
{
int a = 1,b =2, c =3;
auto retVal = [=,&a,&b]()
{
printf("inner c[%d]\n",c);
a = 10;
b = 20;
return a+b;
};
printf("sum[%d]\n",retVal());
printf("a[%d] b[%d] c[%d]\n",a,b,c);
return 0;
}
打印结果:
inner c[3]
sum[30]
a[10] b[20] c[3]
上面的代码中,“捕捉列表”由3项组成。以引用传递的方式捕捉变量a、b,以值传递的方式捕捉变量c。因此在lambda表达式的函数体中修改了变量a和b之后,父作用域中的a、b值也改变。而即使是在lambda函数内部修改了变量c的值,父作用域中的c仍然不会受到影响,因为是值传递的方式。(需在参数列表后面加上 mutable 关键字(修饰符))。同时
#include
#include
#include
using namespace std;
int main()
{
int a = 1,b =2, c =3;
auto retVal = [=,&a,&b]() mutable->int
{
printf("inner c[%d]\n",c);
a = 10;
b = 20;
c = 30;
printf("inner c2[%d]\n",c);
return a+b;
};
printf("sum[%d]\n",retVal());
printf("a[%d] b[%d] c[%d]\n",a,b,c);
return 0;
}
打印结果:
inner c[3]
inner c2[30]
sum[30]
a[10] b[20] c[3]
(2)[=] 表示值传递方式捕捉所有父作用域的变量(包括this)。
(3)[&var] 表示引用传递捕捉变量var。
(4)[&] 表示引用传递捕捉所有父作用域的比哪里(2020-02-18 09:36:12 修改) 的变量(包括this)。
#include
#include
#include
using namespace std;
int main()
{
int a = 1,b =2, c =3;
auto retVal = [&]() mutable->int
{
printf("inner a[%d] b[%d] c[%d]\n",a,b,c);
a = 10;
b = 20;
c = 30;
return a+b;
};
printf("sum[%d]\n",retVal());
printf("a[%d] b[%d] c[%d]\n",a,b,c);
return 0;
}
打印结果:
inner a[1] b[2] c[3]
sum[30]
a[10] b[20] c[30]
(5)[this] 表示值传递方式捕捉当前的this指针。
同理(2),(3),(5)可以参考上面的两个例子。
2. lambda 函数特点
(1)在C++11中,lambda函数是inline(内联函数)。
3. lambda 函数使用
代码一
#include
#include
#include
using namespace std;
int main()
{
int a = 6;
int b = 8;
auto sum = [](int a,int b) ->int{return a + b;};
printf("sum[%d]\n",sum(a,b));
return 0;
}
//打印结果:sum[14]
在代码一中,定义了一个简单的lambda函数,该函数的函数列表能够接收两个int类型的数据,而且返回值为int类型。
注意:lambda函数中,参数列表和返回类型都是可选的部分,而且捕捉列表和函数也可以为空。因此,在某种情况下,C++11中的简略版本的lambda函数可以是这样的:
// 完整语法
[ capture-list ] ( params ) mutable(optional) constexpr(optional)(c++17) exception attribute -> ret { body }
// 可选的简化语法
[ capture-list ] ( params ) -> ret { body }
[ capture-list ] ( params ) { body }