当前位置: 首页 > news >正文

基于容器适配器模式的 Stack 与 Queue 实现:复用底层容器的优雅设计

在这里插入图片描述

🔥草莓熊Lotso:个人主页

❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》

✨生活是默默的坚持,毅力是永久的享受!

🎬 博主简介:

在这里插入图片描述


文章目录

  • 前言:
  • 一. 容器适配器的核心思想
  • 二. Stack 的适配器实现
    • 2.1 代码实现:
    • 2.2 测试接口
  • 三. Queue 的适配器实现
    • 3.1 代码实现
    • 3.2 测试接口
  • 四. 双端队列 deque:结构与适配逻辑
    • 4.1 vector与list的优缺点对比(附CPU高效缓存)
    • 4.2 deque 的本质与核心特性
    • 4.3 deque 底层结构:缓冲区与中控器的协作
    • 4.4 deque 的优缺点:适用场景与局限性
  • 结尾:


前言:

在 C++ 中,Stack(栈)和 Queue(队列)并非从零构建的容器,而是通过 “容器适配器” 模式实现 —— 即复用现有容器的接口,封装出符合自身规则的新数据结构。本文将参考标准库的设计思想,基于自定义底层容器适配,实现功能完整的 Stack 与 Queue,重点解适配器模式的核心逻辑。

在这里插入图片描述


一. 容器适配器的核心思想

适配器 是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口本质是接口转换:通过封装一个底层容器(如 deque、vector、list),屏蔽藏其部分接口,仅暴露符合自身数据访问规则的方法。例如:

  • Stack 需 “后进先出(LIFO)”,只需底层容器支持尾插(push_back)尾删(pop_back)取尾元素(back) 即可;
  • Queue 需 “先进先出(FIFO)”,只需底层容器支持尾插(push_back)头删(pop_front)取头元素(front)取尾元素(back) 即可。

在这里插入图片描述

这种设计的优势在于:无需重复实现内存管理逻辑,直接复用底层容器的成熟功能,同时保持接口的简洁性。
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,比如:
在这里插入图片描述
在这里插入图片描述


二. Stack 的适配器实现

Stack 的核心是 “仅允许栈顶操作”,我们通过模板参数指定底层容器(默认使用 deque,兼顾效率与灵活性),封装其尾操作接口。

2.1 代码实现:

#pragma once
#include<vector>
#include<list>
#include<deque>
using namespace std;
namespace Lotso
{// 模板参数:T为元素类型,Container为底层容器类型(默认deque<T>)template<class T, class Container = deque<T>>class stack{public:stack() {}void push(const T& x ){_con.push_back(x);}void pop(){_con.pop_back();}const T& top() {return _con.back();}size_t size() const{return _con.size();}bool empty() const{return _con.empty();}private:Container _con;};
}
  • 模板参数灵活性:用户可指定任意支持 push_back/pop_back/back 的容器作为底层(如 stack<int, vector<int>> st; 或 stack<string, list<string>> st;)。

2.2 测试接口

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include"stack.h"
using namespace std;int main()
{//Lotso::stack<int, vector<int>> st; // 数组实现//Lotso::stack<int, list<int>> st;   // 链表实现Lotso::stack<int> st;//默认是用的dequest.push(1);st.push(2);st.push(3);st.push(4);while (!st.empty()){cout << st.top() << " ";st.pop();}cout << endl;return 0;
}
  • 测试完成,输出结果符合预期。

在这里插入图片描述


三. Queue 的适配器实现

Queue 的核心是 “队尾入、队头出”,同样通过模板参数指定底层容器(默认 deque),封装其尾插、头删等接口。

3.1 代码实现

#pragma once
#include<list>
#include<deque>
using namespace std;
namespace Lotso
{// 模板参数:T为元素类型,Container为底层容器类型(默认deque<T>)template<class T, class Container = deque<T>>class queue{public:queue() {}void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}const T& front(){return _con.front();}const T& back(){return _con.back();}size_t size() const{return _con.size();}bool empty() const{return _con.empty();}private:Container _con;};
}
  • vector 作为底层容器(vector::pop_front 效率极低,时间复杂度 O (n)),所以我们一般不会去使用它来作为底层容器

