
🎬 博主简介:

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

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

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


Stack 的核心是 “仅允许栈顶操作”,我们通过模板参数指定底层容器(默认使用 deque,兼顾效率与灵活性),封装其尾操作接口。
#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;)。#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;//默认是用的deque
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
return 0;
}
Queue 的核心是 “队尾入、队头出”,同样通过模板参数指定底层容器(默认 deque),封装其尾插、头删等接口。
#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)),所以我们一般不会去使用它来作为底层容器#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;
}类别 | vector | list |
|---|---|---|
访问特性 | 支持快速下标随机访问;CPU 高速缓存命中率高,数据访问效率优异 | 不支持快速下标随机访问;CPU 高速缓存命中率低,数据访问效率较差 |
增删特性 | 尾插、尾删操作效率极高;头部或中间位置插入/删除效率低下(时间复杂度 (O(N))),且插入可能触发扩容,存在性能损耗与空间浪费 | 任意位置插入/删除效率极高(时间复杂度 (O(1)));插入无需扩容,可按需申请与释放内存 |
排序性能 | 适合基于随机访问的排序算法(如快速排序),对数组形式的 vector,快速排序能高效利用其连续存储特性,缓存友好,排序速度快 | 因不支持随机访问,排序时需依赖指针遍历(如归并排序),无法高效利用 CPU 缓存,且指针操作额外开销大,排序速度慢于 vector 搭配快速排序的场景 |

大家对CPU高速缓存感兴趣的话可以看一下这篇文章: 与程序员相关的CPU缓存知识 | 酷 壳 - CoolShell
deque即双端队列,是 STL 中一种双开口的 “连续” 空间数据结构。其核心特性围绕 “双端操作高效” 和 “空间利用灵活” 展开,具体可概括为两点:
push_front/push_back)、删除(pop_front/pop_back)操作,且时间复杂度均为 O(1),无需像 vector 那样头操作时搬移大量元素,也无需像 list 那样维护节点间的指针关联。

buffer)和一个中控器(map) 组成 —— 中控器存储着各个缓冲区的地址,通过迭代器的特殊设计,将分段的缓冲区 “拼接” 成逻辑上的连续空间。

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

迭代器的 “衔接” 作用: deque 的迭代器是实现 “伪连续” 的关键,它内部包含四个核心成员:
cur:指向当前缓冲区中正在访问的元素。first:指向当前缓冲区的起始位置。last:指向当前缓冲区的末尾位置(不含有效元素)。node:指向中控器中当前缓冲区地址所在的节点。当迭代器从一个缓冲区的末尾(cur == last)移动到下一个元素时,会通过 node 找到下一个缓冲区的地址,再将 cur 指向新缓冲区的 first,从而实现 “跨缓冲区连续访问” 的效果。

底层的一些源码和满了之后头插尾插的逻辑:


类别 | 优点 | 缺点 |
|---|---|---|
头尾操作 | 头尾插入、删除效率都不错 | - |
数据访问 | CPU 高速缓存命中率不错,数据访问效率高 | 随机访问支持,但相比 vector 不够极致,大量 [] 访问场景下,vector 更优 |
中间操作 | - | 中间插入、删除效率低,时间复杂度为 (O(N)) |
适用场景:
局限场景:
实际使用案例:
#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();
// 拷贝到vector
vector<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++ 标准库的设计哲学,在实际开发中写出更优雅、高效的代码。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど