C++ 仿函数详解:让对象像函数一样调用

C++ 仿函数详解:让对象像函数一样调用

前言

在 C++ 中,仿函数(Functor) 是指重载了 operator() 的类或结构体的对象,它们的行为类似于普通函数,因此可以像函数一样被调用。仿函数在 STL 算法、回调机制、函数适配器等场景中有着广泛的应用。本文将深入探讨仿函数的概念、优点、使用方式,并结合具体示例进行详细解析。


1. 为什么需要仿函数?

在 C++ 中,我们可以用普通函数或 std::function(C++11 引入)来定义可调用对象,但仿函数相比之下有以下优势:

  • 状态存储:普通函数无法存储状态,而仿函数可以在对象内部维护状态,例如计数器、阈值等。
  • 性能优化:由于仿函数是类的实例,可以通过内联优化减少函数调用的开销。
  • 与 STL 兼容:STL 容器和算法广泛使用仿函数,如 std::sort() 可接受仿函数作为自定义排序规则。

2. 仿函数的基本用法

要定义一个仿函数,需要在类或结构体中重载 operator(),示例如下:

#include <iostream> // 定义仿函数类 struct Add { int operator()(int a, int b) { return a + b; } }; int main() { Add add; // 创建仿函数对象 std::cout << "3 + 5 = " << add(3, 5) << std::endl; // 像函数一样调用 return 0; } 

解析

  • operator() 使 Add 对象 add 变成可调用对象,类似于普通函数 add(3, 5)
  • operator() 可以接受参数,并返回计算结果。

3. 具有状态的仿函数

仿函数可以存储状态,使其在多个调用间保持数据。例如,创建一个计算调用次数的仿函数:

#include <iostream> class Counter { private: int count; public: Counter() : count(0) {} // 初始化计数器为 0 int operator()(int value) { count++; return count * value; // 使用 count 影响计算结果 } int getCount() const { return count; } }; int main() { Counter counter; std::cout << counter(10) << std::endl; // 第 1 次调用 std::cout << counter(10) << std::endl; // 第 2 次调用 std::cout << "调用次数:" << counter.getCount() << std::endl; return 0; } 

解析

  • count 作为成员变量存储状态,每次调用 operator() 都会递增 count
  • 这在 STL 算法、回调机制等场景非常有用。

4. STL 算法中的仿函数

STL 算法通常需要比较、变换、筛选等规则,这时候自定义仿函数特别有用。例如,自定义排序规则:

#include <iostream> #include <vector> #include <algorithm> // 自定义比较规则(降序) struct Compare { bool operator()(int a, int b) { return a > b; // 降序排序 } }; int main() { std::vector<int> vec = {5, 2, 8, 1, 3}; std::sort(vec.begin(), vec.end(), Compare()); // 传递仿函数对象 for (int num : vec) { std::cout << num << " "; } return 0; } 

解析

  • std::sort() 默认是升序排序,我们自定义 Compare 作为降序比较规则
  • std::sort(vec.begin(), vec.end(), Compare()); 传递了 Compare 类型的临时对象作为排序准则

5. STL 提供的标准仿函数

C++ STL 提供了一些标准仿函数,主要在 <functional> 头文件中,例如:

  • 算术运算仿函数std::plus<T>std::minus<T>std::multiplies<T>std::divides<T> 等。
  • 关系运算仿函数std::greater<T>std::less<T>std::equal_to<T> 等。
  • 逻辑运算仿函数std::logical_and<T>std::logical_or<T> 等。

示例:使用 std::greater<> 进行降序排序:

#include <iostream> #include <vector> #include <algorithm> #include <functional> // 包含标准仿函数 int main() { std::vector<int> vec = {5, 2, 8, 1, 3}; std::sort(vec.begin(), vec.end(), std::greater<int>()); // 使用标准仿函数降序排序 for (int num : vec) { std::cout << num << " "; } return 0; } 

解析

  • std::greater<int>() 作为 std::sort 的比较函数,与我们自己写的 Compare 作用类似。

6. Lambda 取代仿函数(C++11)

C++11 引入了 Lambda 表达式,使得代码更加简洁,许多仿函数的使用场景可以用 Lambda 代替。例如:

#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> vec = {5, 2, 8, 1, 3}; // 使用 Lambda 进行降序排序 std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; }); for (int num : vec) { std::cout << num << " "; } return 0; } 

