lambda表达式/匿名函数
lambda表达式基础
lamda表达式就是匿名函数(没有名字的函数),lambda表达式基本语法如下:
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型
{
//函数体其中,仅有“[]{}”是必须的,其他全部可以省略不写。lambda的使用,和函数类似,先要定义,然后调用。
}
参数列表:
1、参数列表和普通函数的参数列表一样,可以是值、引用、const引用等,也是作为形参;
2、如果是非const引用,函数体内部可以修改引用的值;
3、与普通函数不同,lambda表达式的参数不能有默认值。
函数体:
1、可以使用参数列表中传递的参数;
2、可以直接访问静态局部变量,以及全局变量;
3、不能直接访问非静态局部变量;
4、可以使用捕捉列表里捕捉的局部变量;
5、可以在函数体中对全局变量和静态局部变量修改。
捕获列表:
1、捕捉列表中直接写非静态变量的变量名,函数体中便可访问;
2、与参数列表不同,不需要在变量名前加上类型;
3、如果有多个变量,用","隔开;
4、只能捕捉在lambda定义之前已经定义的非静态局部变量;
5、直接写变量名,为值捕获;想要引用方式捕获,则在变量名前加上"&"。
[capture](params) opt -> ret {body; };
//最简单的一个例子:
//普通函数
int Foo(int a, int b){
return a + b;
}
//lamda表达式等价于 c = Foo(1, 2);
int c = [](int a, int b)-> int {
return a + b;
}(1, 2);
//另外一种形式:
auto f = [](int a, int b)-> int {
return a + b;
}
c = f(1, 2);
两个lamda表达式之间是可以嵌套的
int c = [](int n){
return [n](int x){ //内部捕获外部lambda表达式参数n
return n + x;
}(1);
}(2);
mutable
int main()
{
int t = 10;
auto f = [t](){ //Error!! 此时捕获的t是一个常量类型不能修改
return ++t;
}
auto f = [t]() mutable{ //Success!!
return ++t;
}
std::cout << f() << std::endl; // 11
std::cout << f() << std::endl; // 12
std::cout << t << std::endl; // 10
}
为什么第一次第二次输出为11,12,然而t的值还是为10?首先这个 t(捕获列表)只是按值来捕获的,所以 t 在lambda内部的修改不影响外部的 t 的值。其次,内部捕获列表的 t 和外部的 t 不是同一个变量,不是同一个地址!当使用mutable对 t 进行捕获的时候,那么在这个匿名函数的内部,它是独自拥有的,是独一无二的每次进来都是对相同的 t 进行 ++ , 所以第一次第二次输出为11 12。
捕获列表的用法
按值捕获:
int main()
{
int t = 10;
auto f = [t](){
std::cout << t << std::endl;
};
t = 11;
f();
}
结果输出为10,捕获的是声明匿名函数时,捕获列表参数的值。可以理解为,值捕获创建了一个临时对象,拷贝了原变量的值,之后,这个临时变量与原来的变量再无任何瓜葛,lambda中操作的是这个临时变量。
而引用捕获,创建的是一个引用/指针,指向了原来的变量,lambda函数体中操作的就是变量本身。
按引用捕获:
int main()
{
int t = 10;
auto f2 = [&t](){
std::cout << t << std::endl;
t = 13;
};
t = 11;
f2();
std::cout << t << std::endl;
}
结果输出为 11 ,13
按引用捕获在实际应用中是有风险的,主要原因是lambda表达式在被创建后到被执行前这段期间里,这个引用有可能会变得不复存在了,这时候就很难保证这个引用变量在执行的时候一定存在。所以除非我们能确保它在lambda执行的时候是一定存在的,否则应该尽量避免用按引用捕获。
隐式捕获:
前面介绍的,将变量名写入捕获列表,为显式捕获。如果lambda中需要用到的非静态局部变量非常多,全部写入捕获列表,就会显得非常臃肿,有简便写法,即为隐式捕获。
1、空。没有使用任何函数对象参数。
2、=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
4、this。函数体内可以使用Lambda所在类中的成员变量。
5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。6、&a。将a按引用进行传递。
7、a, &b。将a按值进行传递,b按引用进行传递。
8、=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
9、&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
注意:
1、捕获列表的形式为"[=]“(值捕获)或者”[&]“(引用捕获);
2、捕获列表里不能同时写”=“和”&“,即”[=, &]"是非法的;
3、隐式捕获可以和显式捕获搭配使用,但不能和同类型的显示捕获一起使用。即隐式值捕获只能搭配显式引用捕获,隐式引用捕获只能搭配显式值
返回值
如果指定了lambda的返回值类型,则只需要函数体中return的类型可以隐式转换成指定的返回值类型,编译就可以通过编译,类型不匹配可能会给出警告:
int main()
{
auto fun = [](int i) -> int{
if (i>0)
return true;
else
return 11.14;
};
std::cout << fun(-3) << std::endl;
return 0;
}
编译后会有警告:
“return”:从"double"到"int"可能会丢失数据
如果没有显式指定返回值,编译器会以第一个return语句的类型作为返回值类型,之后的所有return语句,必须是此类型,否则会直接报错。