函数调用运算符与function类模板

本文介绍了函数调用运算符的重载方法及其在类中的应用,并探讨了不同调用对象相同调用形式的概念,最后讲解了C++11标准库中的function类型,用于统一包装各种可调用对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、函数调用运算符

写一个函数与调用一个函数都很容易:

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;  // 直接塞进去函数指针而不是函数名
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值