伙伴云客服论坛»论坛 S区 S软件开发 查看内容

0 评论

0 收藏

分享

通晓C++11的lambda函数

目录

    1.lambda函数语法
      1.1 捕获列表1.2 mutable修饰符1.3 匿名lambda函数
    2.lambda与STL

我可以明确告诉你:lambda函数是C++11中最重要的,使用最广泛的,最具现代风格的内容,lambda函数的呈现改变了C++编程的思维方式。
  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5.     int girls=3,boys=4;
  6.     auto totalChild=[](int x,int y)->int{return x+y;};
  7.     return totalChild(girls,boys);
  8. }
复制代码
上面中,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 捕获列表
  1. int main()
  2. {
  3.     []{};//最简单的lambda函数
  4.     int a=3;
  5.     int b=4;
  6.     [=]{return a+b;};
  7.     auto func1=[&](int c){b=a+c;};
  8.     auto func2=[=,&b](int c)->int{return b+=a+c;};
  9. }。
复制代码
上面代码中,我们可以使用捕获列表来捕获,变量a和b
被捕获的变量和基于参数传送的变量是不同的,被捕获的变量更是一种lambda函数的初始状态。
捕获列表是由多个捕获项组成的:
    [var]:表示按值方式捕获变量var[=]:表示按值捕获其父作用域中所有可用的变量(包括this)[&var]:表示按引用捕获变量var[&]:表示按引用捕获其父作用域中所有可用的变量(包括this)[this]:表示按值传送当前的this指针
特别的这些捕获项可用组合使用,例如:[=,&a,&b]表示以引用捕获a和b,按值捕获其他所有变量。[&,a,this]表示按值捕获a和this,按引用捕获其他所有变量。
下面看一段代码
  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5.     int j=12;
  6.     auto fun1=[=]{return j;};
  7.     auto fun2=[&]{return j;};
  8.     cout<<"fun1: "<<fun1()<<endl;
  9.     cout<<"fun2: "<<fun2()<<endl;
  10.     j++;
  11.     cout<<"fun1: "<<fun1()<<endl;
  12.     cout<<"fun2: "<<fun2()<<endl;
  13. }
  14. /*
  15. fun1: 12
  16. fun2: 12
  17. fun1: 12
  18. fun2: 13
  19. */
复制代码
当j++后,再次调用func1()时,我们发现它里面的j却坚持不变,所以上面这段代码反响了一个事实:捕获的变量是lambda函数的初始状态。
在使用lambda函数时,按值传送的变量成为函数中的常量,它不会再运行过程中改变,按引用传送的变量,它类似于函数参数,他会随时检查其值。
  1. #include<iostream>
  2. using namespace std;
  3. int temp=0;
  4. int main()
  5. {
  6.     static int a=0;
  7.     auto fun=[]{temp++;a++;};
  8.     fun();
  9.     fun();
  10.     cout<<temp<<endl;//2
  11.     cout<<a<<endl;//2;
  12.     [temp]{};//编译错误
  13.     [a]{};//编译错误
  14. }
复制代码
lambda函数中也可以直接使用全局变量,但是假设fun1这样就是错误的,因为 lambda函数只能捕获其父作用域中可用的自动变量,而静态变量不需要捕获,可以直接使用。
  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5.     int a=1;
  6.     cout<<"a="<<a<<endl;
  7.     auto foo1=[&]()
  8.     {
  9.         a++;
  10.         cout<<"a="<<a<<endl;
  11.         auto foo2=[&]()
  12.         {
  13.             a++;
  14.             cout<<"a="<<a<<endl;
  15.         };
  16.         foo2();
  17.     };
  18.     foo1();
  19. }
  20. /*
  21. a=1
  22. a=2
  23. a=3
  24. */
复制代码
上面代码中说明,lambda函数中还可以使用lambda函数,这样子,上面代码就狠像pascal语言中的内嵌函数。

1.2 mutable修饰符

lambda函数是具有常量性的,下面这段代码是在stackoverflow网站中的一次讨论:
  1. int main()
  2. {
  3.     int val;
  4.     auto fun1=[=]{val=3;};//编译失败,val无法被赋值
  5.     auto fun2=[=]() mutable {val=3;};
  6.     auto fun3=[&]{val=3;};
  7.     auto fun4=[](int v){v=3;};
  8.     fun4(val);
  9. }
复制代码
上面中,fun1无法通过编译,因为val是按值传送的,所以在函数体中,val就是一个只读常量,无法对其停止赋值,我们可以使用修饰符mutable来取消其只读属性。这样的目的是只是提供一种语法上的可能,在实际使用的时候,我们一般不需要使用mutable,假设需要修改按值传送的值,我们可以直接按值传送参数,而不是捕获它。
实际上,lambda函数中的捕获变量,更像是函数对象(仿函数)中的私有数据成员:
  1. class fun1
  2. {
  3. private:
  4.     int val;
  5. public:
  6.     fun1(int v):val(v){};
  7.     void operator()const{val=3;};//编译出错
  8. }
复制代码
默认情况下,按值捕获的变量,假设不加mutable,它就会等价于上面的仿函数,这里的operator()就是const修饰的,它不允许修改val。

1.3 匿名lambda函数

