lambda表达式是C++11最重要也是最常用的一个特性之一。
lambda表达式有如下优点
- 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,有好的可读性和维护性
- 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力主手边的问题,同时也获取了更高的生产率。
- 在需要的时间和地点实现功能必报,使程序更灵活
lambda表达式的概念和基本用法
lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量,其语法形式可归纳为:
[capture] (params)opt->ret {body;};
lambda表达式可以通过捕获列表捕获一定范围内的变量:
- [] 不捕获任何变量
- [&] 捕获外部作用域中所有变量,并作为引用主函数体中使用
- [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用
- [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
- [bar] 按值捕获bar变量,同时不捕获其他变量。
- [this] 捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项
例如
auto f=[](int a){ return a+1; }
std::cout << f(1)<<std::endl; //输出:2
可以看到,上面通过一行代码定义了一个小小的功能闭包,用来将输入加1并返回。
需要注意的是,初始化列表不能用于返回值的自动推导:
auto x1=[](int i){ return i; }; //OK,return type is int
auto x2={ return {1,2}; }; //error: 无法推导出返回值类型
另外,lambda表达式在没有参数列表时,参数列表时可以省略的。因此像下面的写法都是正确的:
auto f1={ return 1; };
auto f2=[]{ return 1; }; //省略空参数表
一个容易出错的细节是关于lambda表达式的延迟调用的:
int a=0;
auto f=[=]{ return a; }; //按值捕获外部变量
a +=1; //a被修改
std::cout<<f()<<std::endl; //输出0
虽然通过a +=1,a的值取得了变化,但是因为f中存储的a是按值捕获的,a还是捕获时的值,所以仍然为0
下面看一下它的具体用法
class A
{
public:
int i_=0;
void func(int x,int y)
{
auto x1=[]{ return i_; }; //error,没有捕获外部变量
auto x2=[=]{ return i_+x+y; }; //OK,捕获所有外部变量
auto x3=[&]{ return i_+x+y; }; //OK,捕获所有外部变量
auto x4=[this]{ return i_+x+y; }; //OK,捕获this指针
auto x5=[this]{ return i_+x+y; }; //error,没有捕获x,y
auto x6=[this,x,y]{ return i_+x+y; }; //OK,捕获this指针,x,y
auto x7=[this]{ return i_++; }; //OK, 捕获this指针,并修改成员的值
};
int a=0,b=1;
auto f1=[]{ return a; }; //error,没有捕获外部变量
auto f2=[&]{ return a++; }; //OK,捕获所有外部变量,并对a执行自加运算
auto f3=[=]{ return a; }; //OK,捕获所有外部变量,并返回a
auto f4=[=]{ return a++; }; //error,a是以复制方式捕获的,无法修改
auto f5=[a]{ return a+b; }; //error,没有捕获变量b
auto f6=[a,&b]{ return a+(b++); }; //OK,捕获a和b的引用,并对b做自加运算
auto f7=[=,&b]{ return a+(b++); }; //OK,捕获所有变量和b的引用,并对b做自加运算
}