Lambda函数-用法/实现/广义捕获 Lambda函数用法 [捕获列表] (参数) mutable noexcept /throw () -> 返回值类型 { 函数体; };
1.[捕获列表] [ ] 方括号用于向编译器表明当前是一个 lambda 表达式,其不能被省略。在方括号内部,可以注明当前 lambda 函数的函数体中可以使用哪些“外部变量”。
所谓外部变量,指的是和当前 lambda 表达式位于同一作用域内的所有局部变量。
捕获列表
功能
[&]
只有一个 & 符号,表示以引用传递的方式导入所有外部变量;
[&val1,&val2,…]
表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序;
[=,&val1,…]
表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。
[=]
只有一个 = 等号,表示以值传递的方式导入所有外部变量;
[]
空方括号表示当前 lambda 匿名函数中不导入任何外部变量。
[this]
表示以值传递的方式导入当前的 this 指针。
[val1,val2,…]
表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序;
[val,&val2,…]
以上 2 种方式还可以混合使用,变量之间没有前后次序。
case1:不导入任何变量,[ ]为空即可,因此无法访问/修改任何外部变量
int a = 0 , b = 0 ; function<int (int ,int )>fun = [=](int x,int y) -> int { cout <<a<<" " <<b<<endl ; return x+y; }; fun(a,b);cout <<a<<" " <<b<<endl ;
case2: 引用 导入外部a,b变量
int a = 0 , b = 0 ; function<int (int ,int )>fun = [=](int x,int y) -> int { a++; b++; return x+y; }; fun(a,b);cout <<a<<" " <<b<<endl ;
case3: 值捕获外部a,b变量时,a,b变量变成只读,不能被修改因此下面程序出错:
int a = 0 , b = 0 ; function<int (int ,int )>fun = [=](int x,int y) -> int { a++; b++; return x+y; }; fun(a,b);cout <<a<<" " <<b<<endl ;
case4:值捕获外部a,b变量,a,b变成只读,但加上mutable即可以修改
int a = 0 , b = 0 ; function<int (int ,int )>fun = [=](int x,int y) mutable -> int { a++; b++; return x+y; }; fun(a,b);cout <<a<<" " <<b<<endl ;
2.(参数) 和普通函数的定义一样,lambda 匿名函数也可以接收外部传递的多个参数。和普通函数不同的是,如果不需要传递参数,可以连同 () 小括号一起省略;(使用noexcept/throw 和 mutable时除外) ,为了减少这种不必要的记忆和困扰,建议一直加上
3.mutable 此关键字可以省略,如果使用则之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,对于以值传递方式引入的外部变量,不允许在 lambda 表达式内部修改它们的值(可以理解为这部分变量都是 const 常量)。而如果想修改它们,就必须使用 mutable 关键字。
注意,对于以值传递方式引入的外部变量,lambda 表达式修改的是拷贝的那一份,并不会修改真正的外部变量;
4.noexcept/throw() 可以省略,如果使用,在之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,lambda 函数的函数体中可以抛出任何类型的异常。而标注 noexcept 关键字,则表示函数体内不会抛出任何异常;使用 throw() 可以指定 lambda 函数内部可以抛出的异常类型。
值得一提的是,如果 lambda 函数标有 noexcept 而函数体内抛出了异常,又或者使用 throw() 限定了异常类型而函数体内抛出了非指定类型的异常,这些异常无法使用 try-catch 捕获,会导致程序执行失败。
5.-> 返回值类型 指明 lambda 匿名函数的返回值类型。值得一提的是,如果 lambda 函数体内只有一个 return 语句,或者该函数返回 void,则编译器可以自行推断出返回值类型,此情况下可以直接省略-> 返回值类型
。
6. 函数体 和普通函数一样,lambda 匿名函数包含的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有全局变量。
需要注意的是,外部变量会受到以值传递还是以引用传递方式引入的影响,而全局变量则不会。换句话说,在 lambda 表达式内可以使用任意一个全局变量,必要时还可以直接修改它们的值。
Lambda内部实现 Lambda实际上就是一个仿函数:即是函数是一个类,类中重载了operator (),捕获列表中的变量会作为类的private变量
举一个值捕获的例子 ,并添加有mutable (值捕获不能修改,修改必须加上mutable,修改的也是copy的那一份,如果想真正修改请使用引用捕获)
#include <iostream> #include <functional> using namespace std ;int main () { int a = 0 , b = 0 ; function<int (int ,int )>fun = [=](int x,int y) mutable -> int { a++; b++; return x+y; }; fun(a,b); cout <<a<<" " <<b<<endl ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <functional> using namespace std ;int main () { int a = 0 ; int b = 0 ; class __lambda_7_31 //lambda 类的定西 { public : inline int operator () (int x, int y) { a++; b++; return x + y; } private : int a; int b; public : __lambda_7_31(int & _a, int & _b) : a{_a} , b{_b} {} }; function<int (int , int )> fun = std ::function<int (int , int )>(__lambda_7_31{a, b}); fun.operator ()(a, b); std ::operator <<(std ::cout .operator <<(a), " " ).operator <<(b).operator <<(std ::endl ); return 0 ; }
如果我们采用引用方式捕获: 那么会发现a,b都变成了引用,其他无区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class __lambda_7_31 { public : inline int operator () (int x, int y) const { a++; b++; return x + y; } private : int & a; int & b; public : __lambda_7_31(int & _a, int & _b) : a{_a} , b{_b} {} };
初始化捕获/广义捕获(generalized lambda capture) #include <iostream> #include <functional> using namespace std ;int main () { int a = 0 , b = 0 ; function<int (int ,int )>fun = [&a,&b](int x,int y) mutable -> int { a++; b++; return x+y; }; fun(a,b); cout <<a<<" " <<b<<endl ; }
其中这段function<int(int,int)>fun = [&a,&b](int x,int y) mutable -> int
中的[&a,&b]
可以替换为:[&a = a,&b = b]
,这样看似是多此一举的,原因是这个场景中广义捕获的优势没有显现出来,考虑下面一个场景:捕获的时候直接进行move操作,得到move后的右值引用x
unique_ptr <int >b = std ::make_unique<int >(5 );auto fun = [x = std ::move (b)](int t = 5 ) { cout <<*x<<endl ; }; fun();
看一下它内部的细节, 初始化列表的操作时是这样操作的x{std::move(_x)}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <memory> #include <functional> using namespace std ;int main () { unique_ptr <int > b = std ::make_unique<int >(5 ); class __lambda_8_12 { public : inline void operator () (int t) const { std ::cout .operator <<(x.operator *()).operator <<(std ::endl ); } private : std ::unique_ptr <int , std ::default_delete<int > > x; public : __lambda_8_12(std ::unique_ptr <int , std ::default_delete<int > > && _x) : x{std ::move (_x)} {} }; __lambda_8_12 fun = __lambda_8_12{std ::unique_ptr <int , std ::default_delete<int > >(std ::move (b))}; fun.operator ()(5 ); return 0 ; }