news 2026/5/17 0:42:23

【c++面向对象编程】第25篇:仿函数(函数对象):重载operator()

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【c++面向对象编程】第25篇:仿函数(函数对象):重载operator()

目录

一、什么是仿函数?

为什么叫“仿函数”?

二、仿函数相比普通函数的优势

1. 可以保存状态

2. 可以内联,性能更好

3. 可以作为类型使用,方便模板传参

三、仿函数在STL中的典型应用

示例1:sort自定义排序

示例2:find_if查找满足条件的元素

示例3:for_each统计和收集

四、仿函数与lambda表达式的关系

一个lambda的例子

编译器把上面的lambda转换成类似这样的仿函数

带捕获的lambda

五、仿函数 vs 函数指针 vs lambda

对比示例

六、STL中预定义的仿函数

七、常见错误

1. 忘记包含

2. 混淆 obj() 和 obj

3. 在需要可调用对象的地方传了对象而不是临时实例

4. 忘记处理const正确性

八、这一篇的收获


一、什么是仿函数?

先看一个最简单的例子:

cpp

class Adder { public: int operator()(int a, int b) const { return a + b; } }; int main() { Adder add; int result = add(3, 5); // 像函数一样调用 cout << result; // 输出 8 }

add(3, 5)看起来像函数调用,实际上调用的是add.operator()(3, 5)。这就是仿函数——行为像函数的对象

为什么叫“仿函数”?

  • “函数”是指普通的函数

  • “仿函数”是模仿函数行为的对象

