
目录

前言:
上篇博客我们了解了STL中的list类,本篇博客我们继续对STL中的组件进行学习,这篇博客与前几次的不一样,我们知道STL中有六大组件,,前面我们所学习的都是其中的容器,今天我们要学习其中的适配器stack、queue,学习思路与前面的string和vector、list大致是一样的,第一步就是:是什么怎么用,第二步就是我们能不能自己实现一个简单的vector(即为了了解它的底层原理),经过这两步的学习之后,应对绝大多数的场景已经足够使用。stack、queue学起来是比较简单的,只是有一点是与前面是不同的,它们引入了一个适配器的概念,不过总体而言呢,还是相对比较简单,希望大家有所收获!
📕一、stack的介绍
std::stack 是 C++ 标准模板库提供的栈容器适配器,实现了后进先出(LIFO)的数据结构。它基于其他容器(如 deque 或 list)实现,提供受限的栈操作接口。
🌟 核心特性
-
后进先出(LIFO):最后压入的元素最先弹出
-
容器适配器:基于底层容器实现(默认
std::deque) -
受限接口:只暴露栈操作相关的方法
-
无迭代器:不支持元素遍历
📕二、stack的使用
📂 头文件与命名空间
#include <stack>
using namespace std; // 或显式使用 std::stack
类似于string与vector和list,我们学习使用主要针对的就是,构造与初始化、元素访问、修改操作、容量查询。由于stack与queue使用起来很简单,总体来说总共有下面这些接口。下面我们来一一介绍。
| 函数说明 | 接口说明 |
|---|---|
stack()(重点) | 构造空的栈 |
empty()(重点) | 检测 stack 是否为空 |
size()(重点) | 返回 stack 中元素的个数 |
top()(重点) | 返回栈顶元素的引用 |
push(val)(重点) | 将元素 val 压入 stack 中 |
pop()(重点) | 将 stack 中尾部的元素弹出 |
✨2.1 构造与初始化
std::stack<int> stk1; // 空栈(默认底层容器 deque)
std::stack<int, std::list<int>> stk2; // 指定底层容器为 list
std::deque<int> deq {1, 2, 3};
std::stack<int> stk3(deq); // 用已有 deque 初始化
deque是什么下面进行介绍
✨2.2 元素访问
stk1.push(10); // 压栈
int top_val = stk1.top(); // 访问栈顶元素(不弹出)
// 注意:空栈调用 top() 是未定义行为
✨2.3 修改操作
stk1.push(20); // 压入元素
stk1.pop(); // 弹出栈顶元素(不返回该元素)
// 组合操作:获取并弹出
int val = stk1.top();
stk1.pop();
✨2.4 容量查询
bool empty = stk1.empty(); // 判断栈是否为空
size_t size = stk1.size(); // 返回元素数量
✨2.5 关键特性与注意事
2.5.1. 底层容器
| 底层容器 | 特性 | 适用场景 |
|---|---|---|
std::deque(默认) | 内存分段连续,高效两端操作 | 通用场景 |
std::vector | 内存连续,随机访问快 | 需要连续内存时 |
std::list | 任意位置插入高效 | 大对象存储 |
// 使用 vector 作为底层容器
#include <vector>
std::stack<int, std::vector<int>> vec_stack;
2.5.2. 时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
push() | O(1) | 压栈 |
pop() | O(1) | 弹栈 |
top() | O(1) | 访问栈顶 |
size() | O(1) | 大小查询 |
2.5.3. 注意事项
-
空栈检查:调用
top()或pop()前必须检查empty() -
无清空方法:没有
clear(),需手动弹出while (!stk1.empty()) stk1.pop(); -
无迭代器:不能遍历栈内容(违反栈设计原则)
-
容器选择:大对象建议使用
list作为底层容器
学习了上面的知识可以练习下面几道题试试手:
📕三、stack的简单模拟实现
先看实现代码:
namespace hab
{
template<class T, class Container>//这就是适配器的原理就是用之前所学的容器进行转换
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
T& top()
{
return _con.back();
}
private:
Container _con;
};
}
适配器解析:
template<class T, class Container>
所谓适配器就是用一个模板参数,无论你用什么容器只要适合我,你传过来,编译器自动的去处理,所谓适合就是那些接口是适用的,仅此而已。
📕四、queue的介绍
std::queue 是 C++ 标准模板库提供的队列容器适配器,实现了先进先出(FIFO)的数据结构。基于底层容器(默认 deque)实现。
🌟 核心特性
-
先进先出(FIFO):最先插入的元素最先移除
-
容器适配器:基于底层容器实现(默认
std::deque) -
受限接口:只暴露队列操作相关的方法
-
无迭代器:不支持元素遍历
📕五、queue的使用
📂 头文件与命名空间
#include <queue> // 注意:与 stack 不同头文件
using namespace std; // 或显式使用 std::queue
与stack极其相似,主要使用:构造与初始化、元素访问、修改操作、容量查询。由于stack与queue使用起来很简单,总体来说总共有下面这些接口。下面我们来一一介绍。
| 函数声明 | 接口说明 |
|---|---|
queue()(重点) | 构造空的队列 |
empty()(重点) | 检测队列是否为空,是返回 true,否则返回 false |
size()(重点) | 返回队列中有效元素的个数 |
front()(重点) | 返回队头元素的引用 |
back()(重点) | 返回队尾元素的引用 |
push(val)(重点) | 在队尾将元素 val 入队列 |
pop()(重点) | 将队头元素出队列 |
✨5.1 构造与初始化
std::queue<int> que1; // 空队列(默认底层容器 deque)
std::queue<int, std::list<int>> que2; // 指定底层容器为 list
std::deque<int> deq {1, 2, 3};
std::queue<int> que3(deq); // 用已有 deque 初始化
✨5.2 元素访问
que1.push(10); // 入队
int front_val = que1.front(); // 访问队首元素
int back_val = que1.back(); // 访问队尾元素
✨5.3 修改操作
que1.push(20); // 入队(队尾)
que1.pop(); // 出队(队首,不返回元素)
✨5.4 容量查询
bool empty = que1.empty(); // 判断队列是否为空
size_t size = que1.size(); // 返回元素数量
✨5.5 关键特性与注意事
5.5.1. 底层容器要求
| 操作 | 所需容器方法 |
|---|---|
front() | front() |
back() | back() |
push() | push_back() |
pop() | pop_front() |
有效底层容器:
-
std::deque(默认) -
std::list -
❌
std::vector(缺少pop_front())
// 使用 list 作为底层容器
#include <list>
std::queue<int, std::list<int>> list_queue;
5.5.2. 时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
push() | O(1) | 入队 |
pop() | O(1) | 出队 |
front() | O(1) | 访问队首 |
back() | O(1) | 访问队尾 |
5.5.3. 注意事项
-
空队列检查:调用
front()、back()或pop()前必须检查empty() -
无清空方法:需手动弹出元素清空队列
while (!que1.empty()) que1.pop(); -
无中间访问:只能访问首尾元素
-
线程安全:STL 容器本身非线程安全,需外部同步
📕六、queue的简单模拟实现
namespace hab
{
template<class T, class Container>//在队列中实现的话用list,因为vector底层没有实现pop_front()这样的接口
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
private:
Container _con;
};
}
💎 stack 与 queue 对比总结
| 特性 | std::stack | std::queue |
|---|---|---|
| 数据结构 | LIFO(后进先出) | FIFO(先进先出) |
| 核心操作 | push(), pop(), top() | push(), pop(), front(), back() |
| 默认容器 | std::deque | std::deque |
| 访问范围 | 仅栈顶 | 队首和队尾 |
| 典型应用 | 递归/DFS/撤销操作 | BFS/任务队列/缓冲管理 |
| 头文件 | <stack> | <queue> |
📕七、deque(双端队列)(了解)
上面我们提到了stack与queue它们底层是通过适配器实现的,对于stack,vector、list都可以,对于queue只能用list,事实上,STL源码当中两者都没用,而是用了另外的一个容器deque,deque是一个既吸收了vector随机访问的优点,又吸收了list插入删除效率高的优点,可以说是一个集大成者,但是相信大家看到上面我们标注了了解,为什么?因为C++是一个极其注重效率的语言,虽然deque非常好,但是细分情况下,它是比不过vector和list的,例如随机访问,它的效率是不如vector的。大家可以通过下述代码进行试验:
void test_deque() { deque<int> d; vector<int> v; const int n = 100000; srand(time(0)); for (size_t i = 0; i < n; ++i) { int x = rand(); d.push_back(x); v.push_back(x); } size_t begin1 = clock(); sort(d.begin(), d.end()); size_t end1 = clock(); size_t begin2 = clock(); sort(v.begin(), v.end()); size_t end2 = clock(); cout << end1 - begin1 << endl; cout << end2 - begin2 << endl; }
std::deque(Double-Ended Queue)是 C++ 标准模板库提供的一种动态数组容器,支持在两端高效插入和删除元素。它结合了 vector 的随机访问能力和 list 的两端操作效率,是 STL 中功能最全面的序列容器之一。
🌟 核心特性
-
双端高效操作:在头尾插入/删除元素均为 O(1) 时间复杂度
-
随机访问:支持下标操作符
[]和at(),访问效率 O(1) -
分段连续存储:数据存储在多个固定大小的块中(指针数组管理)
-
动态扩容:自动管理内存,扩容时不需要复制所有元素
-
迭代器复杂:随机访问迭代器,但比
vector迭代器更复杂
✨7.1 基本操作与成员函数
- 构造与初始化
std::deque<int> dq1; // 空 deque
std::deque<int> dq2(5, 100); // 5个值为100的元素
std::deque<int> dq3 = {1, 2, 3}; // 初始化列表 (C++11)
std::deque<int> dq4(dq3.begin(), dq3.end()); // 迭代器范围构造
std::deque<int> dq5(dq4); // 拷贝构造
- 元素访问
// 随机访问
int first = dq3[0]; // 下标访问(无边界检查)
int second = dq3.at(1); // 带边界检查(越界抛异常)
int front = dq3.front(); // 首元素
int back = dq3.back(); // 尾元素
// 迭代器访问
auto it = dq3.begin() + 2; // 随机访问迭代器
int val = *it;
- 修改操作
// 两端操作
dq3.push_front(0); // 头部插入
dq3.push_back(4); // 尾部插入
dq3.pop_front(); // 删除头部
dq3.pop_back(); // 删除尾部
// 中间操作
auto pos = dq3.begin() + 2;
dq3.insert(pos, 10); // 指定位置插入(O(n))
dq3.erase(pos); // 删除指定位置(O(n))
// 批量操作
dq3.resize(10); // 调整大小
dq3.assign(5, 42); // 重新赋值
dq3.clear(); // 清空所有元素
- 容量查询
bool empty = dq3.empty(); // 是否为空
size_t size = dq3.size(); // 元素数量
size_t cap = dq3.max_size(); // 理论最大容量
// 注意:deque 没有 capacity() 和 reserve() 方法
✨7.2 关键特性与注意事项
7.2.1. 底层数据结构(分段数组)