3.2 测试接口

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include"queue.h"
using namespace std;int main()
{//Lotso::queue<int> q;//默认是用的deque//Lotso::queue<int, vector<int>> q; // ֧这个不用最好Lotso::queue<int, list<int>> q;//链表实现q.push(1);q.push(2);q.push(3);q.push(4);while (!q.empty()){cout << q.front() << " ";q.pop();}cout << endl;return 0;
}

四. 双端队列 deque:结构与适配逻辑

4.1 vector与list的优缺点对比(附CPU高效缓存)

类别vectorlist
访问特性支持快速下标随机访问;CPU 高速缓存命中率高,数据访问效率优异不支持快速下标随机访问;CPU 高速缓存命中率低,数据访问效率较差
增删特性尾插、尾删操作效率极高;头部或中间位置插入/删除效率低下(时间复杂度 (O(N))),且插入可能触发扩容,存在性能损耗与空间浪费任意位置插入/删除效率极高(时间复杂度 (O(1)));插入无需扩容,可按需申请与释放内存
排序性能适合基于随机访问的排序算法(如快速排序),对数组形式的 vector,快速排序能高效利用其连续存储特性,缓存友好,排序速度快因不支持随机访问,排序时需依赖指针遍历(如归并排序),无法高效利用 CPU 缓存,且指针操作额外开销大,排序速度慢于 vector 搭配快速排序的场景

在这里插入图片描述

  • 数组因为是连续的物理空间,在CPU高速缓存命中率这块就有优势一点。因为我们如果没命中时,将内存数据加载到缓存是连续的一段一起的,然后再访问。

大家对CPU高速缓存感兴趣的话可以看一下这篇文章:
与程序员相关的CPU缓存知识 | 酷 壳 - CoolShell

4.2 deque 的本质与核心特性

deque即双端队列,是 STL 中一种双开口的 “连续” 空间数据结构。其核心特性围绕 “双端操作高效” 和 “空间利用灵活” 展开,具体可概括为两点:

  • 双端操作效率高:支持在头部和尾部进行插入(push_front/push_back)、删除(pop_front/pop_back)操作,且时间复杂度均为 O(1),无需像 vector 那样头操作时搬移大量元素,也无需像 list 那样维护节点间的指针关联。
    在这里插入图片描述

  • “伪连续” 空间结构:表面上支持随机访问,给人 “连续空间” 的使用体验,但实际底层由一段段小的连续缓冲区(buffer)和一个中控器(map) 组成 —— 中控器存储着各个缓冲区的地址,通过迭代器的特殊设计,将分段的缓冲区 “拼接” 成逻辑上的连续空间。
    在这里插入图片描述

4.3 deque 底层结构:缓冲区与中控器的协作

deque 的底层结构可拆解为 “中控器 + 缓冲区” 两部分,二者配合实现 “伪连续” 特性,具体结构如下:

  1. 缓冲区(buffer)
  • 存储实际数据的最小单元,是固定大小的连续内存块(比如默认大小为 512 字节)。
  • 每个缓冲区独立存在,当数据装满一个缓冲区后,会新申请一个缓冲区,而非像 vector 那样扩容时整体迁移旧数据。
  1. 中控器(map)
  • 本质是一个动态数组,存储的是各个缓冲区的首地址。
  • 当缓冲区数量增加导致中控器装满时,会申请一个更大的中控器(类似 vector 扩容),但只需拷贝旧中控器中存储的 “缓冲区地址”,无需迁移缓冲区中的实际数据,效率远高于 vector 扩容。
    在这里插入图片描述

迭代器的 “衔接” 作用:
deque 的迭代器是实现 “伪连续” 的关键,它内部包含四个核心成员:

  • cur:指向当前缓冲区中正在访问的元素。
  • first:指向当前缓冲区的起始位置。
  • last:指向当前缓冲区的末尾位置(不含有效元素)。
  • node:指向中控器中当前缓冲区地址所在的节点。

当迭代器从一个缓冲区的末尾(cur == last)移动到下一个元素时,会通过 node 找到下一个缓冲区的地址,再将 cur 指向新缓冲区的 first,从而实现 “跨缓冲区连续访问” 的效果。
在这里插入图片描述
底层的一些源码和满了之后头插尾插的逻辑
在这里插入图片描述在这里插入图片描述

4.4 deque 的优缺点:适用场景与局限性

类别优点缺点
头尾操作头尾插入、删除效率都不错-
数据访问CPU 高速缓存命中率不错,数据访问效率高随机访问支持,但相比 vector 不够极致,大量 [] 访问场景下,vector 更优
中间操作-中间插入、删除效率低,时间复杂度为 (O(N))

