##简介 Lambda 表达式(lambda expression)是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。
##Lambda的语法 [捕捉块] (参数) mutable或exception声明 -> 返回值类型 {函数体}
Lambda主要分为五个部分:[捕捉块]、(参数)、mutable或exception声明、->返回值类型、{函数体}。
**捕捉块: **
指定捕捉所在作用域中的变量,并供给lambda主体使用。
捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:
- [ ] 不捕捉;
- [var]表示值传递方式捕捉变量var;
- [=]表示值传递方式捕捉所有作用域的变量(包括this);
- [&var]表示引用传递捕捉变量var;
- [&]表示引用传递方式捕捉所有作用域的变量(包括this);
- [this]表示值传递方式捕捉当前的this指针。
所在作用域,也就是包含Lambda函数的语句块,说通俗点就是包含Lambda的“{}”代码块。上面的捕捉列表还可以进行组合, 例如:
- [=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
- [&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
不过值得注意的是, 捕获列表没有先后顺序;捕获列表中的变量不能出现重复申。下面一些例子就是典型的重复,会导致编译时期的错误。 例如:
- [=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
- [&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。
参数: (可选)lambda表达式使用的参数列表。
只有在不使用任何参数,并且没有自定mutable、一个exception_specification 和一个return_type的情况下可以忽略该列表;
参数可以通过按值(如:(a,b))和按引用(如:(&a, &b))两种方式进行传递。
参数列表和普通函数的参数列表类似,区别如下:
- 参数不能有默认值。
- 不允许变长参数列表。
- 不允许未命名的参数。
mutable:(可选)
如果所在作用域的变量是通过值捕捉到,那么lambda表达式主体中可以使用这些变量的副本。这些副本默认标记为const,因此lambda表达式的主体不能修改这些副本的值。如果lambda表达式标记为mutable,那么这些副本则不是const,因此主体可以修改这些本地副本。
exception_specification:(可选)
用于指定lambda可以抛出的异常。
return_type:(可选)返回值的类型。
如果忽略了return_type,那么编译器会根据以下原则判断返回类型:
- 如果lambda表达式主体的形式为{return expression;}那么表达式return_type的类型为expression的类型。
- 其他情况下的return_type为void。
{函数体}
标识函数的实现,这部分不能省略,但函数体可以为空。
##使用
注意:尾部的(),这对括号使得这个lambda表达式立即执行:
string result = [](const string & str) -> string { return "Hello from " + str; }("second Lambda");
cout << "Result: " << result << endl;
输出如下:
Result: Hello from second Lambda
还可以保存lambda表达式的指针,并且通过函数指针执行这个lambda表达式。使用C++11的auto关键字可以轻松地做到这一点:
auto fn = [](const string& str) {return "hello from " + str; };
cout << fn("call 1") << endl;
cout << fn("call 2") << endl;
输出如下:
Hello from call 1
Hello from call 2
##关于Lambda那些奇葩的东西 看以下一段代码:
#include<iostream>
using namespace std;
int main()
{
int j = 10;
auto by_val_lambda = [=]{ return j + 1; };
auto by_ref_lambda = [&]{ return j + 1; };
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
++j;
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
return 0;
}
程序输出结果如下:
by_val_lambda: 11
by_ref_lambda: 11
by_val_lambda: 11
by_ref_lambda: 12
你想到了么???那这又是为什么呢?为什么第三个输出不是12呢?
在by_val_lambda中,j被视为一个常量,一旦初始化后不会再改变(可以认为之后只是一个跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值。所以,在使用Lambda函数的时候,如果需要捕捉的值成为Lambda函数的常量,我们通常会使用按值传递的方式捕捉;相反的,如果需要捕捉的值成成为Lambda函数运行时的变量,则应该采用按引用方式进行捕捉。
再来一段更晕的代码:
#include<iostream>
using namespace std;
int main()
{
int val = 0;
// auto const_val_lambda = [=](){ val = 3; }; wrong!!!
auto mutable_val_lambda = [=]() mutable{ val = 3; };
mutable_val_lambda();
cout<<val<<endl; // 0
auto const_ref_lambda = [&]() { val = 4; };
const_ref_lambda();
cout<<val<<endl; // 4
auto mutable_ref_lambda = [&]() mutable{ val = 5; };
mutable_ref_lambda();
cout<<val<<endl; // 5
return 0;
}
这段代码主要是用来理解Lambda表达式中的mutable关键字的。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。按照规定,一个const的成员函数是不能在函数体内修改非静态成员变量的值。例如上面的Lambda表达式可以看成以下仿函数代码:
class const_val_lambda
{
public:
const_val_lambda(int v) : val(v) {}
void operator()() const { val = 3; } // 常量成员函数
private:
int val;
};
对于const的成员函数,修改非静态的成员变量,所以就出错了。而对于引用的传递方式,并不会改变引用本身,而只会改变引用的值,因此就不会报错了。