在C++标准模板库(STL)中,仿函数(Functor),也称为函数对象,是一个行为类似函数的对象。仿函数通过重载函数调用运算符 ()
,使对象可以像函数一样被调用。仿函数在STL中广泛用于算法和容器的自定义操作,因为它们不仅可以像普通函数一样调用,还可以携带状态和数据,从而提供更强的灵活性和功能性。
1. 仿函数的基本概念
仿函数是一个类或结构体的对象,该类或结构体重载了函数调用运算符 ()
。当我们创建一个仿函数的实例并使用 ()
调用它时,实际上是在调用该对象的 operator()
方法。
示例:简单的仿函数
#include <iostream>
struct Square {
int operator()(int x) const {
return x * x;
}
};
int main() {
Square square;
std::cout << square(5) << std::endl; // 输出 25
return 0;
}
在这个例子中,Square
是一个仿函数,它的 operator()
方法计算输入整数的平方。
2. 仿函数与普通函数的对比
普通函数是编译时无法携带状态的,而仿函数可以通过成员变量存储状态,从而在调用时使用这些状态。
示例:携带状态的仿函数
#include <iostream>
struct Adder {
int base;
Adder(int b) : base(b) {
}
int operator()(int x) const {
return base + x;
}
};
int main() {
Adder add5(5);
std::cout << add5(10) << std::endl; // 输出 15
return 0;
}
在这个例子中,Adder
仿函数通过 base
成员变量携带了一个状态 5
,并在每次调用时将其与输入值相加。
3. STL中的仿函数
STL 中的许多算法和容器都可以使用仿函数。例如,std::sort
可以通过仿函数自定义排序规则,std::for_each
可以使用仿函数执行特定操作。
示例:使用仿函数进行排序
#include <algorithm>
#include <iostream>
#include <vector>
struct Greater {
bool operator()(int a, int b) const {
return a > b;
}
};
int main() {
std::vector<int> vec = {
1, 5, 3, 2, 4};
std::sort(vec.begin(), vec.end(), Greater());
for (int v : vec) {
std::cout << v << " "; // 输出 5 4 3 2 1
}
return 0;
}
在这个例子中,Greater
仿函数用于指定排序时应使用大于关系,从而实现降序排序。
4. 标准库中的仿函数
C++ 标准库(尤其是在 <functional>
头文件中)提供了许多常用的仿函数。它们大多是用于常见操作的模板类。
-
算术仿函数:
std::plus
、std::minus
、std::multiplies
、std::divides
、std::modulus
、std::negate
。例如,
std::plus<int>()(3, 4)
返回 7,相当于3 + 4
。 -
关系仿函数:
std::equal_to
、std::not_equal_to
、std::greater
、std::less
、std::greater_equal
、std::less_equal
。例如,
std::greater<int>()(4, 3)
返回true
。 -
逻辑仿函数:
std::logical_and
、std::logical_or
、std::logical_not
。例如,
std::logical_and<bool>()(true, false)
返回false
。 -
位运算仿函数:
std::bit_and
、std::bit_or
、std::bit_xor
、std::bit_not
。
5. 绑定器和适配器
C++ 还提供了一些工具,可以将仿函数、函数指针或普通函数进行“绑定”或“适配”,从而改变其行为或简化调用。
-
std::bind
:可以将仿函数的某些参数绑定为固定值,从而创建新的仿函数。#include <iostream> #include <functional> int add(int a, int b) { return a + b; } int main() { auto add5 = std::bind(add, 5, std::placeholders::_1); // 将第一个参数固定为 5 std::cout << add5(3) << std::endl; // 输出 8 return 0; }
-
std::function
:是一个通用的函数包装器,可以保存任何可调用对象(包括仿函数、函数指针、lambda 表达式等)。#include <iostream> #include <functional> int main() { std::function<int(int, int)> func = [](int a, int b) { return a * b; }; std::cout << func(3, 4) << std::endl; // 输出 12 return 0; }
6. Lambda表达式与仿函数
在现代C++中,lambda表达式(匿名函数)提供了一种更简洁的方式来创建临时仿函数。Lambda 表达式常用于算法中进行自定义操作,替代传统的仿函数类。
示例:使用lambda替代仿函数
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {
1, 5, 3, 2, 4};
std::sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b; });
for (int v : vec) {
std::cout << v << " "; // 输出 5 4 3 2 1
}
return 0;
}
在这个例子中,lambda表达式 [](int a, int b) { return a > b; }
实现了与前面 Greater
仿函数相同的功能,但代码更简洁。
7. 自定义仿函数的用途
仿函数在STL中有很多应用场景:
- 自定义排序规则:通过仿函数定制排序算法中的排序逻辑。
- 自定义操作:在
std::for_each
等算法中,通过仿函数执行复杂的操作。 - 状态管理:通过仿函数的成员变量在算法中保存状态信息。
8. 总结
仿函数是C++中一个强大且灵活的特性,它结合了对象的状态和函数的行为,使得在STL中使用自定义操作变得更加方便。通过理解仿函数的概念和应用,程序员可以编写出更灵活、更高效的代码。
关键点总结:
- 仿函数是对象化的函数:它们通过重载
operator()
来实现函数调用的行为。 - 标准库提供了常用的仿函数:如算术、关系、逻辑、位运算等仿函数。
- 仿函数可以携带状态:这使得它们比普通函数更加灵活。
- Lambda 表达式是现代 C++ 中更简洁的仿函数实现方式。