7.3.4 lambda的基础使用
依据lambda的语法,编写lambda函数是非常容易的。不过用得上lambda函数的地方比较特殊。最为简单的应用下,我们会利用lambda函数来封装一些代码逻辑,使其不仅具有函数的包装性,也具有就地可见的自说明性。让我们首先来看一个例子,如代码清单7-23所示。
代码清单7-23
extern int z;
extern float c;
void Calc(int&,int,float&,float);
void TestCalc(){
int x,y=3;
float a,b=4.0;
int success=0;
auto validate=&->bool
{
if((x==y+z)&&(a==b+c))
return 1;
else
return 0;
};
Calc(x,y,a,b);
success+=validate();
y=1024;
b=1e13;
Calc(x,y,a,b);
success+=validate();
}
//编译选项:g++ -c-std=c++11 7-3-7.cpp
在代码清单7-23所示的例子中,用户试图用自己写的函数TestCalc进行测试。这里使用了一个auto关键字推导出了validate变量的类型为匿名lambda函数。可以看到,我们使用lambda函数直接访问了TestCal中的局部的变量来完成这个工作。
在没有lambda函数之前,通常需要在TestCalc外声明同样一个函数,并且把TestCalc中的变量当作参数进行传递。出于函数作用域及运行效率考虑,这样声明的函数通常还需要加上关键字static和inline。相比于一个传统意义上的函数定义,lambda函数在这里更加直观,使用起来也非常简便,代码可读性很好,效果上,lambda函数则等同于一个“局部函数”。
注意 局部函数(local function,即在函数作用域中定义的函数),也称为内嵌函数(nested function)。局部函数通常仅属于其父作用域,能够访问父作用域的变量,且在其父作用域中使用。C/C++语言标准中不允许局部函数存在(不过一些其他语言是允许的,比如FORTRAN),C++11标准却用比较优雅的方式打破了这个规则。因为事实上,lambda可以像局部函数一样使用。
必须指出的是,相比于在函数外定义的static inline函数,或者是自定义的宏,本例中lambda函数并没有实际运行时的性能优势(但也不会差,lambda函数在C++11标准中默认是内联的)。同局部函数一样,lambda函数在代码的作用域上仅属于其父作用域,不过直观地看,lambda函数代码的可读性可能更好,尤其对于小的函数而言。
对于运算比较复杂的函数(比如实现一些很复杂的算法),通常函数中会有大量的局部状态(变量),这个时候如果程序员只是需要一些“局部”的功能——比如打印一些内部状态,或者做一些固定的操作,这些功能往往不能与其他任何的代码共享,却要在一个函数中多次重用。那么使用lambda的捕捉列表功能则相较于独立的全局静态函数或私有成员函数方便很多。设计一个仅使用捕捉列表lambda函数不会像设计传统函数一样要关心大量细节:需要多少参数、哪些参数需要按值传递、哪些参数需要按引用或者指针传递,通通不需要程序员来考虑。而且父函数结束后,该lambda函数也就不再可用了,不会污染任何名字空间(当然,事实上如我们所讲的,lambda本身就是匿名的函数)。因此,对于复杂代码的快速开发而言,lambda的出现意义重大。事实上,这些也是局部函数的好处。在C++11之前,程序员只能通过编写类来模拟局部函数,实现复杂而且常常会存在着各种各样的问题,lambda的出现使得这样的做法统统成为了历史。
我们再来看一个常量性的例子。在编写程序的时候,程序员通常会发现自己需要一些“常量”,不过这些常量的值却由自己初始化状态决定的。我们来看看代码清单7-24所示的例子。
代码清单7-24
int Prioritize(int);
int AllWorks(int times){
int i;
int x;
try{
for(i=0;i<times;i++)
x+=Prioritize(i);
}
catch(…){
x=0;
}
const int y=[=]{
int i,val;
try{
for(i=0;i<times;i++)
val+=Prioritize(i);
}
catch(…){
val=0;
}
return val;
}();
}
//编译选项:g++ -std=c++11 7-3-8.cpp-c
在代码清单7-24所示的例子中,我们对x和y的初始化实际是完全一致的。可以看到,x(或y)的初始化需要循环调用函数Prioritize,并且在Prioritize抛出异常的时候对x(或y)赋默认值0。
在不使用函数的情况下,由于初始化要在运行时修改x的值,因此,虽然x在初始化之后对于程序而言是个常量,却不能被声明为const。而在定义y的时候,由于我们就地定义lambda函数并且调用,y仅需使用其返回值,于是常量性得到了保证。
读者可能觉得也可以定义一个普通函数来完成y的常量定义,但对于代码清单7-24中的代码量较少,自说明意义较强的初始化而言,无疑采用lambda函数初始化的时候,代码的可读性会更好。而且这么写,我们也就不需要为这段代码逻辑取个函数名(通常init这样的名字是正确的,但是却很难解释清楚代码做了什么)。这是lambda函数的一个优势所在。