7.3.2 C++11中的lambda函数
lambda的历时悠久,不过具体到C++11中,lambda函数却显得与之前C++规范下的代码在风格上有较大的区别。我们可以通过一个例子先来观察一下,如代码清单7-17所示。
代码清单7-17
int main(){
int girls=3,boys=4;
auto totalChild=->int{return x+y;};
return totalChild(girls,boys);
}
编译选项:g++ -std=c++11 7-3-1.cpp
在代码清单7-17所示的例子当中,我们定义了一个lambda函数。该函数接受两个参数(int x,int y),并且返回其和。直观地看,lambda函数跟普通函数相比不需要定义函数名,取而代之的多了一对方括号([])。此外,lambda函数还采用了追踪返回类型的方式声明其返回值。其余方面看起来则跟普通函数定义一样。
而通常情况下,lambda函数的语法定义如下:
capturemutable->return-type{statement}
其中
❑[capture]:捕捉列表。捕捉列表总是出现在lambda函数的开始处。事实上,[]是lambda引出符。编译器根据该引出符判断接下来的代码是否是lambda函数。捕捉列表能够捕捉上下文中的变量以供lambda函数使用。具体的方法在下文中会再描述。
❑(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略。
❑mutable:mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。
❑->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值的时候也可以连同符号->一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
❑{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
在lambda函数的定义中,参数列表和返还类型都是可选的部分,而捕捉列表和函数体都可能为空。那么在极端情况下,C++11中最为简略的lambda函数只需要声明为
[]{};
就可以了。不过理所应当地,该lambda函数不能做任何事情。
代码清单7-18中列出了各种各样的lambda函数。
代码清单7-18
int main(){
[]{};//最简lambda函数
int a=3;
int b=4;
[=]{return a+b;};//省略了参数列表与返回类型,返回类型由编译器推断为int
auto fun1=&{b=a+c;};//省略了返回类型,无返回值
auto fun2==,&b->int{return b+=a+c;};//各部分都很完整的lambda函数
}
//编译选项:g++ -std=c++11 7-3-2.cpp
在代码清单7-18中,我们看到了各种各样的捕捉列表的使用。直观地讲,lambda函数与普通函数可见的最大区别之一,就是lambda函数可以通过捕捉列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些的数据可以被lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。在代码清单7-17的例子中,我们是使用参数的方式传递变量,现在让我们使用捕捉列表来改写这个例子,如代码清单7-19所示。
代码清单7-19
int main(){
int boys=4,int girls=3;
auto totalChild=girls,&boys->int{return girls+boys;};
return totalChild();
}
//编译选项::g++-std=c++11 7-3-3.cpp
代码清单7-19中,我们使用了捕捉列表捕捉上下文中的变量girls、boys。与代码清单7-17相比,函数的原型发生了变化,即totalChild不再需要传递参数。这个改变看起来平淡无奇,不过读者在阅读7.3.3节之后可以知道,此时girls和boys可以视为lambda函数的一种初始状态,lambda函数的运算则是基于初始状态进行的运算。这与函数简单基于参数的运算是有所不同的。
语法上,捕捉列表由多个捕捉项组成,并以逗号分割。捕捉列表有如下几种形式:
❑[var]表示值传递方式捕捉变量var。
❑[=]表示值传递方式捕捉所有父作用域的变量(包括this)。
❑[&var]表示引用传递捕捉变量var。
❑[&]表示引用传递捕捉所有父作用域的变量(包括this)。
❑[this]表示值传递方式捕捉当前的this指针。
注意 父作用域:enclosing scope,这里指的是包含lambda函数的语句块,在代码清单7-19中,即main函数的作用域。
通过一些组合,捕捉列表可以表示更复杂的意思。比如:
❑[=,&a,&b]表示以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。
❑[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其他所有变量。
不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。
❑[=,a]这里=已经以值传递方式捕捉了所有变量,捕捉a重复。
❑[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。
利用以上的规则,对于代码清单7-19的lambda函数,我们可以通过[=]来声明捕捉列表,进而对totalChild书写上的进一步简化,如代码清单7-20所示。
代码清单7-20
int main(){
int boys=4,girls=3;
auto totalChild==->int{return girls+boys;};//捕捉所有父作用域的变量
return totalChild();
}
//编译选项::g++-std=c++11 7-3-4.cpp
通过捕捉列表[=],lambda函数的父作用域中所有自动变量都被lambda依照传值的方式捕捉了。
必须指出的是,依照现行C++11标准,在块作用域(block scope,可以简单理解为在{}之内的任何代码都是块作用域的)以外的lambda函数捕捉列表必须为空。因此这样的lambda函数除去语法上的不同以外,跟普通函数区别不大。而在块作用域中的lambda函数仅能捕捉父作用域中的自动变量,捕捉任何非此作用域或者是非自动变量(如静态变量等)都会导致编译器报错。