分块存储:元素存储在多个固定大小的内存块中
映射表:使用指针数组(映射表)管理数据块
扩容机制:
添加元素时只需分配新块,无需移动现有元素
扩容成本低于
vector(不需要整体复制)可以发现,它的底层是通过一个中控数组(指针数组),来控制多个数组来实现的,也就是说指针数组里存放着这些数组块的地址,当要访问某个数据的时候先找到在哪个数组,在去数组里找到具体的数据。而且它的迭代器也是很复杂,如下面所述:
7.2.2. 时间复杂度对比
| 操作 | deque | vector | list |
|---|---|---|---|
| 头/尾插入删除 | O(1) | 尾O(1)/头O(n) | O(1) |
| 中间插入删除 | O(n) | O(n) | O(1) |
| 随机访问 | O(1) | O(1) | O(n) |
| 迭代器自增 | O(1) | O(1) | O(1) |
| 内存局部性 | 中等 | 优 | 差 |
7.2.3. 迭代器特性
-
随机访问迭代器:支持
it + n等操作 -
失效规则:
-
头尾插入:通常不失效(除非引起重映射)
-
中间插入:可能失效所有迭代器
-
删除操作:被删位置之后的迭代器失效
-
-
复杂结构:迭代器包含多个指针(当前块位置/块指针/映射表指针)