为什么使用 Lambda?

  • 减少代码量:无需单独定义 struct 作为仿函数类。
  • 提高可读性:Lambda 直接在 std::sort() 处定义逻辑,代码更直观。

尽管 Lambda 更简洁,但仿函数在需要存储状态复用代码跨多个地方使用时仍然是很好的选择。


7. 总结

特性普通函数Lambda仿函数
是否可存储状态❌ 否⚠️ 仅限闭包捕获✅ 是
是否可复用✅ 是❌ 否(仅局部作用域)✅ 是
性能优化⚠️ 可能无法内联✅ 内联优化✅ 内联优化
适用场景一般计算简单的一次性逻辑STL、回调、复杂逻辑

什么时候选择仿函数?

  • 需要存储状态(例如计数器)。
  • 需要复用(多个地方使用相同逻辑)。
  • 需要STL 兼容性(如 std::sort())。
  • 需要高效优化(内联)。

仿函数是 C++ 语言中的重要概念,它使得对象可以像函数一样调用,并在 STL 算法、回调、状态存储等场景中发挥重要作用。虽然 C++11 引入的 Lambda 使代码更加简洁,但仿函数在某些特定场景(如 STL 和状态保持)下仍然不可替代。

如果你对 C++ STL、Lambda 或智能指针等话题感兴趣,可以查看相关的深入文章! 🚀

Read more

《算法闯关指南:优选算法--位运算》--34.判断字符是否唯一,35.丢失的数字

《算法闯关指南:优选算法--位运算》--34.判断字符是否唯一,35.丢失的数字

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 位运算基础前置知识 * 34. 判断字符是否唯一 * 解法(位图的思想): * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 35. 丢失的数字 * 解法(位运算): * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 结尾: 前言: 聚焦算法题实战,系统讲解三大核心板块:优选算法:剖析动态规划、二分法等高效策略,学会寻找“最优解”。 递归与回溯:掌握问题分解与状态回退,攻克组合、排列等难题。 贪心算法:

By Ne0inhk

PaddleOCR文本矫正与排序算法终极指南:从混乱到有序的完整教程

PaddleOCR文本矫正与排序算法终极指南:从混乱到有序的完整教程 【免费下载链接】PaddleOCRAwesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80+ languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices) 项目地址: https://gitcode.com/GitHub_Trending/pa/PaddleOCR 在OCR识别过程中,PaddleOCR通过智能的文本矫正技术和高效的排序算法,让歪扭的文字变端正,让混乱的顺序变清晰。本文将为新手用户全面解析PaddleOCR如何实现OCR文本矫正和PaddleOCR排序算法的完美结合。 🎯 为什么需要文本矫正与排序?

By Ne0inhk
LeetCode——滑动窗口(初阶)

LeetCode——滑动窗口(初阶)

文章目录 * 简要介绍 * 相关例题 * 长度最小的子数组 * 题目描述 * 题目分析 * 实现思路💡 * 实现代码 * 无重复字符的最长子串 * 题目描述 * 题目分析 * 实现思路💡 * 实现代码 * [最大连续1的个数 III](https://gitee.com/link?target=https://leetcode.cn/problems/max-consecutive-ones-iii/) * 题目描述 * 题目分析 * 实现思路💡 * 实现代码 * [将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) * 题目描述 * 题目描述 * 实现思路💡 * 实现代码 简要介绍 我们的滑动窗口算法是我们在笔试面试以及算法竞赛中都比较常见的一种算法,这个算法

By Ne0inhk
栈和队列--数据结构初阶(2)(C/C++)

栈和队列--数据结构初阶(2)(C/C++)

文章目录 * 前言 * 理论部分 * 栈的模拟实现 * STL中的栈容器 * 队列的模拟实现 * STL中的队列容器 * 作业部分 前言 这期的话会给大家讲解栈和队列的模拟实现和在STL中栈和队列怎么用的一些知识和习题部分(这部分侧重于理论知识,习题倒还是不难) 理论部分 栈的模拟实现 typedef int STDataType; typedef struct Stack { STDataType* a;//这里的a想表示的是数组 int top;//表示数组a当前的容量 int capacity; }ST; void STInit(ST* ps) { assert(ps); ps->a = (STDataType*)malloc(sizeof(STDataType) * 4); if (ps->a == NULL) { perror("

By Ne0inhk