C++语言有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。
和其他对象一样,可调用的对象也有类类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定。
然而,两种不同类型的可调用对象却可能共享同一种调用形式。调用形式指明了调用返回的类型以及传递给调用的实参类型。一种调用形式对应一个函数类型,例如:
int(int, int);
是一个函数类型,它接受两个int,返回一个int
不同类型可能具有相同的调用形式
对于几种可调用对象共享同一种调用形式的情况,有时我们希望把它们看成具有相同的类型。例如:
//普通函数
int add(int i, int j) { return i + j; }
//lambda,其产生一个未命名的函数对象类
auto mod = [](int i, int j) { return i % j; }
//函数对象类
struct divide{
int operator()(int denominator, int divisor)
{
return denominator / divisor;
}
};
它们的类型尽管个不相同,但是共享同一种调用形式:
int(int, int)
我们可能希望使用这些可调用对象构建一个简单的桌面计算器,为了实现这一目的,需要定义一个函数表用于存储指向这些可调用对象的“指针”,当程序需要执行某个特定的操作时,就从该表查找该调用的函数。
函数表通过map实现,使用一个string作为关键字,使用实现运算符的函数作为值。
map<string, int(*)(int, int)> binops;
我们可以按照下面的形式将add添加的指针添加到binops中:
//正确:add是一个指向正确类型函数的指针
binops.insert({"+", add}); //{"+", add}是一个Pair
但我们不能将mod或者divide存入binops,因为mod不是一个函数指针。
标准库function类型
我们可以使用一个名为function的新的标准库类型解决上述问题,function定义在functional头文件中,下表列举出function定义的操作。
function的操作 | |
---|---|
function<T> f; | f是一个用来存储可调用对象的空function,这些可调节对象的调用形式应该与函数类型T相同(T是retType(args)) |
function<T> f(nullptr); | 显示构造一个空function |
function<T> f(obj); | 在f中存储可调用对象obj的副本 |
f | 将f作为条件:当f含有一个可调用对象是为真,否则为假 |
f(args) | 调用ff中的对象,参数是args |
定义为function<T>的成员的类型 | |
result_type | 该function类型的可调用对象返回的类型 |
argument_type | 当T有一个或两个实参时定义的类型。如果T只有一个实参,则argument_type是该类型的同义词,如果T有两个实参,则first_argument_type和second_argument_type分表代表 两个实参的类型 |
first_argument_type | |
second_argument_type |
function是一个模板,当创建一个具体的function类型时我们必须提供额外的信息,在此例中,所谓额外的信息是指该function类型能够表示的对象的调用形式。
function<int(int, int)>
使用:
function<int(int, int)> f1 = add; //函数指针
function<int(int, int)> f2 = divide(); //函数对象类的对象
function<int(int, int)> f3 = [](int i, int j) {return i * j;}; //lambda
//调用
cout << f1(4, 2) << endl; //打印6
cout << f2(4, 2) << endl; //打印2
cout << f3(4, 2) << endl; //打印8
使用这个function类型我们可以重新定义map:
//列举可调用对象与二元运算符对应关系的表格
//所有可调用对象都必须接受两个int,返回一个int
//其中的元素可以是函数指针,函数对象或者lambda
map<string, function<int(int, int)>> binops;
我们把所有可调用对象,包括指针、lambda或者函数对象在内,都添加到这个map:
map<string, function<int(int, int)>> binops = {
{"+", add}, //函数指针
{"-", std::minus<int>}, //标准库函数对象
{"/", divide()}, //用户定义的函数对象
{"*", [](int i, int j) { return i * j ;}}, //未命名lambda
{"%", mod}}; //命名的lambda
}
binops["+"](10, 5); //调用add(10, 5);
binops["-"](10, 5); //使用std::minus<int>对象的调用运算符
binops["/"](10, 5); //使用devide对象的调用运算符
binops["*"](10, 5); //调用lambda函数对象
binops["%"](10, 5); //调用lambda函数对象
重载的函数与function
我们不能(直接)将重载函数的名字存入function类型的对象中:
int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
map<string, function<int(int, int)>> binops;
binops.insert({"+", add}) ; //错误,哪个add?
解决办法1:
//存储函数指针而非函数的名字
int (*fp)(int, int) = add; //指针所指向的add是接受两个int的版本
binops.insert({"+", fp}); // 正确,fp指向一个正确的add版本
解决办法2:
//使用lambda来指定我们希望使用的add版本
binops.insert({"+", [](int a, int b) { return add(a, b);}});
lambda内部的函数调用传入两个int,因此该调用只能匹配接受两个int的add版本,而这也正是执行lambda时真正调用的函数。