一、函数调用运算符
写一个函数与调用一个函数都很容易:
void func(int i) {
cout << "这是函数func()" << i << endl;
return;
}
int main() {
func(5);
return 0;
}
通过上面的代码可以感受到,那就是函数调用总离不开一对圆括号,()
就是函数调用的一个明显标记,这个()
有一个称呼叫函数调用运算符。那么如果在类中重载了这个函数调用运算符()
,就可以像使用函数一样使用该类的对象,或者换句话说,可以像函数调用一样来“调用”该类的对象。
class biggerthanzero {
public:
// 重载函数调用运算符()
int operator()(int value) const {
if (value < 0) { // 如果值<0就返回0,否则返回实际的值
return 0;
}
return value;
}
};
main()
函数中使用这个函数调用运算符:
int i = 200;
biggerthanzero obj; // 含有函数调用运算符的对象
int result = obj(i); // 调用类中重载的函数调用运算符(),本行代码等价于int result = obj.operator()(i);
现在往类biggerthanzero中增加一个public
修饰的带一个参数的构造函数,如下:
public:
biggerthanzero(int i) {
cout << "biggerthanzero::biggerthanzero(int i)构造函数执行了" << endl;
}
改变main()
函数中的代码:
int i = 200;
biggerthanzero obj(1); // 对象定义并初始化,调用的是构造函数
obj(i); // 这个才是调用类中重载的函数调用运算符()
只要这个对象所属的类重载了()
,那么这个类对象就变成了可调用对象(函数对象),而且可以调用多个版本的()
,只要在参数类型或数量上有差别即可,例如上面的()
是带一个int
参数的。
二、不同调用对象的相同调用形式
如下函数:
int echovalue(int value) {
cout << value << endl;
return value;
}
上面这个echovalue
函数与biggerthanzero
类中重载的函数调用运算符比较,可以发现它们的形参和返回值是相同的,这就叫作“调用形式相同”。有一句话叫“一种调用形式对应一个函数类型”,本来echovalue
函数与biggerthanzero
对象之间没有什么关系,但是因为它们调用形式相同,从而扯到了“函数类型相同”这个关系上来了。上面范例中的“函数类型”是int(int)
,即接收一个int
参数,返回一个int
值。引入“可调用对象”这个概念(或者叫“函数对象”、“仿函数”),如下两个都是可调用对象:echovalue
函数和重载了函数调用运算符()
的biggerthanzero
类所生成的对象。现在把这些可调用对象的指针保存起来,方便后续随时调用,这些指针就是C语言中的函数指针:
int (*p)(int x, int y); // 定义一个函数指针
p = max; // 将函数max的入口地址赋给指针变量p
int c = (*p)(5, 19); // 调用*p就是调用函数max,p指向函数入口,等价于int c = max(5, 19);
下面通过一个map
容器来存放可调用对象的指针:
#include <map>
map<string, int(*)(int)> myoper;
“键”是一个字符串,“值”是一个函数指针,这里放在map中时,函数指针只保留了*
,指针名就去掉了。
myoper.insert({"ev", echovalue}); // 往容器增加一个键值对
上面代码把echovalue
函数指针(函数名代表函数首地址,可以认为是一个函数指针)放到容器中,接下来放这个来自于类的函数对象,在这里用类名、对象名都不行,例如以下代码都报错:
biggerthanzero obj; // 含有函数调用运算符的对象
myoper.insert({"bt", biggerthanzero}); // 报错
myoper.insert({"bt", obj}); // 报错
这说明系统没有把类biggerthanzero
的对象obj
看成一个函数指针,当然系统更不可能把类biggerthanzero
看成一个函数指针(因为这是个类名)。
三、标准库function类型简介
C++11中,标准库里有一个叫做function的类模板,这个类模版是用来包装一个可调用对象的。使用这个类模版,要提供相应的模版参数,这个模版参数就是指该function类型能够表示(包装)的可调用对象的调用形式,它长下面这样:
#include <function>
function<int(int)>
这种写法是一个类类型(类名就是function<int(int)>
,用来代表(包装)一个可调用对象,它所代表的这个可调用对象接收一个int
参数,返回一个int
值。看看怎么使用:
biggerthanzero obj; // 含有函数调用运算符的对象
function<int(int)> f1 = echovalue; // 函数指针
function<int(int)> f2 = obj; // 类对象,类中重载了函数调用运算符
function<int(int)> f3 = biggerthanzero(); // 类名加()生成一个临时对象,类中重载了函数调用运算符
// 调用一下
f1(5); // 5
cout << f2(3) << endl; // 3
cout << f3(-5) << endl; // 0
现在这个function<int(int)>
类型就好像一个标准一样,把函数指针、函数对象都包装成function<int(int)>
这种类型的对象,相当于把类型给统一了。修改一下上面的myoper容器为:
map<string, function<int(int)>> myoper;
把可调用对象往容器里放:
biggerthanzero obj;
map<string, function<int(int)>> myoper = {
{"ev", echovalue},
{"bt", obj},
{"bt2", biggerthanzero()}
};
调用一下放到容器里的可调用对象:
myoper["ev"](12); // 相当于调用echovalue函数
cout << myoper["bt"](3) << endl; // 调用obj对象的函数调用运算符()
cout << myoper["bt2"](-5) << endl; // 调用biggerthanzero类对象的函数调用运算符()
再看一个问题,如果有一个重载的echovalue
函数,参数和返回值都和上面的不一样:
void echovalue() {
return;
}
这时下面这行语句编译时就会报错:
function<int(int)> f1 = echovalue;
说明只要函数是重载的,就无法包装进function中,可以通过定义一个函数指针来解决:
int(*fp)(int) = echovalue;
function<int(int)> f1 = fp; // 直接塞进去函数指针而不是函数名