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:

 #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.

顺序 选择 循环 总结

上一篇:spring boot中利用mybatis-generator插件生成代码


下一篇:Java 最常见的 200+ 面试题汇总