  • 更专业的名字:函数对象(Function Object)


二、仿函数相比普通函数的优势

1. 可以保存状态

普通函数是“无记忆”的。仿函数可以拥有成员变量,记录调用历史:

cpp

class Counter { private: int count; public: Counter() : count(0) {} void operator()() { count++; cout << "被调用了 " << count << " 次" << endl; } }; int main() { Counter c; c(); // 被调用了 1 次 c(); // 被调用了 2 次 c(); // 被调用了 3 次 }

2. 可以内联,性能更好

编译器更容易对仿函数进行内联优化,而函数指针通常难以内联。

3. 可以作为类型使用,方便模板传参

仿函数的类型是唯一的,可以作为模板参数传递,而函数指针的类型只取决于签名。


三、仿函数在STL中的典型应用

STL算法大量使用仿函数来定制行为。

示例1:sort自定义排序

cpp

#include <iostream> #include <vector> #include <algorithm> using namespace std; // 仿函数:降序排序 class Descend { public: bool operator()(int a, int b) const { return a > b; // a大于b时返回true } }; // 仿函数:按绝对值排序 class AbsCompare { public: bool operator()(int a, int b) const { return abs(a) < abs(b); } }; int main() { vector<int> v = {3, -5, 1, -2, 4}; // 使用仿函数进行降序排序 sort(v.begin(), v.end(), Descend()); cout << "降序: "; for (int x : v) cout << x << " "; // 5 4 3 1 -2 cout << endl; // 使用仿函数按绝对值排序 sort(v.begin(), v.end(), AbsCompare()); cout << "按绝对值: "; for (int x : v) cout << x << " "; // 1 -2 3 4 -5 cout << endl; return 0; }

示例2:find_if查找满足条件的元素

cpp

#include <iostream> #include <vector> #include <algorithm> using namespace std; class IsEven { public: bool operator()(int n) const { return n % 2 == 0; } }; class Between { private: int low, high; public: Between(int l, int h) : low(l), high(h) {} bool operator()(int n) const { return n >= low && n <= high; } }; int main() { vector<int> v = {1, 3, 5, 6, 7, 9, 10, 11}; // 查找第一个偶数 auto it1 = find_if(v.begin(), v.end(), IsEven()); if (it1 != v.end()) { cout << "第一个偶数: " << *it1 << endl; // 6 } // 查找第一个在[5,9]范围内的数 Between b(5, 9); auto it2 = find_if(v.begin(), v.end(), b); if (it2 != v.end()) { cout << "第一个在[5,9]的数: " << *it2 << endl; // 5 } // 查找第一个大于10的数(临时构造仿函数对象) struct { bool operator()(int n) const { return n > 10; } } greaterThan10; auto it3 = find_if(v.begin(), v.end(), greaterThan10); if (it3 != v.end()) { cout << "第一个大于10的数: " << *it3 << endl; // 11 } return 0; }

示例3:for_each统计和收集

cpp

#include <iostream> #include <vector> #include <algorithm> using namespace std; class Accumulator { private: int sum; int count; public: Accumulator() : sum(0), count(0) {} void operator()(int x) { sum += x; count++; } int getSum() const { return sum; } double getAverage() const { return count > 0 ? (double)sum / count : 0; } }; int main() { vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Accumulator acc = for_each(v.begin(), v.end(), Accumulator()); cout << "总和: " << acc.getSum() << endl; // 55 cout << "平均值: " << acc.getAverage() << endl; // 5.5 return 0; }

四、仿函数与lambda表达式的关系

C++11引入了lambda表达式,它本质上就是编译器自动生成一个仿函数类

一个lambda的例子

cpp

auto add = [](int a, int b) { return a + b; }; int x = add(3, 5); // 8

编译器把上面的lambda转换成类似这样的仿函数

cpp

class __Lambda_12345 { // 编译器生成一个唯一的名字 public: auto operator()(int a, int b) const { return a + b; } }; __Lambda_12345 add; int x = add(3, 5); // 8

带捕获的lambda

cpp

int multiplier = 3; auto times = [multiplier](int x) { return x * multiplier; }; cout << times(5); // 15

编译器的展开(简化版):

cpp

class __Lambda_67890 { private: int multiplier; // 捕获的变量成为成员 public: __Lambda_67890(int m) : multiplier(m) {} auto operator()(int x) const { return x * multiplier; } }; int multiplier = 3; __Lambda_67890 times(multiplier); cout << times(5); // 15

结论:lambda是语法糖,底层就是仿函数。理解仿函数就能理解lambda的本质。


五、仿函数 vs 函数指针 vs lambda

特性函数指针仿函数lambda(C++11起)
能否保存状态✅(通过捕获)
语法简洁度一般繁琐✅ 最简洁
内联优化❌ 难以内联✅ 容易内联✅ 容易内联
类型安全签名级别✅ 每个仿函数唯一类型✅ 每个lambda唯一类型
使用场景C风格回调复杂定制/需要状态大多数现代C++场景

对比示例

cpp

#include <iostream> #include <vector> #include <algorithm> using namespace std; // 1. 普通函数 bool isOdd(int n) { return n % 2 == 1; } // 2. 仿函数类 class IsGreaterThan { int threshold; public: IsGreaterThan(int t) : threshold(t) {} bool operator()(int n) const { return n > threshold; } }; // 3. 泛型仿函数(模板) template<typename T> class IsInRange { T low, high; public: IsInRange(T l, T h) : low(l), high(h) {} bool operator()(T val) const { return val >= low && val <= high; } }; int main() { vector<int> v = {1, 5, 8, 12, 15, 20}; // 方式1:函数指针 auto it1 = find_if(v.begin(), v.end(), isOdd); cout << "第一个奇数: " << *it1 << endl; // 方式2:仿函数 IsGreaterThan gt(10); auto it2 = find_if(v.begin(), v.end(), gt); cout << "第一个大于10的数: " << *it2 << endl; // 方式3:泛型仿函数 IsInRange<int> range(5, 15); auto it3 = find_if(v.begin(), v.end(), range); cout << "第一个在[5,15]的数: " << *it3 << endl; // 方式4:lambda(现代C++首选) auto it4 = find_if(v.begin(), v.end(), [](int n) { return n > 5 && n < 15; }); cout << "第一个在(5,15)的数: " << *it4 << endl; // lambda也能保存状态 int target = 12; auto it5 = find_if(v.begin(), v.end(), [target](int n) { return n == target; }); if (it5 != v.end()) { cout << "找到目标值: " << *it5 << endl; } return 0; }

六、STL中预定义的仿函数

C++标准库提供了常用的仿函数,在<functional>头文件中:

分类仿函数作用
算术plus<T>minus<T>multiplies<T>divides<T>modulus<T>加减乘除取模
比较equal_to<T>not_equal_to<T>greater<T>less<T>各种比较
逻辑logical_and<T>logical_or<T>logical_not<T>与或非

cpp

#include <iostream> #include <functional> #include <algorithm> #include <vector> using namespace std; int main() { vector<int> v = {5, 2, 8, 1, 9, 3}; // 使用 greater<int>() 降序排序 sort(v.begin(), v.end(), greater<int>()); cout << "降序: "; for (int x : v) cout << x << " "; // 9 8 5 3 2 1 cout << endl; // 使用 less<int>() 升序排序(默认) sort(v.begin(), v.end(), less<int>()); cout << "升序: "; for (int x : v) cout << x << " "; // 1 2 3 5 8 9 cout << endl; // 使用 plus 做累加 vector<int> a = {1, 2, 3}; vector<int> b = {4, 5, 6}; vector<int> result(3); transform(a.begin(), a.end(), b.begin(), result.begin(), plus<int>()); cout << "逐元素相加: "; for (int x : result) cout << x << " "; // 5 7 9 cout << endl; return 0; }

七、常见错误

1. 忘记包含<functional>

使用std::plus等预定义仿函数时需要包含<functional>

2. 混淆obj()obj

cpp

MyFunctor f; f(); // 调用operator() f; // 这是对象本身,不是调用

3. 在需要可调用对象的地方传了对象而不是临时实例

cpp

sort(v.begin(), v.end(), Descend); // ❌ 传了类型,不是对象 sort(v.begin(), v.end(), Descend()); // ✅ 传了临时对象

4. 忘记处理const正确性

如果operator()不修改成员,应该声明为const,否则不能用于const场景。

cpp

class Bad { public: void operator()() { } // 不是const }; class Good { public: void operator()() const { } // const };

八、这一篇的收获

你现在应该理解:

