zoukankan      html  css  js  c++  java
  • C++11—lambda函数

    【1】lambda表达式语法

    lambda表达式的语法定义如下:

    [capture](parameters)mutable ->return-type { statement };

    (1)[capture]: 捕捉列表。捕捉列表总是出现在lambda函数的开始处。实质上,[]是lambda引出符(即独特的标志符)

    编译器根据该引出符判断接下来的代码是否是lambda函数

    捕捉列表能够捕捉上下文中的变量以供lambda函数使用

    捕捉列表由一个或多个捕捉项组成,并以逗号分隔,捕捉列表一般有以下几种形式:

    <1> [var] 表示值传递方式捕捉变量var

    <2> [=] 表示值传递方式捕捉所有父作用域的变量(包括this指针)

    <3> [&var] 表示引用传递捕捉变量var

    <4> [&] 表示引用传递捕捉所有父作用域的变量(包括this指针)

    <5> [this] 表示值传递方式捕捉当前的this指针

    <6> [=,&a,&b] 表示以引用传递的方式捕捉变量 a 和 b,而以值传递方式捕捉其他所有的变量

    <7> [&,a,this] 表示以值传递的方式捕捉 a 和 this,而以引用传递方式捕捉其他所有变量

    备注:父作用域是指包含lambda函数的语句块

    另外,需要注意的是,捕捉列表不允许变量重复传递。下面的例子就是典型的重复,会导致编译错误:

    [=, a] 这里 = 已经以值传递方式捕捉了所有的变量,那么再捕捉 a 属于重复

    [&,&this] 这里 & 已经以引用传递方式捕捉了所有变量,那么再捕捉 this 属于重复

    (2)(parameters): 参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略

    (3)mutable : mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性(后面有详解)

    在使用该修饰符时,参数列表不可省略(即使参数为空)

    (4)->return-type : 返回类型。用追踪返回类型形式声明函数的返回类型。

    出于方便,不需要返回值的时候也可以连同符号->一起省略

    此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导

    (5){statement} : 函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量

    在lambda函数的定义中,参数列表和返回类型都是可选的部分,而捕捉列表和函数体都可能为空

    那么,在极端情况下,C++11中最为简单的lambda函数只需要声明为:

    []{};

    就可以了。不过显然,这样的lambda函数不能做任何事情(乍一看好漂亮,其实仅是好看)。

    【2】lambda函数示例代码

    示例代码1:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 void main()
     5 {
     6     int a = 20, b = 10;
     7 
     8     auto totalAB = [] (int x, int y)->int { return x + y; };
     9     int aAddb = totalAB(a, b);
    10     cout << "aAddb :" << aAddb << endl;
    11 
    12     auto totalAB2 = [a, &b]()->int { return a + b; };
    13     int aAddb2 = totalAB2();
    14     cout << "aAddb2 :" << aAddb2 << endl;
    15 
    16     auto totalAB3 = [=]()->int { return a + b; };
    17     int aAddb3 = totalAB3();
    18     cout << "aAddb3 :" << aAddb3 << endl;
    19 
    20     []{}; // 最简lambda函数
    21     [=] { return a + b; }; // 省略了参数列表与返回类型,返回类型由编译器推断为int
    22     auto fun1 = [&] (int c) { b = a + c; }; // 省略了返回类型,无返回值
    23     auto fun2 = [=, &b](int c)->int { return b += a + c; }; // 各部分都很完整的lambda函数
    24     cout << "fun2(100) :" << fun2(100) << endl;
    25 }
    26 // Result:
    27 /*
    28 aAddb :30
    29 aAddb2 :30
    30 aAddb3 :30
    31 fun2(100) :130
    32 */

    以上代码仅供学习参考

    【3】lambda函数的作用

    lambda函数的使用示例代码:

     1 #include <iostream>
     2 #include <vector>
     3 #include <algorithm>
     4 #include "time.h"
     5 using namespace std;
     6 
     7 void main()
     8 {
     9     vector<int> nVec;
    10     for (int i = 0; i < 100000; ++i)
    11     {
    12         nVec.push_back(i);
    13     }
    14 
    15     double time_Start = (double)clock();
    16     for (vector<int>::const_iterator it = nVec.begin(); it != nVec.end(); ++it) 
    17     {
    18         cout << *it << endl;
    19     }
    20     double time_Finish = (double)clock();
    21     double time_Interval_1 = (double)(time_Finish - time_Start) / 1000;
    22     
    23     time_Start = (double)clock();
    24     for_each( nVec.begin(), nVec.end(), [] (int val){ cout << val << endl; } );
    25     time_Finish = (double)clock();
    26     double time_Interval_2 = (double)(time_Finish - time_Start) / 1000;
    27 
    28     cout << "time_Interval_1 :" << time_Interval_1 << endl;
    29     cout << "time_Interval_2 :" << time_Interval_2 << endl;
    30 
    31 }
    32 // Result:
    33 /*
    34 time_Interval_1 :17.748
    35 time_Interval_2 :17.513
    36 */

    lambda函数的引入为STL的使用提供了极大的方便。同样是遍历容器,效率反而提高了很多。

    【4】lambda函数 与 仿函数

    何谓仿函数?个人理解,像函数一样工作的对象。

    根据面向对象的编程思想,那么问题来了!既然主语是一个对象,创建这个对象的类长什么样子呢?

    据听说,所有科学中数学学科最重要,语文重要性次之。为什么呢?

    数学可以利用来解决问题,但当问题解决不了的时候,可以用语文涂画,涂画得让人听不懂。好像很高大上一样一样~

    关于仿函数,请看下面示例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class _functor_plus
     5 {
     6 private:
     7     int m_nValue;
     8 
     9 public:
    10     _functor_plus(int nValue = 100);
    11     _functor_plus operator+ (const _functor_plus & funObj);
    12     void printInfo();
    13 };
    14 
    15 _functor_plus::_functor_plus(int nValue) : m_nValue(nValue)
    16 {
    17 }
    18 
    19 _functor_plus _functor_plus::operator+ (const _functor_plus & funObj)
    20 {
    21     m_nValue += funObj.m_nValue;
    22     return _functor_plus(m_nValue);
    23 }
    24 
    25 void _functor_plus::printInfo()
    26 {
    27     cout << m_nValue << endl;
    28 }
    29 
    30 class _functor_override
    31 {
    32 public:
    33     int operator()(int x, int y);
    34 };
    35 
    36 int _functor_override::operator()(int x, int y)
    37 {
    38     return x + y;
    39 }
    40 
    41 int main()
    42 {
    43     _functor_plus plusA, plusB, plusC;
    44     plusC = plusA + plusB;
    45     plusA.printInfo();
    46     plusB.printInfo();
    47     plusC.printInfo();
    48 
    49     int boys = 4, girls = 3;
    50     _functor_override totalChildren;
    51     cout << "totalChildren(int, int): " << totalChildren(boys, girls);
    52 }
    53 // Result:
    54 /*
    55 200
    56 100
    57 200
    58 totalChildren(int, int): 7
    59 */

    在这个例子中,_functor_override类的operator()被重载。

    因此,在调用该函数的时候,我们看到与函数调用一样的形式。

    只不过这里的totalChildren不是函数名称,而是一个对象名称。

    相比于函数,仿函数可以拥有初始化状态:

    一般通过class定义私有成员,并在声明对象的时候对其进行初始化,

    那般,私有成员的状态就成了仿函数的初始状态。

    由于声明一个仿函数对象可以拥有多个不同的初始状态的实例,

    因此,可以借由仿函数产生多个功能类似实质却各不同的仿函数实例。

    请参见下例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Tax
     5 {
     6 private:
     7     double m_dRate;
     8     int m_nBase;
     9 
    10 public:
    11     Tax(double dRate, int nBase) : m_dRate(dRate), m_nBase(nBase)
    12     {
    13     }
    14 
    15     double operator() (double dMoney)
    16     {
    17         return (dMoney - m_nBase) * m_dRate;
    18     }
    19 };
    20 
    21 int main()
    22 {
    23     Tax high(0.40, 30000);
    24     Tax middle(0.25, 20000);
    25     cout << "tax over 3w: " << high(37500) << endl;
    26     cout << "tax over 2w: " << middle(24000) << endl;
    27 }
    28 // Result:
    29 /*
    30 tax over 3w: 3000
    31 tax over 2w: 1000
    32 */

    到这里,是否发现仿函数和lambda之间存在一种“衍生”的关系?

    难道还不明显?没看懂?咱再接着剖析,谁让程序员就这么理性呢?

    请再看下例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class AirportPrice
     5 {
     6 private:
     7     double m_dDutyfreeRate;
     8 
     9 public:
    10     AirportPrice(double dDutyfreeRate) : m_dDutyfreeRate(dDutyfreeRate)
    11     {}
    12 
    13     double operator() (double dPrice)
    14     {
    15         return dPrice * (1 - m_dDutyfreeRate/100);
    16     }
    17 };
    18 
    19 void main()
    20 {
    21     double dRate = 5.5;
    22     AirportPrice fanFunObj(dRate);
    23 
    24     auto ChangLambda = [dRate](double dPrice)->double
    25     {
    26         return dPrice * (1 - dRate/100);
    27     };
    28     double purchased1 = fanFunObj(3699);
    29     double purchased2 = ChangLambda(3699);
    30     cout << "purchased1:" << purchased1 << endl;
    31     cout << "purchased2:" << purchased2 << endl;
    32 }
    33 // Result:
    34 /*
    35 purchased1:3495.55
    36 purchased2:3495.55
    37 */

    分别使用了仿函数和lambda两种方式来完成扣税后的产品价格计算。

    lamba函数捕捉了dRate变量,而仿函数则以dRate进行初始化类。

    其他的,在参数传递上,两者保持一致,结果也一致。

    可以看到,除去在语法层面的差异,lambda函数和仿函数有着相同的内涵:

    即都可以捕捉一些变量作为初始化状态,并接受参数进行运算。

    而事实上,仿函数正是编译器实现lambda的一种方式。

    在现阶段,通常编译器都会把lambda函数转化为一个仿函数对象。

    因此,C++11中,lambda可以视为仿函数的一种等价形式。

    备注:有时,编译时发现lambda函数出现了错误,编译器会提示一些构造函数相关的信息,

    显然是由于lambda的这种实现方式造成的。理解这种实现也能够正确理解错误信息的由来。

    【5】lambda函数等同于一个局部函数

    局部函数,在函数作用域中定义的函数,也称为内嵌函数。

    局部函数通常仅属于其父作用域,能够访问父作用域的变量。

    C/C++语言标准中不允许局部函数存在(FORTRAN语言支持)

    C++11标准却用比较优雅的方式打破了这个规则。

    因为事实上,lambda可以像局部函数一样使用。请参见下例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 extern int z = 100;
     5 extern float c = 100.00;
     6 
     7 void Calc(int& rnOne, int nTwo, float& rfThree, float fFour)
     8 {
     9     rnOne = nTwo;
    10     rfThree = fFour;
    11 }
    12 
    13 void TestCalc()
    14 {
    15     int x, y = 3;
    16     float a, b = 4.0;
    17     int success = 0;
    18 
    19     auto validate = [&]()->bool
    20     {
    21         if ((x == y + z) && (a == b + c))
    22             return 1;
    23         else
    24             return 0;
    25     };
    26 
    27     Calc(x, y, a, b);
    28     success += validate();
    29 
    30     y = 1024;
    31     b = 100.0;
    32     Calc(x, y, a, b);
    33     success += validate();
    34 }
    35 
    36 void main()
    37 {
    38 }

    在没有lambd函数之前,通常需要在TestCalc外声明同样一个函数,

    并且把TestCalc中的变量当作参数进行传递。

    出于函数作用域及运行效率的考虑,那样声明函数通常要加上关键字static 和 inline

    相比于一个传统意义上的函数定义,lambda函数在这里直观,使用方便可读性很好。请参见下例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 int Prioritize(int nValue)
     5 {
     6     return nValue + 10;
     7 }
     8 
     9 int  AllWorks(int nTimes)
    10 {
    11     int i = 0, x = 0;
    12     try
    13     {
    14         for (i = 0; i < nTimes; ++i)
    15         {
    16             x += Prioritize(i);
    17         }
    18     }
    19     catch (...)
    20     {
    21         x = 0;
    22     }
    23 
    24     const int y = [=]()->int
    25     {
    26         int i = 0, val = 0;
    27         try
    28         {
    29             for (; i < nTimes; ++i)
    30             {
    31                 val += Prioritize(i);
    32             }
    33         }
    34         catch (...)
    35         {
    36             val = 0;
    37         }
    38         return val;
    39     }();
    40     // lambda表达式
    41     {
    42         []{}();
    43         [](){}();
    44         []{ cout << "emptyLambdaExec" << endl; }();
    45         [=](){ cout << "const int y :" << y << endl; }();
    46         [&](){ cout << "int x :" << x << endl; }();
    47     }
    48 
    49     return 0;
    50 }
    51 
    52 void main()
    53 {
    54     AllWorks(10);
    55 }
    56 
    57 // Result:
    58 /*
    59 emptyLambdaExec
    60 const int y :145
    61 int x :145
    62 */

    备注:注意此例中的lambda表达式作用域中比较特殊的几个lambda函数。

    【6】关于lambda的一些问题及其有趣的测试

    (1)使用lambda函数时候,不同的捕捉方式会导致不同的结果:

    请看下例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 void main()
     5 {
     6     int j = 10;
     7     auto by_val_lambda = [=] { return j + 1; };
     8     auto by_ref_lambda = [&] { return j + 1; };
     9     cout << "by_val_lambda: " << by_val_lambda() << endl;
    10     cout << "by_ref_lambda: " << by_ref_lambda() << endl;
    11     ++j;
    12     cout << "by_val_lambda: " << by_val_lambda() << endl;
    13     cout << "by_ref_lambda: " << by_ref_lambda() << endl;
    14 }
    15 
    16 //Result:
    17 /*
    18 by_val_lambda: 11
    19 by_ref_lambda: 11
    20 by_val_lambda: 11
    21 by_ref_lambda: 12
    22 */

    充分说明了传值和引用方式的区别。

    (2)使用lambda函数与函数指针

    一般情况下,把匿名的lambda函数赋值给一个auto类型的变量,

    这是一种声明和使用lambda函数的方法。

    结合关于auto的知识,有人会猜测totalChild是一种函数指针类型的变量

    结合lambda函数和仿函数之间关系,大多人会倾向于认为lambda是一种自定义类型。

    实质上,lambda的类型并非简单函数指针类型或自定义类型。

    从C++11标准定义发现,lambda类型被定义为“闭包”的类,而每一个lambda表达式则会产生一个闭包类型的临时对象。

    也因此,严格地讲,lambda函数并非函数指针。

    但是,C++11标准却允许lambda表达式向函数指针的转换,

    前提是lambda函数没有捕捉任何变量,且函数指针所示的函数原型,必须跟lambda函数有着相同的调用方式。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 void main()
     5 {
     6     int girs = 3, boys = 4;
     7     auto totalChild = [](int x, int y)->int{ return x + y; };
     8     typedef int (*pFunAll)(int x, int y);
     9     typedef int (*pFunOne)(int x);
    10 
    11     pFunAll funAll;
    12 //  funAll = totalChild; // 编译失败!
    13 
    14     pFunOne funOne;
    15 //  funOne = totalChild; //编译失败!参数必须一致
    16 
    17     decltype(totalChild) allPeople = totalChild; // 需通过decltype获得lambda的类型
    18 //  decltype(totalChild) totalPeople = funAll; // 编译失败,指针无法转换lambda
    19 }

    第 12 行,编译错误信息如下:

    error C2440: “=”: 无法从“`anonymous-namespace'::<lambda0>”转换为“pFunAll”  

             没有可用于执行该转换的用户定义的转换运算符,或者无法调用该运算符

    MSVC10环境下,第一步编译不通过。

    关于此问题参见文章《在 MSVC10 下,將 lambda expression 轉換成 C 的 function pointer

    第 15 行 编译失败,参数不一致

    第 18 行 编译失败,函数指针转换为lambda也是不成功的。

    值得注意的是,可以通过decltype的方式获取lambda函数的类型。

    (3)lambda函数的常量性以及mutable关键字

    C++11中,默认情况下lambda函数是一个const函数。

    神马意思呢?请参见下例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class const_val_lambda
     5 {
     6 public:
     7     const_val_lambda(int v) : m_nVal(v)
     8     {}
     9 public:
    10     void operator() () const
    11     {
    12 //        m_nVal = 3;  /*注意:常量成员函数*/ 
    13     }
    14 
    15     void ref_const_Fun(int& nValue) const
    16     {
    17         nValue = 100;
    18     }
    19 
    20 private:
    21     int m_nVal;
    22 };
    23 
    24 void main()
    25 {
    26     int val = 10;
    27     // 编译失败!在const的lambda中修改常量
    28 //    auto const_val_lambda = [=]() { val = 3;}; // 不能在非可变 lambda 中修改按值捕获
    29     // 非const的lambda,可以修改常量数据
    30     auto mutable_val_lambda = [=]() mutable{ val = 3; };
    31     // 依然是const的lambda,不过没有改动引用本身
    32     auto const_ref_lambda = [&] { val = 3; };
    33     // 依然是const的lambda,通过参数传递val
    34     auto const_param_lambda = [&](int varA) { varA = 3;};
    35     const_param_lambda(val);
    36 }

    备注:使用引用方式传递的变量在常量成员函数中修改值并不会导致错误。

    【7】lambda 与 STL

    lambda对C++11最大的贡献,或者说改变,应该在STL库中

    相关应用,具体请再参见下例:

     1 #include <vector>
     2 #include <algorithm>
     3 #include <iostream>
     4 using namespace std;
     5 
     6 const int ubound = 3;
     7 
     8 vector<int> nums;
     9 vector<int> largeNums;
    10 
    11 void initNums()
    12 {
    13     for (int i = 1; i < 5; ++i)
    14     {
    15         nums.push_back(i);
    16     }
    17 }
    18 
    19 inline void largeNumsFunc(int i)
    20 {
    21     if (i > ubound)
    22     {
    23         largeNums.push_back(i);
    24     }
    25 }
    26 
    27 void filter()
    28 {
    29     for (auto it = nums.begin(); it != nums.end(); ++it)
    30     {
    31         if ((*it) > ubound)
    32         {
    33             largeNums.push_back(*it);
    34         }
    35     }
    36 
    37     for_each (nums.begin(), nums.end(), largeNumsFunc);
    38 
    39     for_each (nums.begin(), nums.end(), [=](int i)
    40     {
    41         if (i > ubound)
    42         {
    43             largeNums.push_back(i);
    44         }
    45     });
    46 }
    47 
    48 void printInfo()
    49 {
    50     for_each (largeNums.begin(), largeNums.end(), [=](int i)
    51     {
    52         cout << i << " ";
    53     });
    54     cout << endl;
    55 }
    56 
    57 void main()
    58 {
    59     initNums(); // 初始化值
    60     filter(); // 过滤值
    61     printInfo(); //打印信息
    62 }

    具体遇到其它的问题 ,再具体分析和学习。

    【8】在返回类型明确的情况下,可以省略返回值类型,让编译器对返回类型进行推导

    第一部分第4小节:“此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导”,示例如下:

     1 #include <iostream>
     2 #include <string>
     3 
     4 int main()
     5 {
     6     std::string str = "user_behavior_log";
     7     auto key = [&]() {
     8         if (str.empty())
     9         {
    10             return std::string{};
    11         }
    12 
    13         return (str + ".json");
    14     };
    15 
    16     std::cout << key() << std::endl;
    17 
    18     return 0;
    19 }
    20 
    21 // result
    22 /*
    23 user_behavior_log.json
    24 */

    希望能加深对返回类型的理解。

    【9】lambda函数的总结

    C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。

    Good Good Study, Day Day Up.

    顺序 选择 循环 总结

  • 相关阅读:
    Web前端学习第五天——————HTML篇.019页面布局练习
    1111
    开发者如何利用数据分析提高收入
    开发者进行广告合作的几大误区
    高仿人人Android梦想版终极源码发送(转)
    移动开发者如何获取免费流量
    Inno setup常用代码【收藏】
    Inno setup常用代码补充【收藏】
    QT for Window程序部署
    Inno Setup自定义卸载文件名称【收藏】
  • 原文地址:https://www.cnblogs.com/Braveliu/p/4231818.html
Copyright © 2011-2022 走看看