介绍lambda
一个lambda表达式表示一个可调用的代码单元。可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与普通函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式:
[capture list] (parameter list) -> return type { function body }
- capture list(捕获列表):lambda表达式通过捕获列表控制能够访问的外部变量
- parameter list(参数列表)(选填):参数列表可以为空,如有传入参数,在C++11中必须说明参数的类型,
- return type(返回类型)(选填):必须使用尾置返回来指定返回类型,如果忽略返回类型,将通过return语句推断返回类型
- function body(函数体)
可以忽略参数列表和返回类型,但必须包含捕获列表和函数体。如
auto f = [] { return 1; };//定义了一个可调用对象f,不接受参数,返回1
使用调用运算符就可以调用lambda:
cout << f() << endl;//打印1
优点
使用lambda表达式可以减少命名函数或者函数对象的创建,尤其是某个函数可能只会被使用一次时。需要查看函数定义时,也避免了可能的跳转,提供了更佳的可读性,使得开发人员能更加集中精力在当前代码段。
例如,希望使用sort将一个容器中的元素按长度排列。不使用lambda表达式,就需要定义一个命名函数:
bool isShorter(const string& s1,const string& s2)
{
return s1.size() < s2.size();
}
{
vector<string> vs{ "adsds","bcd","def","ghijdsd","klm","nodsdsa" };
sort(vs.begin(), vs.end(),isShorter);
}
使用lambda表达式,代码就可以修改成如下:
vector<string> vs{ "adsds","bcd","def","ghijdsd","klm","nodsdsa" };
sort(vs.begin(), vs.end(),
[](const string& s1,const string& s2)
{return s1.size()<s2.size(); });
lambda捕获
类似参数传递,变量的捕获方式也可以是值或引用。
与传值参数类似,值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量值在lambda创建时拷贝,而不是调用时:
void fun1()
{
int a = 0;
auto f1 = [a] { return a; };//按值捕获a,此时a已被拷贝
++a;
cout << f1() << endl;//输出0;f1保存创建它时对a的拷贝
}
由于被捕获变量在lambda创建时拷贝,因此随后对其进行的修改不会影响到lambda内部对应的值。
定义lambda时可以采用引用方式捕获变量:
void fun2()
{
int a = 0;
auto f2 = [&a] { return a; };//按引用捕获a,f包含a的引用
++a;
cout << f2() << endl;//输出1;f2保存a的引用,而非拷贝
}
捕获列表中的&指出应当以引用方式捕获a。以引用方式捕获的变量与任何类型的引用的行为类似。当在lambda函数体内使用该变量时,实际是使用引用所绑定对象。在上述例子中,lambda返回a时,实际返回的是a指向的对象的值。
引用捕获与返回引用有着相同的问题和限制。在采用引用捕获时,必须保证被引用的对象在lambda执行的时候是存在的。lambda捕获的是局部变量,这些变量在函数结束后不复存在。如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失。
除了显示列出我们希望使用的变量之外,还可以让编译器根据lambda中的代码来推断使用哪些变量。为指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用引用方式捕获,=则表示采用值捕获。例如,可以修改上面的函数
int a = 0;
auto f2 = [=] { return a; };//隐式捕获
如果希望对部分变量值捕获,对其他变量引用捕获,可以混合使用隐式捕获和显式捕获。当混合使用隐式捕获和显式捕获时,显示捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式(使用&),则显式捕获必须采用值方式。反之亦然。
lambda捕获列表
[] | 空捕获列表。lambda不能使用所在函数中的变量。 |
[=] | 隐式捕获列表,按值捕获。lambda将拷贝所使用的来自所在函数的实体的值 |
[&] | 隐式捕获列表,按引用捕获。lambda所使用的来自所在函数的实体都采用引用方式捕获 |
[names] | names 是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量。默认情况下,变量按值捕获,如果名字前使用了&,则采用按引用捕获 |
[&,identifier_list] | identifier_list 是一个逗号分隔的名字列表,包含0个或多个来自所在函数的变量。这些变量按值捕获。而任何隐式捕获的变量都按引用方式捕获。identifier_list 名字前不能有& |
[=,identifier_list] | identifier_list 中的变量都按引用捕获,而任何隐式捕获的变量都按值捕获。identifier_list 中的名字不能包括this,且名字前必须有& |
可变lambda
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable:
void fun3()
{
int a = 0;
auto f3 = [a] () mutable { return ++a; };
a = 10;
auto j = f3();//j为1
}
一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是非const类型:
void fun4()
{
int a = 0;
auto f3 = [&a] () { return ++a; };
a = 10;
auto j = f3();//j为11
}