  • 仿函数:重载operator()的类,对象可以像函数一样调用

  • 优势:可以保存状态、可内联性能好、类型唯一适合模板

  • STL应用sortfind_iffor_each等都广泛使用仿函数

  • lambda本质:编译器生成的匿名仿函数,是语法糖

  • 预定义仿函数<functional>中的greaterlessplus

💡 小作业:写一个仿函数类Filter,构造函数接收一个vector<int> thresholdoperator()接收一个整数并判断它是否在threshold中(多次查找)。用find_if找到第一个在threshold中的元素。


下一篇预告:第26篇《对象的内存模型:成员变量与成员函数的存储分离》——对象的大小由什么决定?成员函数存在哪里?static成员和虚函数vptr放在哪?理解内存布局是写出高效C++的基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/17 0:36:28

基于CircuitPython与ItsyBitsy M4打造可编程宏键盘:从硬件到代码全解析

1. 项目概述&#xff1a;打造你的专属输入利器 在键盘这个看似成熟的领域里&#xff0c;我们真的满足于厂商提供的“标准答案”吗&#xff1f;对于视频剪辑师、程序员、设计师或者硬核游戏玩家来说&#xff0c;一套固定的键位布局和功能&#xff0c;往往意味着效率的妥协。真正…

作者头像 李华
网站建设 2026/5/17 0:36:19

ag402工具库:模块化、函数式与TypeScript优先的现代开发实践

1. 项目概述&#xff1a;一个面向开发者的核心工具库最近在整理自己的技术栈时&#xff0c;发现很多项目里重复造轮子的情况依然严重。尤其是在处理一些基础但关键的开发任务时&#xff0c;比如数据验证、异步流程控制、或者是一些特定格式的解析&#xff0c;每次都要从零开始写…

作者头像 李华
网站建设 2026/5/17 0:24:17

基于Adafruit HalloWing的互动魔法书:嵌入式硬件与手工艺术的融合

1. 项目概述&#xff1a;当魔法书睁开“眼睛”如果你和我一样&#xff0c;对那种能眨眼、会转动眼珠、仿佛有生命的“魔法道具”毫无抵抗力&#xff0c;那么这个项目绝对会让你兴奋。我们这次要做的&#xff0c;不是简单的LED闪烁&#xff0c;而是一本真正拥有“灵魂之窗”的魔…

作者头像 李华
网站建设 2026/5/17 0:20:28

ItsyBitsy 32u4开发板实战指南:从引脚解析到USB HID应用

1. 项目概述&#xff1a;为什么选择 ItsyBitsy 32u4&#xff1f;在嵌入式开发的世界里&#xff0c;我们常常面临一个经典的矛盾&#xff1a;功能强大与体积小巧难以兼得。当你用 Arduino Uno 在面包板上搭建出一个功能完善的原型后&#xff0c;下一步往往是想办法把它塞进一个更…

作者头像 李华
网站建设 2026/5/17 0:18:28

WarcraftHelper终极指南:彻底解决魔兽争霸3现代系统兼容性问题

WarcraftHelper终极指南&#xff1a;彻底解决魔兽争霸3现代系统兼容性问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为经典游戏《魔兽争霸I…

作者头像 李华
网站建设 2026/5/17 0:17:17

别再只会`cmatrix`了!解锁Linux终端屏保的10种炫酷玩法(含快捷键大全)

终端美学革命&#xff1a;10种cmatrix高阶玩法与快捷键全解析 当绿色代码雨第一次在终端流淌而下时&#xff0c;那种黑客帝国般的视觉冲击令人难忘。但你是否知道&#xff0c;这个看似简单的cmatrix命令背后隐藏着一个可编程的视觉艺术工具箱&#xff1f;本文将带你突破基础用法…

作者头像 李华