一文详解C++11中的lambda函数
Shawn-Summer 人气:0我可以明确告诉你:lambda函数是C++11中最重要的,使用最广泛的,最具现代风格的内容,lambda函数的出现改变了C++编程的思维方式。
#include<iostream> using namespace std; int main() { int girls=3,boys=4; auto totalChild=[](int x,int y)->int{return x+y;}; return totalChild(girls,boys); }
上面中,auto和lambda函数的配合是一种经典生成局部函数的方法。lambda函数和普通函数最大的不同的地方就是它没有名字,所以 lambda函数是右值。
lambda函数相较于,函数指针,仿函数,它不仅简单,而且效率高。
1.lambda函数语法
[capture](parameters)mutable ->return-type{statement}
- [capture]:捕捉列表。捕获父作用域中可用的变量,供lambda函数使用,[]是lambda函数的导出符号。
- (parameters):参数列表。和普通函数的参数列表一样,如果没有参数可以省略
- mutable:修饰符号。lambda函数默认是一个const修饰的函数,mutab可以取消其常量性
- ->return-type:返回类型。类似于返回值后置的语法,如果没有返回值或者返回类型可自动推断就可省略声明返回类型。
- {statement}:函数体。
根据上面语法,我们知道[]{}是一种最简单的lambda函数,而且我们发现,从语法角度来看,lambda函数比普通函数多了一个捕获列表,这是它的精髓所在。
1.1 捕获列表
int main() { []{};//最简单的lambda函数 int a=3; int b=4; [=]{return a+b;}; auto func1=[&](int c){b=a+c;}; auto func2=[=,&b](int c)->int{return b+=a+c;}; }。
上面代码中,我们可以使用捕获列表来捕获,变量a和b
被捕获的变量和基于参数传递的变量是不同的,被捕获的变量更是一种lambda函数的初始状态。
捕获列表是由多个捕获项组成的:
- [var]:表示按值方式捕获变量var
- [=]:表示按值捕获其父作用域中所有可用的变量(包括this)
- [&var]:表示按引用捕获变量var
- [&]:表示按引用捕获其父作用域中所有可用的变量(包括this)
- [this]:表示按值传递当前的this指针
特别的这些捕获项可用组合使用,例如:[=,&a,&b]表示以引用捕获a和b,按值捕获其他所有变量。[&,a,this]表示按值捕获a和this,按引用捕获其他所有变量。
下面看一段代码
#include<iostream> using namespace std; int main() { int j=12; auto fun1=[=]{return j;}; auto fun2=[&]{return j;}; cout<<"fun1: "<<fun1()<<endl; cout<<"fun2: "<<fun2()<<endl; j++; cout<<"fun1: "<<fun1()<<endl; cout<<"fun2: "<<fun2()<<endl; } /* fun1: 12 fun2: 12 fun1: 12 fun2: 13 */
当j++后,再次调用func1()时,我们发现它里面的j却保持不变,所以上面这段代码反应了一个事实:捕获的变量是lambda函数的初始状态。
在使用lambda函数时,按值传递的变量成为函数中的常量,它不会再运行过程中改变,按引用传递的变量,它类似于函数参数,他会随时检查其值。
#include<iostream> using namespace std; int temp=0; int main() { static int a=0; auto fun=[]{temp++;a++;}; fun(); fun(); cout<<temp<<endl;//2 cout<<a<<endl;//2; [temp]{};//编译错误 [a]{};//编译错误 }
lambda函数中也可以直接使用全局变量,但是如果fun1这样就是错误的,因为 lambda函数只能捕获其父作用域中可用的自动变量,而静态变量不需要捕获,可以直接使用。
#include<iostream> using namespace std; int main() { int a=1; cout<<"a="<<a<<endl; auto foo1=[&]() { a++; cout<<"a="<<a<<endl; auto foo2=[&]() { a++; cout<<"a="<<a<<endl; }; foo2(); }; foo1(); } /* a=1 a=2 a=3 */
上面代码中说明,lambda函数中还可以使用lambda函数,这样子,上面代码就狠像pascal语言中的内嵌函数。
1.2 mutable修饰符
lambda函数是具有常量性的,下面这段代码是在stackoverflow网站中的一次讨论:
int main() { int val; auto fun1=[=]{val=3;};//编译失败,val无法被赋值 auto fun2=[=]() mutable {val=3;}; auto fun3=[&]{val=3;}; auto fun4=[](int v){v=3;}; fun4(val); }
上面中,fun1无法通过编译,因为val是按值传递的,所以在函数体中,val就是一个只读常量,无法对其进行赋值,我们可以使用修饰符mutable来取消其只读属性。这样的目的是只是提供一种语法上的可能,在实际使用的时候,我们一般不需要使用mutable,如果需要修改按值传递的值,我们可以直接按值传递参数,而不是捕获它。
实际上,lambda函数中的捕获变量,更像是函数对象(仿函数)中的私有数据成员:
class fun1 { private: int val; public: fun1(int v):val(v){}; void operator()const{val=3;};//编译出错 }
默认情况下,按值捕获的变量,如果不加mutable,它就会等价于上面的仿函数,这里的operator()就是const修饰的,它不允许修改val。
1.3 匿名lambda函数
lambda函数本身是右值,它没有名字,它本身就是匿名的,我们一般通过auto来赋予它一个名字,这样就能生成类似一个局部函数的效果。我们也可以不使用auto,我们可以直接生成一个lambda函数,然后调用,例如下面这段代码:
#include<iostream> using namespace std; int main() { const int a=[]{ int ret=0; for(int i=0;i<100;i++) { ret+=i; } return ret; }(); cout<<a<<endl; }
lambda函数定义后直接调用。
实际上,lambda函数的设计初衷就是:就地书写,就地使用。所以诸如上面的写法非常常见。
2.lambda与STL
lambda函数的出现,让我们发现使用STL算法更加简单了。
例如for_each()算法,它接收3个参数,前两个是指示范围的迭代器类型,第3个是接收一个参数的函数符(即仿函数,函数指针,lambda函数)。
#include<vector> #include<algorithm> #include<iostream> using namespace std; extern vector<int> nums; void OneCond(int val) { //传统for方法 for(auto i=nums.begin();i!=nums.end();++i) { if(*i==val) break; } //使用adapter find_if(nums.begin(),nums.end(),bind2nd(equal_to<int>(),val)); //使用lambda函数 find_if(nums.begin(),nums.end(),[=](int i){ return i==val; }); }
上面代码中,有些人认为使用这种adapter会简单一点,就像这里的equal_to<int>()它就是是一个函数对象,我只能说仁者见仁。但是这种使用adapter的方式创建函数对象,要求程序员懂很多STL的知识,而且可读性不好,例如下面这段代码
#include<vector> #include<algorithm> #include<iostream> using namespace std; extern vector<int> nums; void twoCond(int low,int high) { for(auto i=nums.begin();i!=nums.end();i++) { if(*i>=low && *i<high)break; } find_if(nums.begin(),nums.end(),compose2( logical_and<bool>(), bind2nd(less<int>(),high), bind2nd(greater_equal<int>(),low) )); find_if(nums.begin(),nums.end(),[=](int i) { return i>=low && i<high; }); }
再看看下面的lambda简化STL的例子
#include<vector> #include<algorithm> #include<iostream> using namespace std; vector<int> nums; void Add(const int val) { auto print =[]{ for(auto s:nums) { cout<<s<<"\t"; } cout<<endl; }; for(auto i=nums.begin();i!=nums.end();i++) { *i=*i+val; } print(); for_each(nums.begin(),nums.end(),bind2nd(plus<int>(),val)); print(); transform(nums.begin(),nums.end(),nums.begin(),bind2nd(plus<int>(),val)); print(); for_each(nums.begin(),nums.end(),[=](int &i){i+=val;}); print(); } int main() { for(int i=0;i<10;i++) nums.emplace_back(i); Add(10); } /* 10 11 12 13 14 15 16 17 18 19 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 37 38 39 */
我们发现上面代码运行后,第二行相较于第一行没有变化,如果熟悉STL,你会狠容易发现,因为for_each()它不会写回,而transform它会写回。STL新手就会容易犯这个错误,如果你使用lambda函数这些东西就不需要了。
#include<vector> #include<algorithm> #include<iostream> #include<numeric> using namespace std; void Stat(vector<int> &v) { int errors; int score; auto print =[&]{ cout<<"Errors: "<<errors<<endl <<"Score: "<<score<<endl; }; errors=accumulate(v.begin(),v.end(),0); score=accumulate(v.begin(),v.end(),100,minus<int>()); print(); errors=0; score=100; for_each(v.begin(),v.end(),[&](int i){ errors+=i; score-=i; }); print(); } int main() { vector<int> v(10); generate(v.begin(),v.end(),[]{return rand()&10;}); Stat(v); }
总之,lambda函数非常好用
加载全部内容