【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:
#include <iostream>
using namespace std; void main()
{
int a = , b = ; auto totalAB = [] (int x, int y)->int { return x + y; };
int aAddb = totalAB(a, b);
cout << "aAddb :" << aAddb << endl; auto totalAB2 = [a, &b]()->int { return a + b; };
int aAddb2 = totalAB2();
cout << "aAddb2 :" << aAddb2 << endl; auto totalAB3 = [=]()->int { return a + b; };
int aAddb3 = totalAB3();
cout << "aAddb3 :" << aAddb3 << endl; []{}; // 最简lambda函数
[=] { return a + b; }; // 省略了参数列表与返回类型,返回类型由编译器推断为int
auto fun1 = [&] (int c) { b = a + c; }; // 省略了返回类型,无返回值
auto fun2 = [=, &b](int c)->int { return b += a + c; }; // 各部分都很完整的lambda函数
cout << "fun2(100) :" << fun2() << endl;
}
// Result:
/*
aAddb :30
aAddb2 :30
aAddb3 :30
fun2(100) :130
*/
以上代码仅供学习参考
【3】lambda函数的作用
lambda函数的使用示例代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include "time.h"
using namespace std; void main()
{
vector<int> nVec;
for (int i = ; i < ; ++i)
{
nVec.push_back(i);
} double time_Start = (double)clock();
for (vector<int>::const_iterator it = nVec.begin(); it != nVec.end(); ++it)
{
cout << *it << endl;
}
double time_Finish = (double)clock();
double time_Interval_1 = (double)(time_Finish - time_Start) / ; time_Start = (double)clock();
for_each( nVec.begin(), nVec.end(), [] (int val){ cout << val << endl; } );
time_Finish = (double)clock();
double time_Interval_2 = (double)(time_Finish - time_Start) / ; cout << "time_Interval_1 :" << time_Interval_1 << endl;
cout << "time_Interval_2 :" << time_Interval_2 << endl; }
// Result:
/*
time_Interval_1 :17.748
time_Interval_2 :17.513
*/
lambda函数的引入为STL的使用提供了极大的方便。同样是遍历容器,效率反而提高了很多。
【4】lambda函数 与 仿函数
何谓仿函数?个人理解,像函数一样工作的对象。
根据面向对象的编程思想,那么问题来了!既然主语是一个对象,创建这个对象的类长什么样子呢?
据听说,所有科学中数学学科最重要,语文重要性次之。为什么呢?
数学可以利用来解决问题,但当问题解决不了的时候,可以用语文涂画,涂画得让人听不懂。好像很高大上一样一样~
关于仿函数,请看下面示例:
#include <iostream>
using namespace std; class _functor_plus
{
private:
int m_nValue; public:
_functor_plus(int nValue = );
_functor_plus operator+ (const _functor_plus & funObj);
void printInfo();
}; _functor_plus::_functor_plus(int nValue) : m_nValue(nValue)
{
} _functor_plus _functor_plus::operator+ (const _functor_plus & funObj)
{
m_nValue += funObj.m_nValue;
return _functor_plus(m_nValue);
} void _functor_plus::printInfo()
{
cout << m_nValue << endl;
} class _functor_override
{
public:
int operator()(int x, int y);
}; int _functor_override::operator()(int x, int y)
{
return x + y;
} int main()
{
_functor_plus plusA, plusB, plusC;
plusC = plusA + plusB;
plusA.printInfo();
plusB.printInfo();
plusC.printInfo(); int boys = , girls = ;
_functor_override totalChildren;
cout << "totalChildren(int, int): " << totalChildren(boys, girls);
}
// Result:
/*
200
100
200
totalChildren(int, int): 7
*/
在这个例子中,_functor_override类的operator()被重载。
因此,在调用该函数的时候,我们看到与函数调用一样的形式。
只不过这里的totalChildren不是函数名称,而是一个对象名称。
相比于函数,仿函数可以拥有初始化状态:
一般通过class定义私有成员,并在声明对象的时候对其进行初始化,
那般,私有成员的状态就成了仿函数的初始状态。
由于声明一个仿函数对象可以拥有多个不同的初始状态的实例,
因此,可以借由仿函数产生多个功能类似实质却各不同的仿函数实例。
请参见下例:
#include <iostream>
using namespace std; class Tax
{
private:
double m_dRate;
int m_nBase; public:
Tax(double dRate, int nBase) : m_dRate(dRate), m_nBase(nBase)
{
} double operator() (double dMoney)
{
return (dMoney - m_nBase) * m_dRate;
}
}; int main()
{
Tax high(0.40, );
Tax middle(0.25, );
cout << "tax over 3w: " << high() << endl;
cout << "tax over 2w: " << middle() << endl;
}
// Result:
/*
tax over 3w: 3000
tax over 2w: 1000
*/
到这里,是否发现仿函数和lambda之间存在一种“衍生”的关系?
难道还不明显?没看懂?咱再接着剖析,谁让程序员就这么理性呢?
请再看下例:
#include <iostream>
using namespace std; class AirportPrice
{
private:
double m_dDutyfreeRate; public:
AirportPrice(double dDutyfreeRate) : m_dDutyfreeRate(dDutyfreeRate)
{} double operator() (double dPrice)
{
return dPrice * ( - m_dDutyfreeRate/);
}
}; void main()
{
double dRate = 5.5;
AirportPrice fanFunObj(dRate); auto ChangLambda = [dRate](double dPrice)->double
{
return dPrice * ( - dRate/);
};
double purchased1 = fanFunObj();
double purchased2 = ChangLambda();
cout << "purchased1:" << purchased1 << endl;
cout << "purchased2:" << purchased2 << endl;
}
// Result:
/*
purchased1:3495.55
purchased2:3495.55
*/
分别使用了仿函数和lambda两种方式来完成扣税后的产品价格计算。
lamba函数捕捉了dRate变量,而仿函数则以dRate进行初始化类。
其他的,在参数传递上,两者保持一致,结果也一致。
可以看到,除去在语法层面的差异,lambda函数和仿函数有着相同的内涵:
即都可以捕捉一些变量作为初始化状态,并接受参数进行运算。
而事实上,仿函数正是编译器实现lambda的一种方式。
在现阶段,通常编译器都会把lambda函数转化为一个仿函数对象。
因此,C++11中,lambda可以视为仿函数的一种等价形式。
备注:有时,编译时发现lambda函数出现了错误,编译器会提示一些构造函数相关的信息,
显然是由于lambda的这种实现方式造成的。理解这种实现也能够正确理解错误信息的由来。
【5】lambda函数等同于一个局部函数
局部函数,在函数作用域中定义的函数,也称为内嵌函数。
局部函数通常仅属于其父作用域,能够访问父作用域的变量。
C/C++语言标准中不允许局部函数存在(FORTRAN语言支持)
C++11标准却用比较优雅的方式打破了这个规则。
因为事实上,lambda可以像局部函数一样使用。请参见下例:
#include <iostream>
using namespace std; extern int z = ;
extern float c = 100.00; void Calc(int& rnOne, int nTwo, float& rfThree, float fFour)
{
rnOne = nTwo;
rfThree = fFour;
} void TestCalc()
{
int x, y = ;
float a, b = 4.0;
int success = ; auto validate = [&]()->bool
{
if ((x == y + z) && (a == b + c))
return ;
else
return ;
}; Calc(x, y, a, b);
success += validate(); y = ;
b = 100.0;
Calc(x, y, a, b);
success += validate();
} void main()
{
}
在没有lambd函数之前,通常需要在TestCalc外声明同样一个函数,
并且把TestCalc中的变量当作参数进行传递。
出于函数作用域及运行效率的考虑,那样声明函数通常要加上关键字static 和 inline
相比于一个传统意义上的函数定义,lambda函数在这里直观,使用方便可读性很好。请参见下例:
#include <iostream>
using namespace std; int Prioritize(int nValue)
{
return nValue + ;
} int AllWorks(int nTimes)
{
int i = , x = ;
try
{
for (i = ; i < nTimes; ++i)
{
x += Prioritize(i);
}
}
catch (...)
{
x = ;
} const int y = [=]()->int
{
int i = , val = ;
try
{
for (; i < nTimes; ++i)
{
val += Prioritize(i);
}
}
catch (...)
{
val = ;
}
return val;
}();
// lambda表达式
{
[]{}();
[](){}();
[]{ cout << "emptyLambdaExec" << endl; }();
[=](){ cout << "const int y :" << y << endl; }();
[&](){ cout << "int x :" << x << endl; }();
} return ;
} void main()
{
AllWorks();
} // Result:
/*
emptyLambdaExec
const int y :145
int x :145
*/
备注:注意此例中的lambda表达式作用域中比较特殊的几个lambda函数。
【6】关于lambda的一些问题及其有趣的测试
(1)使用lambda函数时候,不同的捕捉方式会导致不同的结果:
请看下例:
#include <iostream>
using namespace std; void main()
{
int j = ;
auto by_val_lambda = [=] { return j + ; };
auto by_ref_lambda = [&] { return j + ; };
cout << "by_val_lambda: " << by_val_lambda() << endl;
cout << "by_ref_lambda: " << by_ref_lambda() << endl;
++j;
cout << "by_val_lambda: " << by_val_lambda() << endl;
cout << "by_ref_lambda: " << by_ref_lambda() << endl;
} //Result:
/*
by_val_lambda: 11
by_ref_lambda: 11
by_val_lambda: 11
by_ref_lambda: 12
*/
充分说明了传值和引用方式的区别。
(2)使用lambda函数与函数指针
一般情况下,把匿名的lambda函数赋值给一个auto类型的变量,
这是一种声明和使用lambda函数的方法。
结合关于auto的知识,有人会猜测totalChild是一种函数指针类型的变量
结合lambda函数和仿函数之间关系,大多人会倾向于认为lambda是一种自定义类型。
实质上,lambda的类型并非简单函数指针类型或自定义类型。
从C++11标准定义发现,lambda类型被定义为“闭包”的类,而每一个lambda表达式则会产生一个闭包类型的临时对象。
也因此,严格地讲,lambda函数并非函数指针。
但是,C++11标准却允许lambda表达式向函数指针的转换,
前提是lambda函数没有捕捉任何变量,且函数指针所示的函数原型,必须跟lambda函数有着相同的调用方式。
#include <iostream>
using namespace std; void main()
{
int girs = , boys = ;
auto totalChild = [](int x, int y)->int{ return x + y; };
typedef int (*pFunAll)(int x, int y);
typedef int (*pFunOne)(int x); pFunAll funAll;
// funAll = totalChild; // 编译失败! pFunOne funOne;
// funOne = totalChild; //编译失败!参数必须一致 decltype(totalChild) allPeople = totalChild; // 需通过decltype获得lambda的类型
// decltype(totalChild) totalPeople = funAll; // 编译失败,指针无法转换lambda
}
第 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函数。
神马意思呢?请参见下例:
#include <iostream>
using namespace std; class const_val_lambda
{
public:
const_val_lambda(int v) : m_nVal(v)
{}
public:
void operator() () const
{
// m_nVal = 3; /*注意:常量成员函数*/
} void ref_const_Fun(int& nValue) const
{
nValue = ;
} private:
int m_nVal;
}; void main()
{
int val = ;
// 编译失败!在const的lambda中修改常量
// auto const_val_lambda = [=]() { val = 3;}; // 不能在非可变 lambda 中修改按值捕获
// 非const的lambda,可以修改常量数据
auto mutable_val_lambda = [=]() mutable{ val = ; };
// 依然是const的lambda,不过没有改动引用本身
auto const_ref_lambda = [&] { val = ; };
// 依然是const的lambda,通过参数传递val
auto const_param_lambda = [&](int varA) { varA = ;};
const_param_lambda(val);
}
备注:使用引用方式传递的变量在常量成员函数中修改值并不会导致错误。
【7】lambda 与 STL
lambda对C++11最大的贡献,或者说改变,应该在STL库中
相关应用,具体请再参见下例:
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std; const int ubound = ; vector<int> nums;
vector<int> largeNums; void initNums()
{
for (int i = ; i < ; ++i)
{
nums.push_back(i);
}
} inline void largeNumsFunc(int i)
{
if (i > ubound)
{
largeNums.push_back(i);
}
} void filter()
{
for (auto it = nums.begin(); it != nums.end(); ++it)
{
if ((*it) > ubound)
{
largeNums.push_back(*it);
}
} for_each (nums.begin(), nums.end(), largeNumsFunc); for_each (nums.begin(), nums.end(), [=](int i)
{
if (i > ubound)
{
largeNums.push_back(i);
}
});
} void printInfo()
{
for_each (largeNums.begin(), largeNums.end(), [=](int i)
{
cout << i << " ";
});
cout << endl;
} void main()
{
initNums(); // 初始化值
filter(); // 过滤值
printInfo(); //打印信息
}
具体遇到其它的问题 ,再具体分析和学习。
【8】在返回类型明确的情况下,可以省略返回值类型,让编译器对返回类型进行推导
第一部分第4小节:“此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导”,示例如下:
#include <iostream>
#include <string> int main()
{
std::string str = "user_behavior_log";
auto key = [&]() {
if (str.empty())
{
return std::string{};
} return (str + ".json");
}; std::cout << key() << std::endl; return ;
} // result
/*
user_behavior_log.json
*/
希望能加深对返回类型的理解。
【9】lambda函数的总结
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。
Good Good Study, Day Day Up.
顺序 选择 循环 总结