C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式)。
下面举了几个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表达式的语法定义如下:
[capture] (parameters) mutable ->return-type {statement};
(1) [capture]: 捕捉列表。捕捉列表总是出现在lambda函数的开始处。实质上,[]是lambda引出符(即独特的标志符)
编译器根据该引出符判断接下来的代码是否是lambda函数
捕捉列表能够捕捉上下文中的变量以供lambda函数使用
捕捉列表由一个或多个捕捉项组成,并以逗号分隔,捕捉列表一般有以下几种形式:
<1> [var] 表示值传递方式捕捉变量var
<2> [=] 表示值传递方式捕捉所有父作用域的变量(包括this指针)
<3> [&var] 表示引用传递捕捉变量var
<4> [&] 表示引用传递捕捉所有父作用域的变量(包括this指针)
<5> [this] 表示值传递方式捕捉当前的this指针
<6> [=,&a,&b] 表示以引用传递的方式捕捉变量 a 和 b,而以值传递方式捕捉其他所有的变量
<7> [&,a,this] 表示以值传递的方式捕捉 a 和 this,而以引用传递方式捕捉其他所有变量
备注:父作用域是指包含lambda函数的语句块
另外,需要注意的是,捕捉列表不允许变量重复传递。下面的例子就是典型的重复,会导致编译错误:
[=, a] 这里 = 已经以值传递方式捕捉了所有的变量,那么再捕捉 a 属于重复
[&,&this] 这里 & 已经以引用传递方式捕捉了所有变量,那么再捕捉 this 属于重复
(2)(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略
(3)mutable : mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性(后面有详解)
在使用该修饰符时,参数列表不可省略(即使参数为空)
(4)->return-type : 返回类型。用追踪返回类型形式声明函数的返回类型。
出于方便,不需要返回值的时候也可以连同符号->一起省略
此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导
(5){statement} : 函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量
在lambda函数的定义中,参数列表和返回类型都是可选的部分,而捕捉列表和函数体都可能为空
那么,在极端情况下,C++11中最为简单的lambda函数只需要声明为:
[] {};就可以了.
Lambda函数可以引用在它之外声明的变量。这些变量的集合叫做一个闭包,闭包被定义在Lambda表达式声明中的方括号[]内。这个机制允许这些变量被按值或按引用捕获。
引用示例:
float f0 = 1.0f;
float f1 = 10.0f;
std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5); // 输出结果: 14.5
std::cout << '\n' << f0 << '\n'; // 输出结果: 14.5
lambda函数是一个依赖于实现的函数对象类型,这个类型的名字只有编译器知道。如果用户想把lambda函数做为一个参数来传递吗,那么形参的类型必须是模板类型或者必须能创建一个std::function类似的对象去捕获lambda函数。使用 auto关键字可以帮助存储lambda函数,
auto my_lambda_func = [&](int x) { /*...*/ };
auto my_onheap_lambda_func = new auto([=](int x) { /*...*/ });
匿名函数存储在变量,数组或vector中,并把它们当做命名参数来传递:
#include<functional>
#include<vector>
#include<iostream>
double eval(std::function<double(double)> f, double x = 2.0){return f(x);}
int main()
{
std::function<double(double)> f0 = [](double x){return 1;};
auto f1 = [](double x){return x;};
decltype(f0) fa[3] = {f0,f1,[](double x){return x*x;}};
std::vector<decltype(f0)> fv = {f0,f1};
fv.push_back ([](double x){return x*x;});
for(int i=0;i<fv.size();i++) std::cout << fv[i](2.0) << "\n";
for(int i=0;i<3;i++) std::cout << fa[i](2.0) << "\n";
for(auto &f : fv) std::cout << f(2.0) << "\n";
for(auto &f : fa) std::cout << f(2.0) << "\n";
std::cout << eval(f0) << "\n";
std::cout << eval(f1) << "\n";
return 0;
}
一个没有指定任何捕获的lambda函数,可以显式转换成一个具有相同声明形式函数指针。所以,像下面这样做是合法的:
auto a_lambda_func = [](int x) { /*...*/ };
void(*func_ptr)(int) = a_lambda_func;
func_ptr(4); //calls the lambda.