在 C++ 中,仿函数(Function Object 或 Functor) 是一种行为类似于函数的对象。仿函数是通过重载 operator() 运算符来实现的,这样一个对象就可以像函数一样调用。

仿函数的核心思想是:对象可以具有类似函数的行为,并且可以携带状态。这为设计灵活的函数调用方式提供了可能性,尤其是在 STL(标准模板库)中,仿函数被广泛应用于算法、容器操作等场景。


1. 仿函数的基本概念

仿函数是通过在一个类中重载 operator() 运算符来实现的。这样,类的对象就可以被调用(表现得像一个函数)。

仿函数的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

class MyFunctor {
public:
// 重载 operator()
void operator()(int x) {
cout << "Called with: " << x << endl;
}
};

int main() {
MyFunctor functor; // 创建仿函数对象
functor(10); // 像函数一样调用对象
return 0;
}

输出

1
Called with: 10

在这个例子中,functor(10) 的调用会自动调用 MyFunctor 类的 operator() 函数。这样,functor 对象就表现得像一个函数。


2. 仿函数的优点

  1. 携带状态:
    仿函数可以包含成员变量,用于保存状态,而普通函数无法做到这一点。

  2. 灵活性和可扩展性:
    仿函数是一个类,可以拥有额外的功能,如构造函数、成员函数等,提供比普通函数更多的功能和灵活性。

  3. STL 算法支持:
    仿函数与 STL 算法(如 std::sortstd::for_each)配合得非常好,用于定义自定义的行为。


3. 仿函数的应用

3.1 仿函数与 STL 算法结合

仿函数可以作为参数传递给 STL 算法,例如 std::sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 定义一个仿函数
class Compare {
public:
bool operator()(int a, int b) {
return a > b; // 降序排列
}
};

int main() {
vector<int> vec = {5, 2, 8, 1, 3};

// 使用仿函数作为自定义排序规则
sort(vec.begin(), vec.end(), Compare());

for (int x : vec) {
cout << x << " ";
}

return 0;
}

输出

1
8 5 3 2 1

说明:

  • 仿函数 Compare 定义了一个自定义排序规则:返回 true 时表示 a 应该排在 b 的前面。
  • std::sort 接受仿函数作为第三个参数,用于对容器中的元素进行排序。

3.2 状态保存的仿函数

仿函数可以保存状态,普通函数无法做到这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

class Adder {
int value; // 保存状态
public:
Adder(int v) : value(v) {}

int operator()(int x) {
return x + value; // 返回加上状态值后的结果
}
};

int main() {
Adder add5(5); // 创建一个仿函数对象,保存状态值 5
Adder add10(10); // 创建另一个仿函数对象,保存状态值 10

cout << add5(3) << endl; // 输出 8(3 + 5)
cout << add10(3) << endl; // 输出 13(3 + 10)

return 0;
}

输出

1
2
8
13

说明:

  • 仿函数 Adder 内部保存了一个状态 value,通过构造函数进行初始化。
  • 调用仿函数时,可以使用该状态进行计算,类似于闭包(closure)的效果。

3.3 使用 STL 的 std::for_each

仿函数可以用于 STL 算法 std::for_each,实现对容器中每个元素的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 定义一个仿函数,用于打印元素
class Print {
public:
void operator()(int x) {
cout << x << " ";
}
};

int main() {
vector<int> vec = {1, 2, 3, 4, 5};

// 使用仿函数与 for_each
for_each(vec.begin(), vec.end(), Print());

return 0;
}

输出

1
1 2 3 4 5

说明:

  • Print 仿函数定义了如何处理每个元素。
  • std::for_each 将容器中的每个元素依次传递给仿函数。

3.4 Lambda 表达式的替代

在现代 C++(C++11 及以上)中,仿函数可以被 Lambda 表达式 替代。Lambda 表达式更简洁,但仿函数在某些复杂场景下依然有优势。

等价于上面的 std::for_each 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
vector<int> vec = {1, 2, 3, 4, 5};

// 使用 Lambda 表达式替代仿函数
for_each(vec.begin(), vec.end(), [](int x) {
cout << x << " ";
});

return 0;
}

输出

1
1 2 3 4 5

4. STL 中常见的仿函数

C++ STL 提供了一些常用的内置仿函数,主要定义在头文件 <functional> 中:

4.1 算术仿函数

  • std::plus:加法。
  • std::minus:减法。
  • std::multiplies:乘法。
  • std::divides:除法。
  • std::modulus:取模。
  • std::negate:取负。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <functional>
using namespace std;

int main() {
plus<int> add; // 加法仿函数
multiplies<int> multiply; // 乘法仿函数

cout << add(3, 5) << endl; // 输出 8
cout << multiply(3, 5) << endl; // 输出 15

return 0;
}

4.2 逻辑仿函数

  • std::equal_to:等于。
  • std::not_equal_to:不等于。
  • std::greater:大于。
  • std::less:小于。
  • std::greater_equal:大于等于。
  • std::less_equal:小于等于。

4.3 逻辑操作仿函数

  • std::logical_and:逻辑与。
  • std::logical_or:逻辑或。
  • std::logical_not:逻辑非。

5. 仿函数与 Lambda 的对比

特性 仿函数 Lambda 表达式
状态管理 通过类的成员变量保存状态 捕获变量(自动或手动捕获)
代码长度 需要定义一个类,代码较长 简洁(C++11 及以上)
扩展性 灵活,可定义复杂行为 简单场景更适用
兼容性 C++98 开始支持 C++11 及以上支持

总结

  • 仿函数是通过重载 operator() 实现的对象,可以像函数一样调用。
  • 仿函数能够保存状态,并灵活应用于 STL 算法。
  • 在现代 C++ 中,仿函数仍然有其独特的作用,尤其是在复杂的场景中,而简单场景更推荐使用 Lambda 表达式。