【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】lambda函数的总结
C++ 11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。
Good Good Study, Day Day Up.
顺序 选择 循环 总结