提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
使用 STL 时,往往会大量用到函数对象,为此要编写很多函数对象类。有的函数对象类只用来定义了一个对象,而且这个对象也只使用了一次,编写这样的函数对象类就有点浪费。而且,定义函数对象类的地方和使用函数对象的地方可能相隔较远,看到函数对象,想要查看其 operator() 成员函数到底是做什么的也会比较麻烦。对于只使用一次的函数对象类,能否直接在使用它的地方定义呢?Lambda 表达式能够解决这个问题。使用 Lambda 表达式可以减少程序中函数对象类的数量,使得程序更加优雅。
1.匿名函数的基本语法
[ 捕获列表 ]( 参数列表 ) mutable( 可选 ) 异常属性 -> 返回类型 {
// 函数体
}
代码如下(示例):
//[ 捕获列表 ]( 参数列表 )-> 返回类型 { 函数体 }int main (){auto Add = []( int a , int b ) -> int {return a + b ;};std::cout << Add ( 1 , 2 ) << std::endl ; // 输出 3return 0 ;}
一般情况下,编译器可以自动推断出 lambda 表达式的返回类型,所以我们可以不指定返回类型
//[ 捕获列表 ]( 参数列表 ){ 函数体 }int main (){auto Add = []( int a , int b ) {return a + b ;};std::cout << Add ( 1 , 2 ) << std::endl ; // 输出 3return 0 ;}
但是如果函数体内有多个return语句时,编译器无法自动推断出返回类型,此时必须指定返回类型。
2.捕获列表
有时候,需要在匿名函数内使用外部变量,所以用捕获列表来传递参数。根据传递参数的行为,捕获列表可分为以下几种:
2.1 值捕获
与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 lambda 表达式被 创建时拷贝 ,而非调用时才拷贝:
void test3 (){cout << "test3" << endl ;int c = 12 ;int d = 30 ;auto Add = [ c , d ]( int a , int b ) -> int {cout << "d = " << d << endl ;return c ;};d = 20 ;std::cout << Add ( 1 , 2 ) << std::endl ;}
2.2 引用捕获
与引用传参类似,引用捕获保存的是引用,值会发生变化。
如果Add中加入一句:c = a;
void test5 (){cout << "test5" << endl ;int c = 12 ;int d = 30 ;auto Add = [ & c , & d ]( int a , int b ) -> int {c = a ; // 编译对的cout << "d = " << d << endl ;return c ;};d = 20 ;std::cout << Add ( 1 , 2 ) << std::endl ;}
2.3 隐式捕获
手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个 & 或 = 向编译器声明采用引用捕获或者值捕获。
void test7 (){cout << "test7" << endl ;int c = 12 ;int d = 30 ;// 把捕获列表的 & 改成 = 再测试auto Add = [ & ]( int a , int b ) -> int {c = a ; // 编译对的cout << "d = " << d << endl ;return c ;};d = 20 ;std::cout << Add ( 1 , 2 ) << std::endl ;std::cout << "c:" << c << std::endl ;}
2.4 空捕获列表
捕获列表'[]'中为空,表示Lambda不能使用所在函数中的变量。
void test8 (){cout << "test7" << endl ;int c = 12 ;int d = 30 ;// 把捕获列表的 & 改成 = 再测试auto Add = []( int a , int b ) -> int {cout << "d = " << d << endl ; // 编译报错return c ; // 编译报错};d = 20 ;std::cout << Add ( 1 , 2 ) << std::endl ;std::cout << "c:" << c << std::endl ;}
2.5 表达式捕获
上面提到的值捕获、引用捕获都是已经在外层作用域声明的变量,因此这些捕获方式捕获的均为左值,而不能捕获右值 。
C++14 之后支持捕获右值,允许捕获的成员用任意的表达式进行初始化,被声明的捕获变量类型会根据表达式进行判断,判断方式与使用 auto 本质上是相同的:
void test9 (){cout << "test9" << endl ;auto important = std::make_unique < int > ( 1 );auto add = [ v1 = 1 , v2 = std::move ( important )]( int x , int y ) -> int {return x + y + v1 + ( * v2 );};std::cout << add ( 3 , 4 ) << std::endl ;}
2.6 泛型 Lambda
在 C++14 之前, lambda 表示的形参只能指定具体的类型,没法泛型化。从 C++14 开始, Lambda 函数的形式参数可以使用 auto 关键字来产生意义上的泛型:
// 泛型 Lambda C++14void test10 (){cout << "test10" << endl ;auto add = []( auto x , auto y ) {return x + y ;};std::cout << add ( 1 , 2 ) << std::endl ;std::cout << add ( 1.1 , 1.2 ) << std::endl ;}
2.7 可变lambda
- 采用值捕获的方式,lambda不能修改其值,如果想要修改,使用mutable修饰
- 采用引用捕获的方式,lambda可以直接修改其值
void test12() {cout << "test12" << endl;int v = 5;// 值捕获方式,使用 mutable 修饰,可以改变捕获的变量值auto ff = [v]() mutable {return ++v;};v = 0;auto j = ff(); // j为 6}void test13() {cout << "test13" << endl;int v = 5;// 采用引用捕获方式,可以直接修改变量值auto ff = [&v] {return ++v;};v = 0;auto j = ff(); // v引用已修改, j 为 1}
总结
1. 如果捕获列表为 [&] ,则表示所有的外部变量都按引用传递给 lambda 使用;
2. 如果捕获列表为 [=] ,则表示所有的外部变量都按值传递给 lambda 使用;
3. 匿名函数构建的时候对于按值传递的捕获列表, 会立即将当前可以取到的值拷贝一份作为常数 ,然后将该常数作为参数传递。
