实例讲解C++编程中lambda表达式的使用
函数对象与Lambdas
你编写代码时,尤其是使用STL算法时,可能会使用函数指针和函数对象来解决问题和执行计算。函数指针和函数对象各有利弊。例如,函数指针具有最低的语法开销,但不保持范围内的状态,函数对象可保持状态,但需要类定义的语法开销。
lambda结合了函数指针和函数对象的优点并避免其缺点。lambda与函数对象相似的是灵活并且可以保持状态,但不同的是其简洁的语法不需要显式类定义。使用lambda,相比等效的函数对象代码,您可以写出不太复杂并且不容易出错的代码。
下面的示例比较lambda和函数对象的使用。第一个示例使用lambda向控制台打印vector对象中的每个元素是偶数还是奇数。第二个示例使用函数对象来完成相同任务。
示例1:使用lambda
此示例将一个lambda传递给for_each函数。该lambda打印一个结果,该结果指出vector对象中的每个元素是偶数还是奇数。
代码
//even_lambda.cpp
//compilewith:cl/EHsc/nologo/W4/MTd
#include<algorithm>
#include<iostream>
#include<vector>
usingnamespacestd;
intmain()
{
//Createavectorobjectthatcontains10elements.
vector<int>v;
for(inti=1;i<10;++i){
v.push_back(i);
}
//Countthenumberofevennumbersinthevectorby
//usingthefor_eachfunctionandalambda.
intevenCount=0;
for_each(v.begin(),v.end(),[&evenCount](intn){
cout<<n;
if(n%2==0){
cout<<"iseven"<<endl;
++evenCount;
}else{
cout<<"isodd"<<endl;
}
});
//Printthecountofevennumberstotheconsole.
cout<<"Thereare"<<evenCount
<<"evennumbersinthevector."<<endl;
}
输出
1iseven 2isodd 3iseven 4isodd 5iseven 6isodd 7iseven 8isodd 9iseven Thereare4evennumbersinthevector.
批注
在此示例中,for_each函数的第三个参数是一个lambda。[&evenCount]部分指定表达式的捕获子句,(intn)指定参数列表,剩余部分指定表达式的主体。
示例2:使用函数对象
有时lambda过于庞大,无法在上一示例的基础上大幅度扩展。下一示例使用函数对象(而非lambda)以及for_each函数,以产生与示例1相同的结果。两个示例都在vector对象中存储偶数的个数。为保持运算的状态,FunctorClass类通过引用存储m_evenCount变量作为成员变量。为执行该运算,FunctorClass实现函数调用运算符operator()。VisualC++编译器生成的代码与示例1中的lambda代码在大小和性能上相差无几。对于类似本文中示例的基本问题,较为简单的lambda设计可能优于函数对象设计。但是,如果你认为该功能在将来可能需要重大扩展,则使用函数对象设计,这样代码维护会更简单。
有关operator()的详细信息,请参阅函数调用(C++)。
代码
//even_functor.cpp
//compilewith:/EHsc
#include<algorithm>
#include<iostream>
#include<vector>
usingnamespacestd;
classFunctorClass
{
public:
//Therequiredconstructorforthisexample.
explicitFunctorClass(int&evenCount)
:m_evenCount(evenCount){}
//Thefunction-calloperatorprintswhetherthenumberis
//evenorodd.Ifthenumberiseven,thismethodupdates
//thecounter.
voidoperator()(intn)const{
cout<<n;
if(n%2==0){
cout<<"iseven"<<endl;
++m_evenCount;
}else{
cout<<"isodd"<<endl;
}
}
private:
//DefaultassignmentoperatortosilencewarningC4512.
FunctorClass&operator=(constFunctorClass&);
int&m_evenCount;//thenumberofevenvariablesinthevector.
};
intmain()
{
//Createavectorobjectthatcontains10elements.
vector<int>v;
for(inti=1;i<10;++i){
v.push_back(i);
}
//Countthenumberofevennumbersinthevectorby
//usingthefor_eachfunctionandafunctionobject.
intevenCount=0;
for_each(v.begin(),v.end(),FunctorClass(evenCount));
//Printthecountofevennumberstotheconsole.
cout<<"Thereare"<<evenCount
<<"evennumbersinthevector."<<endl;
}
输出
1iseven 2isodd 3iseven 4isodd 5iseven 6isodd 7iseven 8isodd 9iseven Thereare4evennumbersinthevector.
声明Lambda表达式
示例1
由于lambda表达式已类型化,所以你可以将其指派给auto变量或function对象,如下所示:
代码
//declaring_lambda_expressions1.cpp
//compilewith:/EHsc/W4
#include<functional>
#include<iostream>
intmain()
{
usingnamespacestd;
//Assignthelambdaexpressionthataddstwonumberstoanautovariable.
autof1=[](intx,inty){returnx+y;};
cout<<f1(2,3)<<endl;
//Assignthesamelambdaexpressiontoafunctionobject.
function<int(int,int)>f2=[](intx,inty){returnx+y;};
cout<<f2(3,4)<<endl;
}
输出
5 7
备注
虽然lambda表达式多在函数的主体中声明,但是可以在初始化变量的任何地方声明。
示例2
VisualC++编译器将在声明而非调用lambda表达式时,将表达式绑定到捕获的变量。以下示例显示一个通过值捕获局部变量i并通过引用捕获局部变量j的lambda表达式。由于lambda表达式通过值捕获i,因此在程序后面部分中重新指派i不影响该表达式的结果。但是,由于lambda表达式通过引用捕获j,因此重新指派j会影响该表达式的结果。
代码
//declaring_lambda_expressions2.cpp
//compilewith:/EHsc/W4
#include<functional>
#include<iostream>
intmain()
{
usingnamespacestd;
inti=3;
intj=5;
//Thefollowinglambdaexpressioncapturesibyvalueand
//jbyreference.
function<int(void)>f=[i,&j]{returni+j;};
//Changethevaluesofiandj.
i=22;
j=44;
//Callfandprintitsresult.
cout<<f()<<endl;
}
输出
47
调用Lambda表达式
你可以立即调用lambda表达式,如下面的代码片段所示。第二个代码片段演示如何将lambda作为参数传递给标准模板库(STL)算法,例如find_if。
示例1
以下示例声明的lambda表达式将返回两个整数的总和并使用参数5和4立即调用该表达式:
代码
//calling_lambda_expressions1.cpp
//compilewith:/EHsc
#include<iostream>
intmain()
{
usingnamespacestd;
intn=[](intx,inty){returnx+y;}(5,4);
cout<<n<<endl;
}
输出
9
示例2
以下示例将lambda表达式作为参数传递给find_if函数。如果lambda表达式的参数是偶数,则返回true。
代码
//calling_lambda_expressions2.cpp
//compilewith:/EHsc/W4
#include<list>
#include<algorithm>
#include<iostream>
intmain()
{
usingnamespacestd;
//Createalistofintegerswithafewinitialelements.
list<int>numbers;
numbers.push_back(13);
numbers.push_back(17);
numbers.push_back(42);
numbers.push_back(46);
numbers.push_back(99);
//Usethefind_iffunctionandalambdaexpressiontofindthe
//firstevennumberinthelist.
constlist<int>::const_iteratorresult=
find_if(numbers.begin(),numbers.end(),[](intn){return(n%2)==0;});
//Printtheresult.
if(result!=numbers.end()){
cout<<"Thefirstevennumberinthelistis"<<*result<<"."<<endl;
}else{
cout<<"Thelistcontainsnoevennumbers."<<endl;
}
}
输出
Thefirstevennumberinthelistis42.
嵌套Lambda表达式
示例
你可以将lambda表达式嵌套在另一个中,如下例所示。内部lambda表达式将其参数与2相乘并返回结果。外部lambda表达式通过其参数调用内部lambda表达式并在结果上加3。
代码
//nesting_lambda_expressions.cpp
//compilewith:/EHsc/W4
#include<iostream>
intmain()
{
usingnamespacestd;
//Thefollowinglambdaexpressioncontainsanestedlambda
//expression.
inttimestwoplusthree=[](intx){return[](inty){returny*2;}(x)+3;}(5);
//Printtheresult.
cout<<timestwoplusthree<<endl;
}
输出
13备注
在该示例中,[](inty){returny*2;}是嵌套的lambda表达式。
高阶Lambda函数
示例
许多编程语言都支持高阶函数的概念。高阶函数是采用另一个lambda表达式作为其参数或返回lambda表达式的lambda表达式。你可以使用function类,使得C++lambda表达式具有类似高阶函数的行为。以下示例显示返回function对象的lambda表达式和采用function对象作为其参数的lambda表达式。
代码
//higher_order_lambda_expression.cpp
//compilewith:/EHsc/W4
#include<iostream>
#include<functional>
intmain()
{
usingnamespacestd;
//Thefollowingcodedeclaresalambdaexpressionthatreturns
//anotherlambdaexpressionthataddstwonumbers.
//Thereturnedlambdaexpressioncapturesparameterxbyvalue.
autoaddtwointegers=[](intx)->function<int(int)>{
return[=](inty){returnx+y;};
};
//Thefollowingcodedeclaresalambdaexpressionthattakesanother
//lambdaexpressionasitsargument.
//Thelambdaexpressionappliestheargumentztothefunctionf
//andmultipliesby2.
autohigherorder=[](constfunction<int(int)>&f,intz){
returnf(z)*2;
};
//Callthelambdaexpressionthatisboundtohigherorder.
autoanswer=higherorder(addtwointegers(7),8);
//Printtheresult,whichis(7+8)*2.
cout<<answer<<endl;
}
输出
30在函数中使用Lambda表达式
示例
你可以在函数的主体中使用lambda表达式。lambda表达式可以访问该封闭函数可访问的任何函数或数据成员。你可以显式或隐式捕获this指针,以提供对封闭类的函数和数据成员的访问路径。
你可以在函数中显式使用this指针,如下所示:
voidApplyScale(constvector<int>&v)const
{
for_each(v.begin(),v.end(),
[this](intn){cout<<n*_scale<<endl;});
}
你也可以隐式捕获this指针:
voidApplyScale(constvector<int>&v)const
{
for_each(v.begin(),v.end(),
[=](intn){cout<<n*_scale<<endl;});
}
以下示例显示封装小数位数值的Scale类。
//function_lambda_expression.cpp
//compilewith:/EHsc/W4
#include<algorithm>
#include<iostream>
#include<vector>
usingnamespacestd;
classScale
{
public:
//Theconstructor.
explicitScale(intscale):_scale(scale){}
//Printstheproductofeachelementinavectorobject
//andthescalevaluetotheconsole.
voidApplyScale(constvector<int>&v)const
{
for_each(v.begin(),v.end(),[=](intn){cout<<n*_scale<<endl;});
}
private:
int_scale;
};
intmain()
{
vector<int>values;
values.push_back(1);
values.push_back(2);
values.push_back(3);
values.push_back(4);
//CreateaScaleobjectthatscaleselementsby3andapply
//ittothevectorobject.Doesnotmodifythevector.
Scales(3);
s.ApplyScale(values);
}
输出
3 6 9 12
备注
ApplyScale函数使用lambda表达式打印小数位数值与vector对象中的每个元素的乘积。lambda表达式隐式捕获this指针,以便访问_scale成员。
配合使用Lambda表达式和模板
示例
由于lambda表达式已类型化,因此你可以将其与C++模板一起使用。下面的示例显示negate_all和print_all函数。negate_all函数将一元operator-应用于vector对象中的每个元素。print_all函数将vector对象中的每个元素打印到控制台。
代码
//template_lambda_expression.cpp
//compilewith:/EHsc
#include<vector>
#include<algorithm>
#include<iostream>
usingnamespacestd;
//Negateseachelementinthevectorobject.Assumessigneddatatype.
template<typenameT>
voidnegate_all(vector<T>&v)
{
for_each(v.begin(),v.end(),[](T&n){n=-n;});
}
//Printstotheconsoleeachelementinthevectorobject.
template<typenameT>
voidprint_all(constvector<T>&v)
{
for_each(v.begin(),v.end(),[](constT&n){cout<<n<<endl;});
}
intmain()
{
//Createavectorofsignedintegerswithafewelements.
vector<int>v;
v.push_back(34);
v.push_back(-43);
v.push_back(56);
print_all(v);
negate_all(v);
cout<<"Afternegate_all():"<<endl;
print_all(v);
}
输出
34 -43 56 Afternegate_all(): -34 43 -56
处理异常
示例
lambda表达式的主体遵循结构化异常处理(SEH)和C++异常处理的原则。你可以在lambda表达式主体中处理引发的异常或将异常处理推迟至封闭范围。以下示例使用for_each函数和lambda表达式将一个vector对象的值填充到另一个中。它使用try/catch块处理对第一个矢量的无效访问。
代码
//eh_lambda_expression.cpp
//compilewith:/EHsc/W4
#include<vector>
#include<algorithm>
#include<iostream>
usingnamespacestd;
intmain()
{
//Createavectorthatcontains3elements.
vector<int>elements(3);
//Createanothervectorthatcontainsindexvalues.
vector<int>indices(3);
indices[0]=0;
indices[1]=-1;//Thisisnotavalidsubscript.Itwilltriggeranexception.
indices[2]=2;
//Usethevaluesfromthevectorofindexvaluesto
//filltheelementsvector.Thisexampleusesa
//try/catchblocktohandleinvalidaccesstothe
//elementsvector.
try
{
for_each(indices.begin(),indices.end(),[&](intindex){
elements.at(index)=index;
});
}
catch(constout_of_range&e)
{
cerr<<"Caught'"<<e.what()<<"'."<<endl;
};
}
输出
Caught'invalidvector<T>subscript'.
备注
有关异常处理的详细信息,请参阅VisualC++中的异常处理。
配合使用Lambda表达式和托管类型(C++/CLI)
示例
lambda表达式的捕获子句不能包含具有托管类型的变量。但是,你可以将具有托管类型的实际参数传递到lambda表达式的形式参数列表。以下示例包含一个lambda表达式,它通过值捕获局部非托管变量ch,并采用System.String对象作为其参数。
代码
//managed_lambda_expression.cpp
//compilewith:/clr
usingnamespaceSystem;
intmain()
{
charch='!';//alocalunmanagedvariable
//Thefollowinglambdaexpressioncaptureslocalvariables
//byvalueandtakesamanagedStringobjectasitsparameter.
[=](String^s){
Console::WriteLine(s+Convert::ToChar(ch));
}("Hello");
}
输出
Hello!