在 C++ 编程中,仿函数(Functor) 也叫函数对象(Function Object),是一个披着类 / 对象外衣、却能像普通函数一样被调用的语法特性。它是 STL(标准模板库)的核心设计之一,既能实现函数的功能,又拥有对象的状态、封装、继承等优势,是 C++ 区别于其他语言的实用编程技巧。
本文从基础定义、用法、优势、实战场景完整讲解仿函数,帮你快速掌握这个高频知识点。
一、什么是仿函数?
1. 核心定义
仿函数 = 重载了 operator() 运算符的类的对象。
简单说:
写一个类,在里面重载 () 运算符;
创建这个类的对象;
这个对象就可以像函数一样使用 对象名(参数) 调用。
因为 "用起来像函数",所以叫仿函数。
2. 最简示例
先看一个最直观的例子:
cpp
#include <iostream>
using namespace std;
// 定义一个仿函数类
class MyFunctor {
public:
// 重载 () 运算符 → 核心!
void operator()() {
cout << "我是仿函数!" << endl;
}
};
int main() {
MyFunctor func; // 创建对象
func(); // 像函数一样调用!本质是 func.operator()()
return 0;
}
运行输出:
cpp
我是仿函数!
3. 带参数的仿函数
仿函数可以像普通函数一样接收参数、有返回值:
cpp
// 加法仿函数
class Add {
public:
int operator()(int a, int b) {
return a + b;
}
};
int main() {
Add add;
int res = add(10, 20); // 像函数一样传参调用
cout << res << endl; // 输出 30
return 0;
}
二、仿函数 vs 普通函数 vs Lambda
你一定会问:既然有函数,为什么还要仿函数?
这是仿函数最核心的价值:仿函数是对象,可以保存状态。
1. 仿函数最大优势:自带 "记忆"(状态)
普通函数用静态变量也能存状态,但线程不安全、代码混乱;仿函数用成员变量存状态,安全、干净、灵活。
示例:计数器仿函数
cpp
// 计数仿函数:记录自己被调用了多少次
class Counter {
private:
int count = 0; // 成员变量 → 状态
public:
int operator()() {
return ++count;
}
};
int main() {
Counter c1, c2; // 两个独立对象,各自计数
cout << c1() << endl; // 1
cout << c1() << endl; // 2
cout << c2() << endl; // 1
return 0;
}
输出:
cpp
1
2
1
两个对象互不干扰,完美实现带独立状态的函数。
2. 三者对比
表格
| 特性 | 普通函数 | 仿函数(函数对象) | Lambda 表达式 |
|---|---|---|---|
| 能否保存状态 | 难(静态变量不安全) | 能(成员变量) | 能(捕获变量) |
| 能否复用 / 继承 | 不能 | 能 | 不能 |
| 作为 STL 算法参数 | 可以 | 推荐 | 可以 |
| 代码复杂度 | 简单 | 稍复杂 | 简洁 |
结论 :需要带状态、可复用、可配置的函数逻辑时,仿函数是最优解。
三、仿函数在 STL 中的实战用法
STL 大量算法(sort、find_if、for_each 等)都依赖仿函数作为策略参数,这是仿函数最常用的场景。
1. 配合 sort 实现自定义排序
默认 sort 是升序,用仿函数可以轻松改规则:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 降序仿函数
class GreaterThan {
public:
bool operator()(int a, int b) {
return a > b;
}
};
int main() {
vector<int> v = {3,1,4,1,5};
sort(v.begin(), v.end(), GreaterThan()); // 传入仿函数对象
for(int x : v) cout << x << " ";
return 0;
}
输出:
cpp
5 4 3 1 1
- 配合 for_each 遍历处理
cpp
// 打印仿函数
class Print {
public:
void operator()(int x) {
cout << x << " ";
}
};
// 使用
for_each(v.begin(), v.end(), Print());
四、仿函数的分类(STL 标准)
C++ STL 内置了大量标准仿函数,放在 <functional> 头文件中,分为三类:
1. 算术仿函数
plus<T>:加法
minus<T>:减法
multiplies<T>:乘法
negate<T>:取反
示例:
cpp
#include <functional>
plus<int> p;
cout << p(10, 20) << endl; // 30
2. 关系仿函数
equal_to<T>:等于
greater<T>:大于
less<T>:小于
常用于排序:
cpp
sort(v.begin(), v.end(), greater<int>()); // 降序
3. 逻辑仿函数
logical_and<T>:逻辑与
logical_or<T>:逻辑或
logical_not<T>:逻辑非
五、仿函数底层原理(一句话)
仿函数本质是对象调用重载的 operator() ,编译器会把 func(a,b) 直接翻译成 func.operator()(a,b)。
它的效率极高,和普通函数几乎没有性能差距,甚至比函数指针更快。
六、使用注意事项
- 仿函数必须重载
()运算符,这是唯一入口; - 仿函数对象可以临时创建 (直接写
类名()); - 成员变量可以给仿函数设置初始状态;
- STL 算法传参时,优先用仿函数 / Lambda,少用函数指针;
- 现代 C++ 中,简单场景可用 Lambda 替代仿函数,但复杂带状态逻辑仍推荐仿函数。
总结
- 仿函数 :重载
operator()的类的对象,用起来像函数; - 核心优势 :可以用成员变量保存状态,比普通函数更强大;
- 主要用途:作为 STL 算法的策略(排序、过滤、遍历);
- 语法 :
返回值 operator()(参数) { 实现 }; - STL 常用 :
greater<int>()、less<int>()、plus<int>()。
仿函数是 C++ 面向对象 + 泛型编程的经典结合,掌握它能大幅提升代码的灵活性和复用性。