函数对象是通过重载operator()实现的可调用对象,能携带状态,常用于STL算法中传递带上下文的行为。与普通函数和Lambda相比,它支持状态保持、类型封装和复用,适用于自定义比较器、谓词及策略模式等场景。

函数对象,简单来说,就是一个行为像函数的对象。它通过在一个类里重载了括号操作符
operator()
函数对象的核心,在于一个类实例能被“调用”。当你为一个类重载了
operator()
举个例子,假设我们想创建一个能将任何传入数字乘以某个固定因子的“函数”。如果用普通函数,这个因子就得作为参数每次传入。但用函数对象,我们可以这样:
#include <iostream>
// 这是一个函数对象类
class Multiplier {
private:
int factor; // 这是一个状态,每个Multiplier对象都有自己的factor
public:
// 构造函数,初始化状态
Multiplier(int f) : factor(f) {}
// 重载了operator(),让这个类的实例可以像函数一样被调用
int operator()(int val) const {
return val * factor;
}
};
int main() {
Multiplier multiplyBy5(5); // 创建一个函数对象,它的factor是5
Multiplier multiplyBy10(10); // 创建另一个函数对象,它的factor是10
std::cout << "5 * 10 = " << multiplyBy5(10) << std::endl; // 调用multiplyBy5对象
std::cout << "10 * 20 = " << multiplyBy10(20) << std::endl; // 调用multiplyBy10对象
// 你甚至可以直接在需要可调用对象的地方使用它
// std::vector<int> numbers = {1, 2, 3};
// std::transform(numbers.begin(), numbers.end(), numbers.begin(), multiplyBy5);
// 这样,numbers里的每个元素都会被乘以5
return 0;
}在这个
Multiplier
multiplyBy5
multiplyBy10
Multiplier
factor
在我看来,函数对象之所以重要,最核心的原因就是它能“记住”东西,也就是它有状态。普通函数是“纯粹”的,每次调用都是从零开始,没有任何上下文信息。但很多时候,我们的操作需要一些背景数据,或者需要在多次操作之间保持某种累积值。
试想一下,如果你要对一个容器里的所有元素进行某种操作,这个操作本身又依赖于一个外部变量。比如,你想把所有数字都加上一个用户输入的偏移量。如果用普通函数,你可能需要把这个偏移量作为额外的参数传递给算法,或者使用全局变量(这通常不是个好主主意)。但如果用函数对象,你可以把这个偏移量作为函数对象的成员变量,在构造时传入,之后每次调用
operator()
再者,函数对象提供了一种类型化的方式来封装行为。这意味着你可以把它们作为模板参数传递给泛型算法,或者存储在容器中,甚至通过继承实现多态行为(尽管现在有了
std::function
要理解函数对象的独特之处,我们得把它和另外两种可调用实体——普通函数和Lambda表达式——做个对比。
首先是普通函数。它们是代码块,有固定的签名(参数类型和返回类型),并且不持有任何状态(除非访问全局变量或静态变量,但这会引入其他问题)。它们是“纯”行为的封装。你可以通过函数指针来传递它们,但函数指针无法携带任何额外的上下文信息。例如:
void printNum(int n) { std::cout << n << std::endl; }然后是函数对象。就像我们上面讨论的,它们是类实例,通过重载
operator()
最后是Lambda表达式(C++11引入的)。说实话,Lambda表达式在很大程度上可以看作是编译器为我们“匿名地”生成了一个函数对象。它们提供了一种非常简洁的语法来定义一个临时的、匿名的可调用对象。Lambda可以通过“捕获列表”来捕获其所在作用域的变量,这本质上就是将这些变量作为其内部生成的函数对象的成员变量。
// Lambda表达式示例
int offset = 10;
auto addOffset = [offset](int val) { // 捕获了offset变量
return val + offset;
};
std::cout << "10 + offset = " << addOffset(10) << std::endl; // 输出 20可以看到,Lambda表达式做到了和函数对象类似的事情:携带状态(通过捕获列表),并像函数一样被调用。那么,有了Lambda,函数对象是不是就没用了?当然不是!
虽然Lambda在很多简单场景下更方便,但显式的函数对象类仍然有其价值:
std::function
所以,它们不是替代关系,而是互补关系。Lambda是写短小、即时可调用代码的利器,而函数对象则更适合封装复杂、需要复用或参与类型体系的行为。
在C++的实际开发中,函数对象无处不在,尤其是在标准库中。
一个非常经典的场景是STL算法的定制化。
std::sort
std::for_each
std::transform
std::sort
#include <vector>
#include <algorithm>
#include <iostream>
// 自定义比较器:按绝对值降序排列
struct AbsDescComparator {
bool operator()(int a, int b) const {
return std::abs(a) > std::abs(b);
}
};
int main() {
std::vector<int> nums = { -5, 2, -8, 1, 7 };
std::sort(nums.begin(), nums.end(), AbsDescComparator()); // 传入一个AbsDescComparator的实例
for (int n : nums) {
std::cout << n << " "; // 输出:-8 7 -5 2 1
}
std::cout << std::endl;
return 0;
}这里,
AbsDescComparator
另一个常见应用是自定义谓词(Predicate)。谓词是返回布尔值的可调用对象,常用于
std::find_if
std::remove_if
// 查找大于某个阈值的数字
class GreaterThan {
private:
int threshold;
public:
GreaterThan(int t) : threshold(t) {}
bool operator()(int val) const {
return val > threshold;
}
};
// ... 在代码中使用
// auto it = std::find_if(vec.begin(), vec.end(), GreaterThan(5));此外,函数对象还在策略模式中有所体现,不同的函数对象代表不同的算法策略。在一些老旧的C++代码库中,或者需要与C风格API交互时,函数对象也常被用作回调函数的封装,尽管现在
std::function
总的来说,每当你需要将一段“行为”作为参数传递,并且这段行为又需要携带一些上下文信息(状态)时,函数对象(或其现代形式Lambda)就是你的首选工具。它们让C++的泛型编程变得异常强大和灵活。
以上就是函数对象是什么概念 重载operator()的类实例的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号