1. 什么是容器适配器?
C++ 标准库提供了三种容器适配器:
-
stack—— 后进先出(LIFO,Last In First Out) -
queue—— 先进先出(FIFO,First In First Out) -
priority_queue—— 按优先级出队(本课最后会简单提及)
它们内部可以选用不同的底层容器(默认是 deque,但你也可以指定 vector 或 list),然后只暴露几个特定的接口,屏蔽了无关操作。
可以这样理解:stack 就像一摞盘子,只能动最上面那个;queue 就像排队,先来的先服务,从一端进另一端出。
2. 头文件和基本声明
#include <stack> // 使用 stack
#include <queue> // 使用 queue 和 priority_queue
声明一个栈和一个队列:
std::stack<int> st; // 存放 int 的栈,底层默认用 deque<int>
std::queue<std::string> q; // 存放 string 的队列,底层默认用 deque<std::string>
如果需要指定底层容器(通常没必要,除非有特殊性能需求):
std::stack<int, std::vector<int>> st_vec; // 栈,底层用 vector
std::queue<int, std::list<int>> q_list; // 队列,底层用 list
3. stack:后进先出
stack 可以理解为一个只开放了尾部操作的序列容器。
3.1 常用操作
| 函数 | 功能 |
|---|---|
push(val) | 将元素压入栈顶 |
emplace(args...) | 在栈顶原地构造元素,避免临时对象 |
pop() | 移除栈顶元素(不返回该元素) |
top() | 返回栈顶元素的引用(不删除) |
empty() | 判断栈是否为空 |
size() | 返回栈中元素个数 |
重要提醒:pop() 不返回值,这是因为为了保证异常安全,C++ 将“获取栈顶”和“移除栈顶”分成了两个操作。你必须先用 top() 取值,再 pop() 移除。
3.2 简单示例
#include <iostream>
#include <stack>
int main() {
std::stack<int> st;
// 压入元素
st.push(10);
st.push(20);
st.push(30);
std::cout << "栈大小:" << st.size() << "\n";
// 遍历栈?(stack 不支持迭代器,只能通过 top + pop 逐个取出)
std::cout << "从栈顶开始取出:";
while (!st.empty()) {
std::cout << st.top() << " "; // 先取栈顶
st.pop(); // 再移除
}
std::cout << "\n栈现在是空的吗?" << std::boolalpha << st.empty() << "\n";
return 0;
}
输出:
栈大小:3
从栈顶开始取出:30 20 10
栈现在是空的吗?true
可见最后进去的 30 最先出来,这就是 LIFO。
4. queue:先进先出
queue 像一个两端开口的通道,一端只进,另一端只出。
4.1 常用操作
| 函数 | 功能 |
|---|---|
push(val) | 在队尾添加元素 |
emplace(args...) | 在队尾原地构造元素 |
pop() | 移除队首元素(不返回该元素) |
front() | 返回队首元素的引用(不删除) |
back() | 返回队尾元素的引用 |
empty() | 判断队列是否为空 |
size() | 返回队列中元素个数 |
注意:queue 有 front 和 back,但没有 top;stack 有 top,没有 front/back。这是它们的核心区别。
4.2 简单示例
#include <iostream>
#include <queue>
int main() {
std::queue<std::string> q;
// 入队
q.push("张三");
q.push("李四");
q.push("王五");
std::cout << "队伍大小:" << q.size() << "\n";
std::cout << "队首:" << q.front() << ",队尾:" << q.back() << "\n";
// 出队处理
std::cout << "开始服务:";
while (!q.empty()) {
std::cout << q.front() << " "; // 服务当前队首
q.pop(); // 离开队伍
}
std::cout << "\n现在队伍空了吗?" << std::boolalpha << q.empty() << "\n";
return 0;
}
输出:
队伍大小:3
队首:张三,队尾:王五
开始服务:张三 李四 王五
现在队伍空了吗?true
先进来的“张三”先离开,后来的“王五”最后离开,这就是 FIFO。
5. stack 和 queue 的底层容器
默认情况下,stack 和 queue 都使用 std::deque 作为底层容器。deque 是一种双端队列,两端增删都是 O(1),且支持随机访问(虽然适配器不用它)。
为什么默认选 deque 而不是 vector 或 list?
-
deque在两端增删非常高效,不会像vector那样在尾部扩容时可能导致大搬迁(虽然vector尾插也是 O(1) 均摊,但有时代价较大),也不会像list那样占用过多指针内存且缓存不友好。 -
deque在大多数实现中会分块存储,综合性能均衡。
6. 不能做什么(常见误区)
-
直接遍历
stack和queue都没有迭代器,不能使用范围 for 循环,也不能用[]随机访问。如果你想遍历,只能一个个top/front后pop,这会改变容器内容。如果想保留元素,必须用一个副本:std::stack<int> temp = st; // 拷贝一份 while (!temp.empty()) { /* 访问 temp.top() 并 pop */ } -
pop()不返回值
一定要先top()或front()取到值,再pop()。写成一连串pop()只会删除元素,拿不到数据。 -
访问前不判断空
对一个空栈调用top()或pop()是未定义行为,等价于操作无效内存。一定先检查!empty()。 -
混淆
back()和front()
queue的back()是最后加入的元素(队尾),front()是最早加入的(队首)。不要用错。
7. 小结
| 场景 | 推荐容器 |
|---|---|
| 后进先出,如函数调用栈、括号匹配、撤销操作 | stack |
| 先进先出,如任务排队、消息缓冲、BFS 搜索 | queue |
| 需要两端插入删除,且随机访问 | deque(直接使用) |

1644

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



