在 C++ 中,函数调用运算符 () 的重载是一种特殊的运算符重载方式,允许自定义类的对象像函数一样被调用(这类对象也被称为 "函数对象" 或 "仿函数")。函数调用运算符重载是实现 STL 算法(如sort、for_each)自定义逻辑的核心手段,也是现代 C++ 中 lambda 表达式的底层实现基础。
一、核心规则
1、重载方式:只能作为类的成员函数重载(全局函数无法重载())。
2、返回值:无固定要求(可返回任意类型,如int、bool、自定义类型,也可void)。
3、参数列表:支持任意数量、任意类型的参数(可重载多个版本,区分参数类型 / 个数)。
4、const 修饰:若函数对象无需修改自身状态,建议加const(保证常量对象可调用)。
5、语义:调用对象(参数)等价于调用对象.operator()(参数)。
二、基础示例:简单仿函数
先实现一个最基础的函数调用运算符重载,让对象能像函数一样执行逻辑:
cpp
#include <iostream>
using namespace std;
// 定义一个可调用的类(仿函数)
class Add {
public:
// 重载函数调用运算符:接收两个int,返回和
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
// 创建函数对象
Add add_obj;
// 像调用函数一样调用对象(等价于 add_obj.operator()(3,5))
int result = add_obj(3, 5);
cout << "3 + 5 = " << result << endl; // 输出:3 + 5 = 8
// 也可直接创建临时对象并调用
cout << "10 + 20 = " << Add()(10, 20) << endl; // 输出:10 + 20 = 30
return 0;
}
三、进阶示例:带状态的仿函数
函数对象的核心优势是可保存状态(成员变量),这是普通函数无法做到的。例如实现一个 "累加器":
cpp
#include <iostream>
using namespace std;
class Accumulator {
private:
int total = 0; // 保存累加状态
public:
// 重载1:接收int,累加并返回当前总和
int operator()(int num) {
total += num;
return total;
}
// 重载2:无参版本,返回当前总和
int operator()() const {
return total;
}
// 重置状态
void reset() {
total = 0;
}
};
int main() {
Accumulator acc;
// 多次调用,累计状态
acc(10); // total = 10
acc(20); // total = 30
acc(30); // total = 60
cout << "累计总和:" << acc() << endl; // 输出:60
acc.reset();
acc(5);
cout << "重置后累计:" << acc() << endl; // 输出:5
return 0;
}
四、实战场景:配合 STL 算法使用
函数调用运算符重载最常用的场景是为 STL 算法提供自定义逻辑(如排序、遍历、筛选)。
示例 1:自定义排序规则
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 按年龄降序排序的仿函数
class CompareAgeDesc {
public:
// 假设元素是pair<姓名, 年龄>
bool operator()(const pair<string, int>& a, const pair<string, int>& b) const {
return a.second > b.second; // 降序:a的年龄 > b的年龄则a排在前面
}
};
int main() {
vector<pair<string, int>> students = {
{"张三", 20}, {"李四", 25}, {"王五", 18}
};
// 使用仿函数作为排序规则
sort(students.begin(), students.end(), CompareAgeDesc());
// 输出排序结果
for (const auto& s : students) {
cout << s.first << ":" << s.second << "岁" << endl;
}
return 0;
}
示例 2:遍历容器并累加
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 累加容器中元素的仿函数
class SumInt {
private:
int sum = 0;
public:
// 重载():接收int,累加
void operator()(int num) {
sum += num;
}
// 获取累加结果
int getSum() const {
return sum;
}
};
int main() {
vector<int> nums = {1, 2, 3, 4, 5};
// 用for_each遍历,传入仿函数
SumInt sum_obj = for_each(nums.begin(), nums.end(), SumInt());
cout << "数组总和:" << sum_obj.getSum() << endl; // 输出:15
return 0;
}
五、关键注意事项
1、与普通函数的区别:
函数对象可保存状态(成员变量),普通函数只能通过全局 / 静态变量实现(不推荐);
函数对象支持重载(多个operator()版本,区分参数),普通函数重载需手动写多个函数名。
2、const 正确性:
若仿函数无需修改自身状态,务必给operator()加const(如排序规则的仿函数);
加const后,常量对象也能调用该运算符。
3、与 lambda 表达式的关系:
C++11 后的 lambda 表达式本质是编译器自动生成的 "匿名仿函数";
上述排序示例用 lambda 简化:
cpp
sort(students.begin(), students.end(),
[](const pair<string, int>& a, const pair<string, int>& b) {
return a.second > b.second;
});
4、重载多个版本:
可根据参数类型 / 个数重载多个operator(),满足不同调用场景:
cpp
class Calc {
public:
int operator()(int a, int b) const { return a + b; }
double operator()(double a, double b) const { return a * b; }
int operator()(int a) const { return a * 2; }
};
// 调用
Calc calc;
cout << calc(3,5) << endl; // 8(int加法)
cout << calc(2.5, 4.0) << endl;// 10.0(double乘法)
cout << calc(6) << endl; // 12(int翻倍)
5、不可重载为全局函数:
operator() 是成员函数专属的重载运算符,全局函数无法重载(因为对象()的语法要求左操作数是类对象)。
六、总结
函数调用运算符重载的核心价值是:
让类对象拥有 "函数行为",兼具函数的灵活性和类的状态管理能力;
是 STL 算法自定义逻辑的核心手段,也是 lambda 表达式的底层支撑;
重载时需注意const修饰(无状态时必加),并根据场景设计参数和返回值。
相比普通函数,函数对象(仿函数)在需要 "带状态的函数逻辑" 或 "重载不同参数的函数行为" 时,优势尤为明显。