lambda函数自身是右值,它没有名字,它自身就是匿名的,我们一般通过auto来赋予它一个名字,这样就能生成类似一个部分函数的效果。我们也可以不使用auto,我们可以直接生成一个lambda函数,然后调用,例如下面这段代码:
  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5.     const int a=[]{
  6.         int ret=0;
  7.         for(int i=0;i<100;i++)
  8.         {
  9.             ret+=i;
  10.         }
  11.         return ret;
  12.     }();
  13.     cout<<a<<endl;
  14. }
复制代码
lambda函数定义后直接调用。
实际上,lambda函数的设计初衷就是:就地书写,就地使用。所以诸如上面的写法非常常见。

2.lambda与STL

lambda函数的呈现,让我们发现使用STL算法更加简单了。
例如for_each()算法,它接收3个参数,前两个是指示范围的迭代器类型,第3个是接收一个参数的函数符(即仿函数,函数指针,lambda函数)。
  1. #include<vector>
  2. #include<algorithm>
  3. #include<iostream>
  4. using namespace std;
  5. extern vector<int> nums;
  6. void OneCond(int val)
  7. {
  8.     //传统for方法
  9.     for(auto i=nums.begin();i!=nums.end();++i)
  10.     {
  11.         if(*i==val)
  12.             break;
  13.     }
  14.     //使用adapter
  15.     find_if(nums.begin(),nums.end(),bind2nd(equal_to<int>(),val));
  16.     //使用lambda函数
  17.     find_if(nums.begin(),nums.end(),[=](int i){
  18.         return i==val;
  19.     });
  20. }
复制代码
上面代码中,有些人认为使用这种adapter会简单一点,就像这里的equal_to<int>()它就是是一个函数对象,我只能说仁者见仁。但是这种使用adapter的方式创建函数对象,要求程序员懂很多STL的知识,而且可读性不好,例如下面这段代码
  1. #include<vector>
  2. #include<algorithm>
  3. #include<iostream>
  4. using namespace std;
  5. extern vector<int> nums;
  6. void twoCond(int low,int high)
  7. {
  8.     for(auto i=nums.begin();i!=nums.end();i++)
  9.     {
  10.         if(*i>=low && *i<high)break;
  11.     }
  12.     find_if(nums.begin(),nums.end(),compose2(
  13.         logical_and<bool>(),
  14.         bind2nd(less<int>(),high),
  15.         bind2nd(greater_equal<int>(),low)
  16.     ));
  17.     find_if(nums.begin(),nums.end(),[=](int i)
  18.     {
  19.         return i>=low && i<high;
  20.     });
  21. }
复制代码
再看看下面的lambda简化STL的例子
  1. #include<vector>
  2. #include<algorithm>
  3. #include<iostream>
  4. using namespace std;
  5. vector<int> nums;
  6. void Add(const int val)
  7. {
  8.     auto print =[]{
  9.         for(auto s:nums)
  10.         {
  11.             cout<<s<<"\t";
  12.         }
  13.         cout<<endl;
  14.     };
  15.     for(auto i=nums.begin();i!=nums.end();i++)
  16.     {
  17.         *i=*i+val;
  18.     }
  19.     print();
  20.     for_each(nums.begin(),nums.end(),bind2nd(plus<int>(),val));
  21.     print();
  22.     transform(nums.begin(),nums.end(),nums.begin(),bind2nd(plus<int>(),val));
  23.     print();
  24.     for_each(nums.begin(),nums.end(),[=](int &i){i+=val;});
  25.     print();
  26. }
  27. int main()
  28. {
  29.     for(int i=0;i<10;i++)
  30.     nums.emplace_back(i);
  31.     Add(10);
  32. }
  33. /*
  34. 10      11      12      13      14      15      16      17      18      19
  35. 10      11      12      13      14      15      16      17      18      19
  36. 20      21      22      23      24      25      26      27      28      29
  37. 30      31      32      33      34      35      36      37      38      39
  38. */
复制代码
我们发现上面代码运行后,第二行相较于第一行没有变化,假设熟悉STL,你会狠容易发现,因为for_each()它不会写回,而transform它会写回。STL新手就会容易犯这个错误,假设你使用lambda函数这些东西就不需要了。
  1. #include<vector>
  2. #include<algorithm>
  3. #include<iostream>
  4. #include<numeric>
  5. using namespace std;
  6. void Stat(vector<int> &v)
  7. {
  8.     int errors;
  9.     int score;
  10.     auto print =[&]{
  11.         cout<<"Errors: "<<errors<<endl
  12.         <<"Score: "<<score<<endl;
  13.     };
  14.     errors=accumulate(v.begin(),v.end(),0);
  15.     score=accumulate(v.begin(),v.end(),100,minus<int>());
  16.     print();
  17.     errors=0;
  18.     score=100;
  19.     for_each(v.begin(),v.end(),[&](int i){
  20.         errors+=i;
  21.         score-=i;
  22.     });
  23.     print();
  24. }
  25. int main()
  26. {
  27.     vector<int> v(10);
  28.     generate(v.begin(),v.end(),[]{return rand()&10;});
  29.     Stat(v);
  30. }
复制代码
总之,lambda函数非常好用
以上就是一文详解C++11中的lambda函数的详细内容,更多关于C++11 lambda函数的资料请关注网站其它相关文章!

回复

举报 使用道具

相关帖子
全部回复
暂无回帖,快来参与回复吧
本版积分规则 高级模式
B Color Image Link Quote Code Smilies

薇可馨
注册会员
主题 25
回复 27
粉丝 0
|网站地图
快速回复 返回顶部 返回列表