news 2026/1/29 16:27:31

一文搞懂 C++ 仿函数与适配器:从概念到实战代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文搞懂 C++ 仿函数与适配器:从概念到实战代码

如果你在学 C++ STL,可能会对 “仿函数”“适配器” 这两个词感到陌生 —— 明明有函数指针,为啥要搞仿函数?栈和队列看着像独立容器,怎么又和 “适配器” 挂钩了?

其实这两个概念的核心特别简单:仿函数是 “像函数的类”,解决灵活比较的问题;适配器是 “容器的转换器”,解决代码复用的问题。今天咱们用大白话 + 实战代码,把这两个知识点讲透,新手也能轻松跟上!

一、仿函数:让 “类” 像函数一样干活

先想个场景:你写了个堆调整函数AdjustDown,需要比较父节点和子节点的大小。如果今天要 “小堆”(父节点比子节点小),明天要 “大堆”(父节点比子节点大),难道要写两个几乎一样的函数吗?

仿函数就是为解决这个问题而生的 —— 它用类封装比较逻辑,但用起来和函数完全一样,还能通过模板实现 “泛型比较”。

1. 仿函数是什么?本质是 “重载了 () 的类”

仿函数(也叫函数对象)不是函数,而是一个重载了operator()的类。这个类的对象可以像函数一样被调用,比如com(a, b),本质是调用com.operator()(a, b)

举个最常用的例子:文档里的less仿函数(修正了原文档的语法错误,加了详细注释):

cpp

// 模板仿函数:实现“小于”比较逻辑 template <class T> // T是泛型,支持int、double等各种类型 class less { public: // 重载operator():接收两个const引用(避免拷贝,保证不修改参数) // 返回值是bool,表示t1是否小于t2 bool operator()(const T& t1, const T& t2) const { return t1 < t2; // 核心比较逻辑 } }; // 同理,还能实现“大于”仿函数 template <class T> class greater { public: bool operator()(const T& t1, const T& t2) const { return t1 > t2; } };

关键特点:

  • 用模板支持任意类型(int、string、自定义类都能用);
  • 比较逻辑封装在类里,想换逻辑只换仿函数类型,不用改核心代码;
  • 比函数指针更安全(编译期检查)、更灵活(可封装状态,比如带比较阈值)。

2. 仿函数怎么用?实战堆调整函数

拿文档里的AdjustDown(堆的向下调整)举例,看看仿函数如何替代 “硬编码比较”。

原来的硬编码方式(不灵活)

如果直接写_con[child] < _con[child+1],只能实现一种堆(比如小堆),要改大堆就得改代码:

cpp