它的迭代器是通过四个指针来相互配合进行随机访问的,仅作了解即可。
✨7.3 deque 与其他容器对比
| 特性 | std::deque | std::vector | std::list |
|---|---|---|---|
| 存储结构 | 分段连续 | 单块连续 | 离散节点 |
| 随机访问 | O(1) | O(1) | O(n) |
| 头部插入 | O(1) | O(n) | O(1) |
| 尾部插入 | O(1) | O(1)* | O(1) |
| 中间插入 | O(n) | O(n) | O(1) |
| 迭代器失效 | 复杂(部分失效) | 频繁(扩容时全失效) | 稳定(仅删除元素失效) |
| 内存开销 | 中(指针+可能碎片) | 低(仅容量冗余) | 高(每个节点两指针) |
| 缓存友好 | 中 | 优 | 差 |
注:vector 尾部插入均摊 O(1),但扩容时成本高
std::deque 是 STL 中最灵活的序列容器,核心优势在于:
-
双端 O(1) 操作:完美实现队列和栈
-
高效随机访问:接近
vector的性能,注意是接近,但是相对于vector还是逊色点 -
智能扩容机制:避免
vector的整体复制问题
适用场景:
-
需要同时支持随机访问和双端操作的序列
-
滑动窗口算法
-
作为
stack和queue的底层容器 -
数据量较大且扩容成本敏感的应用
对于deque,大家知道它可以弥补vector、list的缺陷,是一个全能的容器,并且作为stack、queue底层适配器,简单了解其中的使用即可。
📕八、总结
本篇博客我们了解学习了stack、queue、deque。 std::stack 与 std::queue 皆为容器适配器,默认基于 deque,分别提供 LIFO 与 FIFO 语义。stack 仅暴露 push、pop、top、empty、size,复杂度全 O(1),底层可换 vector/list;queue 增 front/back,同理复杂度 O(1),底层需支持 pop_front。二者均不可遍历,仅接口差异。简单模拟:内部保存 Container _con,转调其 push_back/pop_back 或 pop_front 即可。deque 是双端队列,分段数组,头尾插删 O(1),随机访问 O(1),中间插删 O(n),迭代器失效规则复杂,比 vector 头插快,比 list 缓存友好,常用于栈、队列的默认底层。希望大家有所收获!


2054

被折叠的 条评论
为什么被折叠?



