c++ mutable 获取lambda返回值类型怎样指定返回类型

C++11 新特性:Lambda 表达式 | DevBean Tech World
&/&&/&C++11 新特性:Lambda 表达式
C++11 新特性:Lambda 表达式
参考文章:
或许,Lambda 表达式算得上是 C++ 11 新增特性中最激动人心的一个。这个全新的特性听起来很深奥,但却是很多其他语言早已提供(比如 C#)或者即将提供(比如 Java)的。简而言之,Lambda 表达式就是用于创建匿名函数的。GCC 4.5.x 和 Microsoft Visual Studio 早已提供了对 lambda 表达式的支持。在 GCC 4.7 中,默认是不开启 C++ 11 特性的,需要添加
-std=c++11 编译参数。而 VS2010 则默认开启。
为什么说 lambda 表达式如此激动人心呢?举一个例子。标准 C++ 库中有一个常用算法的库,其中提供了很多算法函数,比如 sort() 和 find()。这些函数通常需要提供一个“谓词函数 predicate function”。所谓谓词函数,就是进行一个操作用的临时函数。比如 find() 需要一个谓词,用于查找元素满足的条件;能够满足谓词函数的元素才会被查找出来。这样的谓词函数,使用临时的匿名函数,既可以减少函数数量,又会让代码变得清晰易读。
下面来看一个例子:
#include &algorithm&
#include &cmath&
void abssort(float *x, unsigned N)
std::sort(x,
[](float a, float b) { return std::abs(a) & std::abs(b); });
#include &algorithm&#include &cmath&&void abssort(float *x, unsigned N){&&std::sort(x,&&&&&&&&&&&&x + N,&&&&&&&&&&&&[](float a, float b) { return std::abs(a) & std::abs(b); });}
从上面的例子来看,尽管支持 lambda 表达式,但 C++ 的语法看起来却很“神奇”。lambda 表达式使用一对方括号作为开始的标识,类似于声明一个函数,只不过这个函数没有名字,也就是一个匿名函数。这个匿名函数接受两个参数,a和b;其返回值是一个 bool 类型的值,注意,返回值是自动推断的,不需要显式声明,不过这是有条件的!条件就是,lambda 表达式的语句只有一个 return。函数的作用是比较 a、b 的绝对值的大小。然后,在此例中,这个 lambda 表达式作为一个闭包被传递给 std::sort() 函数。
下面,我们来详细解释下这个神奇的语法到底代表着什么。
我们从另外一个例子开始:
std::cout && [](float f) { return std::abs(f); } (-3.5);
std::cout && [](float f) { return std::abs(f); } (-3.5);
输出值是什么?3.5!注意,这是一个函数对象(由 lambda 表达式生成),其实参是 -3.5,返回值是参数的绝对值。lambda 表达式的返回值类型是语言自动推断的,因为std::abs()的返回值就是 float。注意,前面我们也提到了,只有当 lambda 表达式中的语句“足够简单”,才能自动推断返回值类型。
C++ 11 的这种语法,其实就是匿名函数声明之后马上调用(否则的话,如果这个匿名函数既不调用,又不作为闭包传递给其它函数,那么这个匿名函数就没有什么用处)。如果你觉得奇怪,那么来看看 JavaScript 的这种写法:
JavaScript
function() {} ();
function(a) {} (-3.5);
function() {} ();&function(a) {} (-3.5);
C++ 11 的写法完全类似 JavaScript 的语法。
如果我不想让 lambda 表达式自动推断类型,或者是 lambda 表达式的内容很复杂,不能自动推断怎么办?比如,std::abs(float)的返回值是 float,我想把它强制转型为 int。那么,此时,我们就必须显式指定 lambda 表达式返回值的类型:
std::cout && [](float f) -& int { return std::abs(f); } (-3.5);
std::cout && [](float f) -& int { return std::abs(f); } (-3.5);
这个语句与前面的不同之处在于,lambda 表达式的返回时不是 float 而是 int。也就是说,上面语句的输出值是 3。返回值类型的概念同普通的函数返回值类型是完全一样的。
当我们想引用一个 lambda 表达式时,我们可以使用auto关键字,例如:
auto lambda = [] () -& int { return val * 100; };
auto lambda = [] () -& int { return val * 100; };
auto关键字实际会将 lambda 表达式转换成一种类似于std::function的内部类型(但并不是std::function类型,虽然与std::function“兼容”)。所以,我们也可以这么写:
std::function&int()& lambda = [] () -& int { return val * 100; };
std::function&int()& lambda = [] () -& int { return val * 100; };
如果你对std::function&int()&这种写法感到很神奇,可以查看 C++ 11 的有关std::function的用法。简单来说,std::function&int()&就是一个可调用对象模板类,代表一个可调用对象,接受 0 个参数,返回值是int。所以,当我们需要一个接受一个double作为参数,返回int的对象时,就可以写作:std::function&int(double)&。
引入 lambda 表达式的前导符是一对方括号,称为 lambda 引入符(lambda-introducer)。lambda 引入符是有其自己的作用的,不仅仅是表明一个 lambda 表达式的开始那么简单。lambda 表达式可以使用与其相同范围 scope 内的变量。这个引入符的作用就是表明,其后的 lambda 表达式以何种方式使用(正式的术语是“捕获”)这些变量(这些变量能够在 lambda 表达式中被捕获,其实就是构成了一个闭包)。目前为止,我们看到的仅仅是一个空的方括号,其实,这个引入符是相当灵活的。例如:
float f0 = 1.0;
std::cout && [=](float f) { return f0 + std::abs(f); } (-3.5);
float f0 = 1.0;std::cout && [=](float f) { return f0 + std::abs(f); } (-3.5);
其输出值是 4.5。[=] 意味着,lambda 表达式以传值的形式捕获同范围内的变量。另外一个例子:
float f0 = 1.0;
std::cout && [&](float f) { return f0 += std::abs(f); } (-3.5);
std::cout && '\n' && f0 && '\n';
float f0 = 1.0;std::cout && [&](float f) { return f0 += std::abs(f); } (-3.5);std::cout && '\n' && f0 && '\n';
输出值是 4.5 和 4.5。[&] 表明,lambda 表达式以传引用的方式捕获外部变量。那么,下一个例子:
float f0 = 1.0;
std::cout && [=](float f) mutable { return f0 += std::abs(f); } (-3.5);
std::cout && '\n' && f0 && '\n';
float f0 = 1.0;std::cout && [=](float f) mutable { return f0 += std::abs(f); } (-3.5);std::cout && '\n' && f0 && '\n';
这个例子很有趣。首先,[=]意味着,lambda 表达式以传值的形式捕获外部变量。C++ 11 标准说,如果以传值的形式捕获外部变量,那么,lambda 体不允许修改外部变量,对 f0 的任何修改都会引发编译错误。但是,注意,我们在 lambda 表达式前声明了mutable关键字,这就允许了 lambda 表达式体修改 f0 的值。因此,我们的例子本应报错,但是由于有 mutable 关键字,则不会报错。那么,你会觉得输出值是什么呢?答案是,4.5 和 1.0。为什么 f0 还是 1.0?因为我们是传值的,虽然在 lambda 表达式中对 f0 有了修改,但由于是传值的,外部的 f0 依然不会被修改。
上面的例子是,所有的变量要么传值,要么传引用。那么,是不是有混合机制呢?当然也有!比如下面的例子:
float f0 = 1.0f;
float f1 = 10.0f;
std::cout && [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
std::cout && '\n' && f0 && '\n';
float f0 = 1.0f;float f1 = 10.0f;std::cout && [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);std::cout && '\n' && f0 && '\n';
这个例子的输出是 14.5 和 14.5。在这个例子中,f0 通过引用被捕获,而其它变量,比如 f1 则是通过值被捕获。
下面我们来总结下所有出现的 lambda 引入符:
// 不捕获任何外部变量
// 以值的形式捕获所有外部变量
// 以引用形式捕获所有外部变量
[x, &y] // x 以传值形式捕获,y 以引用形式捕获
[=, &z]// z 以引用形式捕获,其余变量以传值形式捕获
// x 以值的形式捕获,其余变量以引用形式捕获
另外有一点需要注意。对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
[this]() { this-&someFunc(); }();
[this]() { this-&someFunc(); }();
至此,我们已经大致了解了 C++ 11 提供的 lambda 表达式的概念。建议通过结合 lambda 表达式与std::sort()或std::for_each()这样的标准函数来尝试使用一下吧!新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
丰衣足食, 积分 522, 距离下一级还需 478 积分
论坛徽章:0
尤其是和STL算法结合使用的时候,终于可以丢掉那些binder、std::unary_function, std::binary_function了,真是大快人心啊。
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
富足长乐, 积分 7418, 距离下一级还需 582 积分
论坛徽章:0
本帖最后由 wwwsq 于
10:06 编辑
binder、std::unary_function, std::binary_function这些东西本来就没多少价值。不但太晦涩了,而且因为缺乏语法层面的支持,显得繁琐而容易出错。
lambda比那种晦涩的方式稍微好一些。但是也好的有限,主要是语法有点诡异,javascript风格的东西放在C++里面显得很破碎。
如果想玩lambda,可以直接去玩node.js,或者试试C#、Java。
大富大贵, 积分 10950, 距离下一级还需 9050 积分
论坛徽章:2
lisp 语言的玩意儿
纯 C++ 还是不用为好
论坛徽章:16
。。。。 有兴趣加入BOOST研究小组么 ?
家境小康, 积分 1734, 距离下一级还需 266 积分
论坛徽章:1
比以前有进步,但还是很难用。
稍有积蓄, 积分 369, 距离下一级还需 131 积分
论坛徽章:0
不能推导参数的类型, 这样的lambda不要也罢
丰衣足食, 积分 522, 距离下一级还需 478 积分
论坛徽章:0
本帖最后由 hniu 于
21:37 编辑
hgrany 发表于
不能推导参数的类型, 这样的lambda不要也罢
C++11的维基百科上不是说可以自动推导的吗?
auto myLambdaFunc = [this]() { this-&SomePrivateMemberFunction(); };
auto myOnheapLambdaFunc = new auto([=] { /*...*/ });
复制代码
丰衣足食, 积分 522, 距离下一级还需 478 积分
论坛徽章:0
evaspring 发表于
。。。。 有兴趣加入BOOST研究小组么 ?
有啊,怎么加入?
富足长乐, 积分 7242, 距离下一级还需 758 积分
论坛徽章:1
新版的lambda觉得很好用啊,至少比以前的函数对象要好很多了
富足长乐, 积分 7418, 距离下一级还需 582 积分
论坛徽章:0
本帖最后由 wwwsq 于
09:41 编辑
egmkang 发表于
新版的lambda觉得很好用啊,至少比以前的函数对象要好很多了
functor是彻底的垃圾,lambda是半垃圾。是好了一倍,但也还是不够好。博客访问: 178503
博文数量: 42
博客积分: 1126
博客等级: 少尉
技术积分: 960
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++
&C++ 11中引入的一个非常重要的概念就是右值引用。理解右值引用是学习“移动语义”(move semantics)的基础。而要理解右值引用,就必须先区分左值与右值。&&&&&& 对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值。左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。下面给出一些例子来进行说明。&int&a&=&10;&int&b&=&20;&int&*pFlag&=&&a;&vector<int>&vctT&vctTemp.push_back(1);&string&str1&=&"hello&";&string&str2&=&"world";&const&int&&m&=&1;&&&&&& 请问,a,b, a+b, a++, ++a, pFlag, *pFlag, vctTemp[0], 100, string("hello"), str1, str1+str2, m分别是左值还是右值?&&&&&&&&&& a和b都是持久对象(可以对其取地址),是左值;&&& &&&&&& a+b是临时对象(不可以对其取地址),是右值;&&&&&&&&&& a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;&&&&&&&&&& ++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;&&&&&&&&&& pFlag和*pFlag都是持久对象(可以对其取地址),是左值;&&&&&&&&&& vctTemp[0]调用了重载的[]操作符,而[]操作符返回的是一个int &,为持久对象(可以对其取地址),是左值;&&&&&&&&&& 100和string("hello")是临时对象(不可以对其取地址),是右值;&&&&&&&&&& str1是持久对象(可以对其取地址),是左值;&&&&&&&&&& str1+str2是调用了+操作符,而+操作符返回的是一个string(不可以对其取地址),故其为右值;&&&&&&&&&& m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。&&&&& 区分清楚了左值与右值,我们再来看看左值引用。左值引用根据其修饰符的不同,可以分为非常量左值引用和常量左值引用。&&&&& 非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值。如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象。&&&&& 常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。&&&&& 可以看出,使用左值引用时,我们无法区分出绑定的是否是非常量右值的情况。那么,为什么要对非常量右值进行区分呢,区分出来了又有什么好处呢?这就牵涉到C++中一个著名的性能问题——拷贝临时对象。考虑下面的代码:vector<int>&GetAllScores(){&vector<int>&vctT&vctTemp.push_back(90);&vctTemp.push_back(95);&return&vctT}&&&&&& 当使用vector vctScore = GetAllScores()进行初始化时,实际上调用了三次构造函数。尽管有些编译器可以采用RVO(Return Value Optimization)来进行优化,但优化工作只在某些特定条件下才能进行。可以看到,上面很普通的一个函数调用,由于存在临时对象的拷贝,导致了额外的两次拷贝构造函数和析构函数的开销。当然,我们也可以修改函数的形式为void GetAllScores(vector &vctScore),但这并不一定就是我们需要的形式。另外,考虑下面字符串的连接操作:&string&s1("hello");&string&s&=&s1&+&"a"&+&"b"&+&"c"&+&"d"&+&"e";&&&&&& 在对s进行初始化时,会产生大量的临时对象,并涉及到大量字符串的拷贝操作,这显然会影响程序的效率和性能。怎么解决这个问题呢?如果我们能确定某个值是一个非常量右值(或者是一个以后不会再使用的左值),则我们在进行临时对象的拷贝时,可以不用拷贝实际的数据,而只是“窃取”指向实际数据的指针(类似于STL中的auto_ptr,会转移所有权)。C++ 11中引入的右值引用正好可用于标识一个非常量右值。C++ 11中用&表示左值引用,用&&表示右值引用,如:&int&&&a&=&10;&&&&&&& 右值引用根据其修饰符的不同,也可以分为非常量右值引用和常量右值引用。&&&&&& 非常量右值引用只能绑定到非常量右值,不能绑定到非常量左值、常量左值和常量右值(VS2010 beta版中可以绑定到非常量左值和常量左值,但正式版中为了安全起见,已不允许)。如果允许绑定到非常量左值,则可能会错误地窃取一个持久对象的数据,而这是非常危险的;如果允许绑定到常量左值和常量右值,则非常量右值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。&&&&&& 常量右值引用可以绑定到非常量右值和常量右值,不能绑定到非常量左值和常量左值(理由同上)。&&&&&& 有了右值引用的概念,我们就可以用它来实现下面的CMyString类。class&CMyString{public:&&&&//&构造函数&CMyString(const&char&*pszSrc&=&NULL)&{&&cout&<<&"CMyString(const&char&*pszSrc&=&NULL)"&<<&&&if&(pszSrc&==&NULL)&&{&&&m_pData&=&new&char[1];&&&*m_pData&=&'\0';&&}&&else&&{&&&m_pData&=&new&char[strlen(pszSrc)+1];&&&strcpy(m_pData,&pszSrc);&&}&}&&&&//&拷贝构造函数&CMyString(const&CMyString&&s)&{&&cout&<<&"CMyString(const&CMyString&&s)"&<<&&&m_pData&=&new&char[strlen(s.m_pData)+1];&&strcpy(m_pData,&s.m_pData);&}&&&&//&move构造函数&CMyString(CMyString&&&s)&{&&cout&<<&"CMyString(CMyString&&&s)"&<<&&&m_pData&=&s.m_pD&&s.m_pData&=&NULL;&}&&&&//&析构函数&~CMyString()&{&&cout&<<&"~CMyString()"&<<&&&delete&[]&m_pD&&m_pData&=&NULL;&}&&&&//&拷贝赋值函数&CMyString&&operator&=(const&CMyString&&s)&{&&cout&<<&"CMyString&&operator&=(const&CMyString&&s)"&<<&&&if&(this&!=&&s)&&{&&&delete&[]&m_pD&&&m_pData&=&new&char[strlen(s.m_pData)+1];&&&strcpy(m_pData,&s.m_pData);&&}&&return&*this;&}&&&&//&move赋值函数&CMyString&&operator&=(CMyString&&&s)&{&&cout&<<&"CMyString&&operator&=(CMyString&&&s)"&<<&&&if&(this&!=&&s)&&{&&&delete&[]&m_pD&&&m_pData&=&s.m_pD&&&s.m_pData&=&NULL;&&}&&return&*this;&}private:&char&*m_pD};&&&&&&& 可以看到,上面我们添加了move版本的构造函数和赋值函数。那么,添加了move版本后,对类的自动生成规则有什么影响呢?唯一的影响就是,如果提供了move版本的构造函数,则不会生成默认的构造函数。另外,编译器永远不会自动生成move版本的构造函数和赋值函数,它们需要你手动显式地添加。&&&&&&& 当添加了move版本的构造函数和赋值函数的重载形式后,某一个函数调用应当使用哪一个重载版本呢?下面是按照判决的优先级列出的3条规则:&&&&&&&&&&&& 1、常量值只能绑定到常量引用上,不能绑定到非常量引用上。&&&&&&&&&&&& 2、左值优先绑定到左值引用上,右值优先绑定到右值引用上。&&&&&&&&&&&& 3、非常量值优先绑定到非常量引用上。&&&&&&& 当给构造函数或赋值函数传入一个非常量右值时,依据上面给出的判决规则,可以得出会调用move版本的构造函数或赋值函数。而在move版本的构造函数或赋值函数内部,都是直接“移动”了其内部数据的指针(因为它是非常量右值,是一个临时对象,移动了其内部数据的指针不会导致任何问题,它马上就要被销毁了,我们只是重复利用了其内存),这样就省去了拷贝数据的大量开销。&&&&&&& 一个需要注意的地方是,拷贝构造函数可以通过直接调用*this = s来实现,但move构造函数却不能。这是因为在move构造函数中,s虽然是一个非常量右值引用,但其本身却是一个左值(是持久对象,可以对其取地址),因此调用*this = s时,会使用拷贝赋值函数而不是move赋值函数,而这已与move构造函数的语义不相符。要使语义正确,我们需要将左值绑定到非常量右值引用上,C++ 11提供了move函数来实现这种转换,因此我们可以修改为*this = move(s),这样move构造函数就会调用move赋值函数。C++的Lambda表达式在WIN RT的异步编程中,占有非常重要的作用。但C++的Lambda表达式又不同于其他语言,比如C#,javascript。本篇旨在讨论C++ Lambda表达式的基本语法和概念,希望大家多多指正。&&首先,我们看一下Lambda表达式的基本构成&1. 是捕获值列表,2.是传入参数列表,3.可修改标示符,4.错误抛出标示符,5.函数返回值,6.是函数体。&在.NET 中,我们认为比较标准的Lambda表达式应该是这个样子//&declaring_lambda_expressions1.cpp#include&int&main(){&&&//&Assign&the&lambda&expression&that&adds&two&numbers&to&an&auto&variable.&&&auto&f1&=&[]&(int&x,&int&y)&{&return&x&+&y;&};&&&&//&Assign&the&same&lambda&expression&to&a&function&object.&&&function<int&(int,&int)>&f2&=&[]&(int&x,&int&y)&{&return&x&+&y;&};& &f1(3,4);&}f1是一个auto的值,也是function这个模板类型,我们可以理解成为一个函数指针。然后我们用f1(3,4)去调用他。&如果我们想在函数声明的时候就直接执行他,我们可以在Lambda表达式的最后加传入参数,像这样。int&main(){&&&using&namespace&&&&int&n&=&[]&(int&x,&int&y)&{&return&x&+&y;&}(5,&4);&&&//assign&the&return&type&&&int&n&=&[]&(int&x,&int&y)&->&int{&return&x&+&y;}(5,&4);&&&cout&<<&n&<<&}第二个表达式中声明的返回值必须跟随->符号,并且两个必须同时出现。如果返回值唯一的话,我们可以省略->+返回值类型。Lambda表达式允许返回值不唯一的情况,但必须指定返回值类型。在以上的例子当中,只是常规的Lambda表达式用法,下面我们要说一说捕获值列表。&捕获值列表,是允许我们在Lambda表达式的函数体中直接使用这些值,捕获值列表能捕获的值是所有在此作用域可以访问的值,包括这个作用域里面的临时变量,类的可访问成员,全局变量。捕获值的方式分两种,一种是按值捕获,一种是按引用捕获。顾名思义,按值捕获是不改变原有变量的值,按引用捕获是可以在Lambda表达式中改变原有变量的值。[&] 所有的值都是按引用捕获[=] 所有的值都是按值捕获如果你不想某些值被按引用或者按值捕获,但其他的值却想那样做的话[ &, n ] 除了n 所有的值按引用捕获[ = , &n ]除了n所有的值按值捕获当然,我们也可以指定某几个值的捕获属性[ m, n ]m,n按引用捕获[ &m, &n ]m,n按值捕获&&&&&&&&int&m&=&0,&n&=&0;&&&&&&&&&&[=]&(int&a)&mutable&{&m&=&++n&+&a;&}(4);&&&&&&[&]&(int&a)&{&m&=&++n&+&a;&}(4);&&&&&&[=,&m]&(int&a)&mutable&{&m&=&++n&+&a;&}(4);&&&&&&[&,m]&(int&a)&mutable&{&m&=&++n&+&a;&}(4);&&&&&&[m,n]&(int&a)&mutable&{&m&=&++n&+&a;&}(4);&&&&&&[&m,&n]&(int&a)&{&m&=&++n&+&a;&}(4);&&&&&&[=]&(int&a)&mutable&{&m&=&++n&+&a;&}(4);&大家一定好奇为什么这里有很多mutable。在按值引用的情况下,Lambda函数体内部是不能直接修改引用值的。如下面注释代码,是会报错的。这种情况下,我们要在Lambda表达式前加mutable,但是结果m,n 依然没有被修改,维持按值引用的特性。int&main(){&&&&&&int&m&=&0,&n&=&0;&&&&&&//&不加mutable会报错&&&&&&//[=]&(int&a){&m&=&++n&+&a;&}(4);&&&&&&//[m,n]&(int&a){&m&=&++n&+&a;&}(4);&&&&&&[=]&(int&a)&mutable&{&m&=&++n&+&a;&}(4);&&&&&//&&&&&//&[=]&(int&m,&int&n,&int&a){m=++n+a;&}(m,&n,&4);&&&&&//&下面这个函数m,n的值依然会被修改,因为m,n是按引用传入的&&&&&//&[=]&(int&&m,&int&&n,&int&a){m=++n+a;&}(m,&n,&4);&&&&&&cout&<<&m&<<&endl&<<&n&<<&}&&在这个例子中捕获值列表[this]中的this是用来指向这个类的,但[this]只有在类的内部,或者是this指针存在的情况下才能使用。class&Scale{public:&&&//&The&constructor.&&&explicit&Scale(int&scale)&&&&&&:&_scale(scale)&&&{&&&}&&&//&Prints&the&product&of&each&element&in&a&vector&object&&&&//&and&the&scale&value&to&the&console.&&&void&ApplyScale(const&vector<int>&&v)&const&&&{&&&&&&for_each(v.begin(),&v.end(),&&&&&&&&&&[this](int&n)&{&cout&<<&n&*&_scale&<<&&});&&&}private:&&&int&_};&关于异常:我们可以通过try-catch去捕获异常,而在Lambda表达式中声明throw(),是指示编译器这个函数不会抛异常,会引起编译的警告。&然后,Lambda可以支持返回函数指针,或者说是嵌套一个Lambda表达式,比如:int&main(){&&&using&namespace&&&&//&The&following&lambda&expression&contains&a&nested&lambda&&&//&expression.&&&int&m&=&[](int&x)&&&&&&&{&return&[](int&y)&{&return&y&*&2;&}(x)&+&3;&}(5);&&&//&Print&the&result.&&&cout&<<&m&<<&}我们可以把&return&[](int&y)&{&return&y&*&2;&}(x) 抽象成 f(x) 所以原函数就是return f(5)+3 就是2*5+3=13&加入函数指针之后,我们来看一看一个Lambda表达式可以写的多复杂,这是来自于MSDN的官方的例子。//&higher_order_lambda_expression.cpp//&compile&with:&/EHsc#include&#include&int&main(){&&&using&namespace&&&&//&The&following&code&declares&a&lambda&expression&that&returns&&&&//&another&lambda&expression&that&adds&two&numbers.&&&&//&The&returned&lambda&expression&captures&parameter&x&by&value.&&&auto&g&=&[](int&x)&->&function<int&(int)>&&&&&&&{&return&[=](int&y)&{&return&x&+&y;&};&};&&&//&The&following&code&declares&a&lambda&expression&that&takes&another&&&//&lambda&expression&as&its&argument.&&&//&The&lambda&expression&applies&the&argument&z&to&the&function&f&&&//&and&adds&1.&&&auto&h&=&[](const&function<int&(int)>&&f,&int&z)&&&&&&&{&return&f(z)&+&1;&};&&&//&Call&the&lambda&expression&that&is&bound&to&h.&&&&auto&a&=&h(g(7),&8);&&&//&Print&the&result.&&&cout&<<&a&<<&}&&&结果很简单就是7+8+1=16 我通过代码帮大家展开一下:& &auto&g&=&[](int&x)&->&function<int&(int)>&&&&&&&{&return&[=](int&y)&{&return&x&+&y;&};&};&&&auto&h&=&[](const&function<int&(int)>&&f,&int&z)&&&&&&&{&return&f(z)&+&1;&};&&&auto&a&=&h(g(7),&8);&&&//&解:&&&//&我们先看看g(7)&等于什么&&&//&我们把g的返回值&return&[=](int&y)&{&return&x&+&y;&};&抽象成一个函数t(y)&&&//&那么g(x)返回的就t(y)&&&//&也就是g(7)=t(y)&这里g的参数和t的参数无关&&&//&那么&h(g(7),&8)=h(t(y),&8))&&&//&代入h的表达式,我们发现t(y)就是f(z)&&&//&代入的结果就是&return&t(8)+1,再把g(7)代入就是7+8+1=16&&&& &cout&<<&a&<<&&&最后,有人会很好奇foe_each为什么可以传入Lambda表达式首先,我们看看for_each的展开template<class&InputIterator,&class&Function>&&Function&for_each(InputIterator&first,&InputIterator&last,&Function&f)&&{&&&&for&(&;&first!=&++first&)&f(*first);&&&&return&f;&&}//From:&/reference/algorithm/for_each/&&当然这不是实际的代码,但是我们可以看到,调用的只是f()再传入迭代器的值,所以,我们在写for_each的Lambda表达式的时候,传入参数一定是和迭代器的类型是匹配的。在没有Lambda表达式的时候,只要是能写成&f(*first)这样的东西传进来的都行,所以就会出现结构体重载()操作符,这样的奇葩void&myfunction&(int&i)&{&&cout&<<&"&"&<<&i;}struct&myclass&{&&void&operator()&(int&i)&{cout&<<&"&"&<<&i;}}&int&main&()&{&&vector<int>&&&myvector.push_back(10);&&myvector.push_back(20);&&myvector.push_back(30);&&cout&<<&"myvector&contains:";&&for_each&(myvector.begin(),&myvector.end(),&myfunction);&&//&or:&&cout&<<&"\nmyvector&contains:";&&for_each&(myvector.begin(),&myvector.end(),&myobject);&&cout&<<&&&return&0;}&在C++中Lambda表达式被设计的相对复杂,但我相信,这也是C++这门语言的魅力所在,功能很强大,但是很难学。
阅读(5116) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。

我要回帖

更多关于 mybatis 指定返回类型 的文章

 

随机推荐