void AdjustDown(int parent) { size_t child = parent * 2 + 1; // 左孩子 while (child < _con.size()) { // 硬编码“找更大的孩子”,想找更小的就得改“<”为“>” if (child + 1 < _con.size() && _con[child] < _con[child + 1]) { ++child; } // 硬编码“父节点小于孩子则交换” if (_con[parent] < _con[child]) { swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else { break; } } }
用仿函数的灵活方式(一键切换堆类型)

把比较逻辑交给仿函数,核心代码不变,换仿函数就换逻辑:

cpp

// 给AdjustDown加仿函数模板参数Compare,默认用less(小堆) template <class Compare = less<int>> void AdjustDown(int parent) { size_t child = parent * 2 + 1; Compare com; // 创建仿函数对象,像“函数工具”一样用 while (child < _con.size()) { // 用com(a,b)替代硬编码的“<”,比较逻辑由Compare决定 if (child + 1 < _con.size() && com(_con[child], _con[child + 1])) { ++child; // 找到“符合比较逻辑”的孩子(less找大的,greater找小的) } // 同样用com比较父和子 if (com(_con[parent], _con[child])) { swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else { break; } } } // 调用示例: vector<int> _con = {3,1,2,4}; AdjustDown<less<int>>(_con, 0); // 用less,调小堆 AdjustDown<greater<int>>(_con, 0); // 用greater,调大堆(不用改核心代码!)

3. 仿函数小结

  • 核心:用类封装逻辑,用对象模拟函数调用
  • 优势:泛型支持、逻辑可换、编译安全;
  • 常见场景:STL 算法(比如sort的第三个参数)、容器底层(堆、set/map 的比较)。

建议配一张图帮助理解:![仿函数工作流程](这里建议插入一张流程图,内容包括:1. 定义 Compare 仿函数(less/greater)→ 2. 创建 com 对象 → 3. 调用 com (a,b) 触发 operator () → 4. 返回比较结果。用箭头连接,标注每个步骤的作用。)

二、适配器:容器的 “电源转换器”,核心是代码复用

你家里的电源是 220V,但手机充电需要 5V,这时候就需要 “电源适配器”。C++ 里的适配器也是这个道理:基于已有的容器(比如 deque),封装一层接口,变成新的数据结构(比如栈、队列),不用重新写底层存储逻辑。

文档里的栈和队列,就是最典型的 “容器适配器”—— 它们的底层依赖deque(双端队列),自己只封装 “栈 / 队列专属接口”。

1. 适配器是什么?本质是 “接口封装 + 复用底层容器”

适配器不自己实现存储,而是 “借” 已有容器的底层结构(比如 deque 的动态数组),然后暴露符合自己特性的接口:

  • 栈(stack)是 “后进先出”(LIFO),只需要 “尾插、尾删、取尾元素”,所以复用 deque 的push_backpop_backback()
  • 队列(queue)是 “先进先出”(FIFO),只需要 “尾插、头删、取头 / 尾元素”,所以复用 deque 的push_backpop_frontfront()back()

这样做的好处:不用重复写底层扩容、内存管理代码,直接复用 deque 的成熟实现,减少 bug。

2. 实战代码:用 deque 实现栈(stack)适配器

下面是文档里的stack适配器代码(修正了语法错误,比如_con_con.size的笔误,加了注释):

cpp

#pragma once // 防止头文件重复包含 #include <deque> // 依赖deque作为底层容器 namespace practice { // 自定义命名空间,避免和STL冲突 // 模板参数:T是栈存储的元素类型,Con是底层容器(默认用deque<T>) template <class T, class Con = std::deque<T>> class stack { public: // 1. 压栈:复用deque的push_back(尾插) void push(const T& x) { _con.push_back(x); } // 2. 出栈:复用deque的pop_back(尾删) void pop() { // 注意:实际项目中应该先判断栈是否为空,避免崩溃 if (empty()) { throw std::runtime_error("stack is empty!"); } _con.pop_back(); } // 3. 获取栈顶元素:复用deque的back()(取尾元素) T& top() { if (empty()) { throw std::runtime_error("stack is empty!"); } return _con.back(); } // 4. 获取栈顶元素(const版本,供const对象调用) const T& top() const { if (empty()) { throw std::runtime_error("stack is empty!"); } return _con.back(); } // 5. 获取栈大小:复用deque的size() size_t size() const { return _con.size(); } // 6. 判断栈是否为空:复用deque的size() bool empty() const { return _con.empty(); // 比_con.size() == 0更高效 } private: Con _con; // 底层容器对象,所有操作都委托给它 }; }
调用示例:

cpp

#include "my_stack.h" #include <iostream> using namespace practice; int main() { stack<int> s; // 用默认底层容器deque<int> s.push(1); s.push(2); s.push(3); std::cout << "栈顶元素:" << s.top() << std::endl; // 输出3 std::cout << "栈大小:" << s.size() << std::endl; // 输出3 s.pop(); // 出栈3 std::cout << "出栈后栈顶:" << s.top() << std::endl; // 输出2 return 0; }

3. 实战代码:用 deque 实现队列(queue)适配器

和栈类似,队列适配器复用 deque 的接口,只暴露 “先进先出” 需要的功能(修正文档语法错误后):

cpp

#pragma once #include <deque> namespace practice { template <class T, class Con = std::deque<T>> class queue { public: // 1. 入队:尾插(复用deque的push_back) void push(const T& x) { _con.push_back(x); } // 2. 出队:头删(复用deque的pop_front) void pop() { if (empty()) { throw std::runtime_error("queue is empty!"); } _con.pop_front(); } // 3. 获取队头元素(复用deque的front()) T& front() { if (empty()) { throw std::runtime_error("queue is empty!"); } return _con.front(); } // 4. 获取队尾元素(复用deque的back()) T& back() { if (empty()) { throw std::runtime_error("queue is empty!"); } return _con.back(); } // 5. 队列大小和判空 size_t size() const { return _con.size(); } bool empty() const { return _con.empty(); } private: Con _con; // 底层容器,委托所有操作 }; }
调用示例:

cpp

#include "my_queue.h" #include <iostream> using namespace practice; int main() { queue<int> q; q.push(10); q.push(20); q.push(30); std::cout << "队头:" << q.front() << ",队尾:" << q.back() << std::endl; // 10, 30 q.pop(); // 出队10 std::cout << "出队后队头:" << q.front() << std::endl; // 20 return 0; }

4. 适配器小结

  • 核心:“借鸡生蛋”,复用已有容器的底层,封装新接口
  • 常见适配器:stack(栈)、queue(队列)、priority_queue(优先队列,底层是 vector);
  • 灵活点:可以指定底层容器,比如stack<int, vector<int>> s(用 vector 做栈的底层)。

建议配一张图帮助理解:![栈 / 队列适配器与 deque 的关系](这里建议插入一张对比图,左侧是 stack 的接口(push、pop、top),右侧是 queue 的接口(push、pop、front、back),中间指向底层 deque 的对应接口(push_back、pop_back、back、pop_front、front),用箭头标注 “复用关系”,比如 stack::push → deque::push_back。)

三、总结:仿函数与适配器的核心价值

概念本质解决的问题典型场景
仿函数重载 () 的类灵活切换比较 / 运算逻辑,避免硬编码sort 排序、堆调整、set 比较
适配器封装已有容器的接口复用底层代码,快速实现新数据结构stack 栈、queue 队列

简单来说:

  • 想让 “比较逻辑可换”,用仿函数
  • 想 “快速实现栈 / 队列”,不用自己写底层,用适配器

这两个概念是 STL 的设计精髓,理解它们不仅能帮你用好 STL,还能提升自己的 “代码复用” 和 “灵活设计” 能力。试着把上面的代码敲一遍,感受一下仿函数的灵活和适配器的便捷吧!

如果有疑问,欢迎在评论区留言讨论~

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

【工具分享】--编写POC之Wavely的使用

最近面试有时候会被问到是否编写过poc&#xff0c;由于我真正手写的经验其实有点不足所以第一次被问到时候是有点懵逼的&#xff0c;所以有了这篇poc总结&#xff08;当然啦&#xff0c;这是我回过头又复习了许多篇有关poc的文章并且结合了我自己常用的工具再输出的文章&#x…

作者头像 李华
网站建设 2026/1/25 3:50:37

CSGO财富导师成了全网通缉犯,整个群都在喊“砍他”

&#x1f4a5; 最近超多“导狗”人设彻底崩塌&#xff01; 被全网人肉通缉&#xff0c;几千人的会员群直接炸锅‼️ “报警&#xff01;砍他&#xff01;”骂声此起彼伏&#xff0c;那叫一个热闹… 这就是我一直不让你们听所谓“博主分析饰品走势”、不让跟风炒饰品的原因&…

作者头像 李华
网站建设 2026/1/27 18:37:07

无线网络仿真:蓝牙网络仿真_(3).蓝牙网络仿真环境搭建

蓝牙网络仿真环境搭建 在无线网络仿真中&#xff0c;蓝牙网络的仿真环境搭建是一个重要的步骤。本节将详细介绍如何搭建一个蓝牙网络仿真环境&#xff0c;包括选择合适的仿真工具、配置仿真参数、生成仿真场景以及验证仿真结果等关键步骤。通过本节的学习&#xff0c;您将能够掌…

作者头像 李华
网站建设 2026/1/27 15:06:58

STM32单片机分享:智能鱼缸系统

目录 一、项目成品图片 二、项目功能简介 1.主要器件组成 2.功能详解介绍 三、项目原理图设计 四、项目PCB硬件设计 项目PCB图 五、项目程序设计 六、项目实验效果 ​编辑 七、项目包含内容 一、项目成品图片 哔哩哔哩视频链接&#xff1a; https://www.bilibili.c…

作者头像 李华
网站建设 2026/1/29 8:40:42

阿里Java面试被问:WebSocket的心跳检测和自动重连实现

一、核心概念与必要性 为什么需要心跳检测和重连&#xff1f; 网络不稳定&#xff1a;移动网络、Wi-Fi切换、代理服务器可能导致连接断开 服务器限制&#xff1a;Nginx/负载均衡器默认30-60秒超时 浏览器限制&#xff1a;部分浏览器标签页休眠时暂停WebSocket 资源清理&…

作者头像 李华