适用场景

  • 适合只需头尾操作、无需频繁遍历的场景,因此成为 stack(仅尾操作)、queue(头尾操作)的默认底层容器,能 “扬长避短”。

局限场景

  • 需要频繁遍历(如 for 循环遍历所有元素)、或需要大量随机访问的场景 —— 此类场景优先选择 vector(连续空间,遍历 / 随机访问高效),若需频繁中间插入 / 删除则选择 list。

实际使用案例

#include<deque>int main()
{deque<int> dp;dp.push_back(1);dp.push_back(1);dp.push_back(1);dp.push_front(2);dp.push_front(3);dp.push_front(4);dp[0] += 10;for (auto e : dp){cout << e << " ";}cout << endl;return 0;
}

在这里插入图片描述

和vector的sort效率对比

#include<deque>
#include<vector>
void test_op1()
{srand(time(0));const int N = 1000000;deque<int> dq;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;v.push_back(e);dq.push_back(e);}int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();sort(dq.begin(), dq.end());int end2 = clock();printf("vector:%d\n", end1 - begin1);printf("deque:%d\n", end2 - begin2);
}
//
void test_op2()
{srand(time(0));const int N = 1000000;deque<int> dq1;deque<int> dq2;for (int i = 0; i < N; ++i){auto e = rand() + i;dq1.push_back(e);dq2.push_back(e);}int begin1 = clock();sort(dq1.begin(), dq1.end());int end1 = clock();int begin2 = clock();// 拷贝到vectorvector<int> v(dq2.begin(), dq2.end());sort(v.begin(), v.end());dq2.assign(v.begin(), v.end());int end2 = clock();printf("deque sort:%d\n", end1 - begin1);printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}int main()
{test_op1();test_op2();return 0;
}

在这里插入图片描述


结尾:

往期回顾:
《C++ Stack 与 Queue 完全使用指南:基础操作 + 经典场景 + 实战习题》
结语:本文实现的 Stack 与 Queue 严格遵循容器适配器模式,通过封装底层容器,以极少的代码实现了完整功能。这种设计不仅体现了 “复用” 的编程思想,更展示了如何通过接口抽象,将复杂的内存管理与数据结构规则解耦。理解适配器模式,能帮助我们更深入地掌握 C++ 标准库的设计哲学,在实际开发中写出更优雅、高效的代码。

✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど

在这里插入图片描述

http://www.dtcms.com/a/506623.html

相关文章:

  • Kafka面试精讲 Day 26:Kafka集群部署与配置
  • 73_基于深度学习的水面漂浮垃圾检测系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • 在JavaScript中,清除 Canvas 画布上的内容
  • 方便做简笔画的网站或软件公司人员管理系统
  • SQL之参数类型讲解
  • CTFSHOW—WEB5
  • UU远程深度测评:重构远程控制体验的“无套路”标杆
  • 提升 iOS 26 系统流畅度的实战指南,多工具组合监控
  • vue3:vue3 + elementplus + pinia实现js的XMLHttpRequest 下载功能。
  • 如何在macOS上免密登录阿里云ECS服务器
  • 把“天猫”装进 JVM:Java 关键词商品爬虫从 0 到 1(含完整可运行代码)
  • tar打包过滤指定目录指南
  • 塘厦镇住房规划建设局网站wordpress主题生成
  • 5-SpringCloud-服务链路追踪 Micrometer Tracing
  • 网站怎样做谷歌推广没有网站怎么做淘宝客
  • 【C/C++基本功】union联合体彻底详解
  • 万字 Apache ShardingSphere 完全指南:从分库分表到分布式数据库生态
  • WebPages PHP:深入理解与高效实践
  • 数据结构-滑动窗口三题
  • 做黑网站吗怎么建设营销型网站
  • 免费企业网站程序关注love石家庄公众号微信
  • 如何进行网站开发网站开发郑州
  • 东营建网站wordpress商城汉化主题
  • 合肥昱天建设有限公司网站2016手机网站制作规范
  • 网站制作 青岛seo工具下载
  • 做初中物理题目的网站photoshop 做网站logo
  • 网站开发技术项目邢台网站建设最新报价
  • 木地板企业网站模版网站空间到期怎么办
  • 佛山著名网站建设公司赣州瑞金网站建设
  • 免费模板下载网站推荐免费asp企业网站源码