7.3.3 lambda与仿函数
好的编程语言一般都有好的库支持,C++也不例外。C++语言在标准程序库STL中向用户提供了一些基本的数据结构及一些基本的算法等。在C++11之前,我们在使用STL算法时,通常会使用到一种特别的对象,一般来说,我们称之为函数对象,或者仿函数(functor)。仿函数简单地说,就是重定义了成员函数operator()的一种自定义类型对象。这样的对象有个特点,就是其使用在代码层面感觉跟函数的使用并无二样,但究其本质却并非函数。我们可以看一个仿函数的例子,如代码清单7-21所示。
代码清单7-21
class_functor{
public:
int operator()(int x,int y){return x+y;}
};
int main(){
int girls=3,boys=4;
_functor totalChild;
return totalChild(5,6);
}
//编译选项:g++7-3-5.cpp-std=c++11
这个例子中,class_functor的operator()被重载,因此,在调用该函数的时候,我们看到跟函数调用一样的形式,只不过这里的totalChild不是函数名称,而是对象名称。
注意 相比于函数,仿函数可以拥有初始状态,一般通过class定义私有成员,并在声明对象的时候对其进行初始化。私有成员的状态就成了仿函数的初始状态。而由于声明一个仿函数对象可以拥有多个不同初始状态的实例,因此可以借由仿函数产生多个功能类似却不同的仿函数实例(这里是一个多状态的仿函数的实例)。
include <iostream>
using namespace std;
class Tax{
private:
float rate;
int base;
public:
Tax(float r,int b):rate(r),base(b){}
float operator()(float money){return(money-base)*rate;}
};
int main(){
Tax high(0.40,30000);
Tax middle(0.25,20000);
cout<<"tax over 3w:"<<high(37500)<<endl;
cout<<"tax over 2w:"<<middle(27500)<<endl;
return 0;
}
这里通过带状态的仿函数,可以设定两种不同的税率的计算。
而仔细观察的话,除去自定义类型_functor的声明及其对象的定义,可以发现代码清单7-21跟代码清单7-17中lambda函数的定义看起非常类似。这是否说明仿函数跟lambda在实现之间存在着一种默契呢?我们可以再来看一个例子,如代码清单7-22所示。
代码清单7-22
class AirportPrice{
private:
float_dutyfreerate;
public:
AirportPrice(float rate):_dutyfreerate(rate){}
float operator()(float price){
return price*(1-_dutyfreerate/100);
}
};
int main(){
float tax_rate=5.5f;
AirportPrice Changi(tax_rate);
auto Changi2=
tax_rate->float{return price*(1-tax_rate/100);};
float purchased=Changi(3699);
float purchased2=Changi2(2899);
}
//编译选项:g++7-3-6.cpp-std=c++11
代码清单7-22是一个机场返税的例子。该例中,分别使用了仿函数和lambda两种方式来完成扣税后的产品价格计算。在这里我们看到,lambda函数捕捉了tax_rate变量,而仿函数则以tax_rate初始化类。其他的,如在参数传递上,两者保持一致。可以看到,除去在语法层面上的不同,lambda和仿函数却有着相同的内涵——都可以捕捉一些变量作为初始状态,并接受参数进行运算。
而事实上,仿函数是编译器实现lambda的一种方式。在现阶段,通常编译器都会把lambda函数转化为成为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式了,或者更动听地说,lambda是仿函数的“语法甜点”。
我们可以通过图7-1展现代码清单7-22中的lambda函数和仿函数是如何等价的。
图 7-1 lambda函数及与其等价的仿函数
注意 有的时候,我们在编译时发现lambda函数出现了错误,编译器会提示一些构造函数等相关信息。这显然是由于lambda的这种实现方式造成的。理解了这种实现,用户也就能够正确理解错误信息的由来。
如前面提到的,仿函数被广泛地用于STL中,同样的,在C++11中,lambda也在标准库中被广泛地使用。由于其书写简单,通常可以就地定义,因此用户常可以使用lambda代替仿函数来书写代码,我们可以在7.3.4节中看到相关的应用方式,在7.3.6中了解lambda何时可以取代仿函数。