实例讲解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!