VC10 和 C++0x lambda 表达式.docx
- 文档编号:7264196
- 上传时间:2023-01-22
- 格式:DOCX
- 页数:14
- 大小:21.12KB
VC10 和 C++0x lambda 表达式.docx
《VC10 和 C++0x lambda 表达式.docx》由会员分享,可在线阅读,更多相关《VC10 和 C++0x lambda 表达式.docx(14页珍藏版)》请在冰豆网上搜索。
VC10和C++0xlambda表达式
VC10和C++0x
(1)-lambda表达式
CPLUSPLUS2011-01-1414:
15:
00阅读84评论0 字号:
大中小 订阅
【本文大部分内容译自VisualC++TeamBlog】
尽管C++社区对C++0x很是追捧,但是各厂商对于新标准的支持并不热乎。
盼星星盼月亮,微软作为Windows平台上最强势的C++编译器厂商也终于在VisualStudio2010中开始支持C++0x的特性。
Lambda表达式,auto和静态断言(static_assert)
VisualStudio2010中的VisualC++编译器,即VC10,包含了4个C++0x的语言特性-lambda表达式,auto,static_assert和rvaluereference(右值引用).
相关链接:
∙C++0xlanguagefeaturestatus:
http:
//open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html
∙C++0xlibraryfeaturestatus:
http:
//open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html
∙C++0xWorkingDraft:
http:
//open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf
lambdas
使用过函数式编程语言(如lisp,F#)或一些动态语言(如Python,Javascript)的大侠对于lambda表达式一定不会陌生。
在C++0x中,引入了lambda表达式来定义无名仿函数。
下面是一个lambda表达式的简单例子:
//File:
meow.cpp
#include
#include
#include
#include
usingnamespacestd;
intmain(){
vector
for(inti=0;i<10;++i){
v.push_back(i);
}
for_each(v.begin(),v.end(),[](intn){cout< cout< return0; } C: \Temp>cl/EHsc/nologo/W4meow.cpp>NUL&&meow 0123456789 for_each一行中,中括号[]称为lambdaintroducer,它告诉编译器接下来的是一个lambda表达式;接下来(intn)是lambda表达式的参数声明;最后大括号里边就是“函数体”了。 注意这里因为lambda表达式生成的是functor,所以“函数体”实际上是指这个functor的operator()的调用部分。 你也许会问: 那么返回值呢? 缺省情况下lambda表达式生成的functor调用返回类型为void。 所以,可以理解为上边的代码会被编译器翻译成如下: #include #include #include #include usingnamespacestd; structLambdaFunctor{ voidoperator()(intn)const{ cout< } }; intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } for_each(v.begin(),v.end(),LambdaFunctor()); cout< return0; } 为了方便,以下会用"lambda返回void"的简短表述来代替冗长啰嗦的表述: lambda表达式生成一个functor类型,这个functor类型的函数调用操作符(operator())返回的类型是void. 请大家一定记住: lambda表达式生成了类型,并构造该类型的实例。 下面的例子中lambda表达式的“函数体”包含多条语句: #include #include #include #include usingnamespacestd; intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } for_each(v.begin(),v.end(),[](intn){ cout< if(n%2==0){ cout<<"even"; }else{ cout<<"odd"; } }); cout< return0; } 上文提到了lambda表达式缺省情况下返回void.那么如果需要返回其他类型呢? 答案是: lambda表达式的“函数体”中如果有一个return的表达式,例如{returnexpression;},那么编译器将自动推演expression的类型作为返回类型。 #include #include #include #include #include #include usingnamespacestd; intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } deque transform(v.begin(),v.end(),front_inserter(d),[](intn){returnn*n*n;}); for_each(d.begin(),d.end(),[](intn){cout< cout< } 上例中返回值n*n*n很简单,类型推演是显而易见的。 但是如果lambda表达式中有非常复杂的表达式时,编译器可以无法推演出其类型,或者是推演出现二义性,这时候你可以 显式地指明返回值类型。 如下所示: transform(v.begin(),v.end(),front_inserter(d),[](intn)->double{ if(n%2==0){ returnn*n*n; }else{ returnn/2.0; } }); 黑体部分中有的“->double”显式地指明了lambda表达式的返回类型是double. 以上例子中的lambda都是无状态的(stateless),不包含任何数据成员。 很多时候我们需要lambda包含数据成员以保存状态,这一点可以通过“捕获”(capturing)局部变量来实现。 lambda表达式的导入符(lambda-introducer)是空的,也就是“[]”,表明该lambda是一个无状态的。 但是在lambda导入符中可以指定一个“捕获列表”(capture-list)。 intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intx=0; inty=0; cout<<"Input: "; cin>>x>>y; v.erase(remove_if(v.begin(),v.end(),[x,y](intn){returnx for_each(v.begin(),v.end(),[](intn){cout< cout< } 上边的代码中的lambda使用了局部变量x和y,将值介于x和y之间的元素从集合中删除。 程序运行示例如下- Input: 47 01234789 上边的代码可以理解为: classLambdaFunctor{ public: LambdaFunctor(inta,intb): m_a(a),m_b(b){} booloperator()(intn)const{returnm_a private: intm_a; intm_b; }; intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intx=0; inty=0; cout<<"Input: "; cin>>x>>y; v.erase(remove_if(v.begin(),v.end(),LambdaFunctor(x,y)),v.end()); copy(v.begin(),v.end(),ostream_iterator cout< } 上面代码中很重要的一点信息是: lambda中捕获的局部变量是以“传值”的方式传给匿名函数对象的。 在匿名函数对象中,保存有“捕获列表”中局部变量的拷贝。 这一点使得匿名函数对象的生命周期能够长于main中的x,y局部变量。 然而这样的传值方式带来几个限制: 1.lambda中的这两个拷贝并不能被改变,因为缺省情况下函数对象的operator()是const; 2.有的对象的拷贝操作开销很大或者不可能(例如如果上面代码中的x,y是数据库链接或者某个singleton) 3.即使在lambda内部修改了m_a,m_b也不能够影响外边main函数中的x和y 既然有了“传值”,你一定猜到了还会有“传引用”。 bingo! 你是对的。 在讨论“传引用”之前,我们先来看看另一个比较有用的东西。 假设你有一大堆的局部变量需要被lambda使用,那么你的“捕获列表”将会写的很长,这肯定不是件愉快的事情。 好在C++委员会的老头们也想到了,C++0x中提供了一个省心的东西: 如果捕获列表写成[=],表示lambda将捕获所有的局部变量,当然也是传值方式。 这种方式姑且被称为“缺省捕获”(capture-default)。 intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intx=0; inty=0; cout<<"Input: "; cin>>x>>y;//EVIL! v.erase(remove_if(v.begin(),v.end(),[=](intn){returnx for_each(v.begin(),v.end(),[](intn){cout< cout< } 当编译器在lambda的作用范围内看到局部变量x,y时,它会以传值的方式从main函数中将他们捕获。 下面我们来看如何突破前面提到的3点限制。 第一点,修改lambda表达式中的局部变量拷贝(e.g.m_a,m_b) 缺省情况下,lambda的operator()是const修饰的,但是你可以使用mutable关键字改变这一点。 intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intx=1; inty=1; for_each(v.begin(),v.end(),[=](int&r)mutable{ constintold=r; r*=x*y; x=y; y=old; }); for_each(v.begin(),v.end(),[](intn){cout< cout< cout< } 代码运行结果如下 00062460120210336504 1,1 这里我们解决了第一个限制,但是却产生了一个新的限制 4. lambda中对捕获变量的修改并不会影响到main函数中的局部变量,因为lambda捕获局部变量使用的是传值方式 下面该“传引用”的方式登场了,它能够有效地解决2,3,4三个限制。 传引用的语法为: lambda-introducer[&x,&y] 这里的捕获列表应该理解为: X&x,Y&y;因为我们实际上是取的x,y的引用而不是地址。 intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intx=1; inty=1; for_each(v.begin(),v.end(),[&x,&y](int&r){ constintold=r; r*=x*y; x=y; y=old; }); for_each(v.begin(),v.end(),[](intn){cout< cout< cout< } 运行结果如下- 00062460120210336504 8,9 上面代码会被编译器“翻译”成: #pragmawarning(push) #pragmawarning(disable: 4512)//assignmentoperatorcouldnotbegenerated classLambdaFunctor{ public: LambdaFunctor(int&a,int&b): m_a(a),m_b(b){} voidoperator()(int&r)const{ constintold=r; r*=m_a*m_b; m_a=m_b; m_b=old; } private: int&m_a; int&m_b; }; #pragmawarning(pop) intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intx=1; inty=1; for_each(v.begin(),v.end(),LambdaFunctor(x,y)); copy(v.begin(),v.end(),ostream_iterator cout< cout< } 注意: 当你使用lambda时,VC10编译器会为lambda的定义部分自动禁用C4512警告。 当以传引用方式捕获局部变量时,lambda的函数对象在自己内部以引用方式保存main函数中的局部变量。 当然因为使用的是局部对象的引用,使用lambda表达式时一定要注意不能够超出局部变量的生命周期。 和上文提高的[=]类似,我们可以用[&]来以“传引用”的方式捕获所有的局部变量。 到目前为止,局部变量的捕获方式要么是“值语义”要么是“引用语义”,那么可以混合这两种方式吗? 可以! 例如: [a,b,c,&d,e,&f,g],其中变量d和f是按引用语义捕获,而a,b,c,e和g是按值语义捕获。 另外很有用的一点是: 你可以指定一个缺省捕获(capture-default),然后重载(override)某些局部变量的捕获方式。 下边例子中[=,&sum,&product]告诉编译器用值语义方式捕获所有的局部变量,但是有两个例外-sum和product是按引用语义来捕获。 intmain(){ vector for(inti=0;i<10;++i){ v.push_back(i); } intsum=0; intproduct=1; intx=1; inty=1; for_each(v.begin(),v.end(),[=,&sum,&product](int&r)mutable{ sum+=r; if(r! =0){ product*=r; } constintold=r; r*=x*y; x=y; y=old; }); for_each(v.begin(),v.end(),[](intn){cout< cout< cout<<"sum: "< "< cout<<"x: "< "< } 运行结果如下- 00062460120210336504 sum: 45,product: 362880 x: 1,y: 1 再来看看下边的代码-在lambda中使用类成员变量 classKitty{ public: explicitKitty(inttoys): m_toys(toys){} voidmeow(constvector for_each(v.begin(),v.end(),[m_toys](intn){ cout<<"Ifyougaveme"< }); } private: intm_toys; }; intmain(){ vector for(inti=0;i<3;++i){ v.push_back(i); } Kittyk(5); k.meow(v); } 不幸的是,编译这段代码将产生这样的错误: errorC3480: 'Kitty: : m_toys': alambdacapturevariablemustbefromanenclosingfunctionscope 为什么呢? lambda表达式能够让你不活局部变量,但是类的数据成员并不是局部变量。 解决方案呢? 别着急。 lambda为捕获类的数据成员大开方便之门,你可以捕获this指针。 classKitty{ public: explicitKitty(inttoys): m_toys(toys){} voidmeow(constvector for_each(v.begin(),v.end(),[this](intn){ cout<<"Ifyougaveme"< }); } private: intm_toys; }; intmain(){ vector for(inti=0;i<3;++i){ v.push_back(i); } Kittyk(5); k.meow(v); } 运行结果- Ifyougaveme0toys,Iwouldhave5toystotal. Ifyougaveme1toys,Iwouldhave6toystotal. Ifyougaveme2toys,Iwouldhave7toystotal. 当lambda表达式捕获“this”时,编译器看到m_toys后会在this所指向对象的范围内进行名字查找,m_toys被隐式地推演为this->m_toys。 当然你也可以让编译器省省力气。 显式地在 捕获列表中使用this->m_toys。 lambda比较智能,你也可以隐式地捕获this指针。 如下所示: classKitty{ public: explicitKitty(inttoys): m_toys(toys){} voidmeow(constvector for_each(v.begin(),v.end(),[=](intn){ cout<<"Ifyougaveme"<
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VC10 C+0x lambda 表达式 C+
![提示](https://static.bdocx.com/images/bang_tan.gif)