std::stack 默认后进先出,底层以deque实现,仅支持push/top/pop等LIFO操作;可显式指定vector或list为底层容器,性能与内存特性各异;自实现需严防边界错误与异常安全问题。

std::stack 默认就是后进先出,不用自己实现
标准库的 std::stack 本质是容器适配器,底层默认用 std::deque 实现,所有操作天然满足 LIFO(Last In, First Out):只有栈顶能被访问和修改,push() 往顶加,pop() 从顶删,top() 只读顶元素。
它不提供迭代器、不支持随机访问,也没法查看中间元素——这正是刻意设计的限制,不是缺陷。
-
push(x):插入到逻辑栈顶(实际是底层容器的尾部) -
top():返回栈顶引用,但不移除;调用前必须确保非空,否则行为未定义 -
pop():只删除栈顶,不返回值;不能对空栈调用,否则崩溃 - 没有
find、size虽然有,但不是 O(1) 均摊(deque的size()是 O(1),但换成list底层就不是)
换底层容器会影响性能和行为细节
你可以显式指定底层容器,比如用 std::vector 或 std::list 替代默认的 deque:
std::stack<int std::vector>> s1; // 底层 vector std::stack<int std::list>> s2; // 底层 list </int></int>
关键差异在内存布局和操作开销:
-
vector:push/pop平均 O(1),但可能触发扩容(少量额外拷贝);内存连续,缓存友好 -
list:每次push/pop都是严格 O(1),无扩容问题;但节点分散,缓存不友好 -
deque(默认):折中方案,分段连续,push/pop均摊 O(1),扩容开销比vector小
注意:std::stack 不暴露底层容器接口,所以即使你选了 vector,也不能调用 capacity() 或 reserve()。
手写简单栈时,别直接用 raw array 模拟 top 指针
如果真要自己实现(比如教学或嵌入式受限环境),常见错误是用裸数组 + 整数下标模拟栈顶,却忽略边界检查或初始化:
struct MyStack {
int data[100];
int top_idx = -1; // 初始为空,不是 0!
void push(int x) {
if (top_idx == 99) throw std::overflow_error("stack full");
data[++top_idx] = x; // 先自增再赋值
}
int top() {
if (top_idx == -1) throw std::underflow_error("stack empty");
return data[top_idx];
}
void pop() {
if (top_idx == -1) throw std::underflow_error("stack empty");
--top_idx;
}
};
容易踩的坑:
-
top_idx初始化为-1,不是0;否则push第一个元素会写到data[0],但pop后变成-1,下次push再++才到0,逻辑错乱 - 没检查溢出/下溢就操作数组 → 未定义行为,调试极难定位
- 没考虑异常安全:若
push中构造元素抛异常,top_idx已自增但数据未写入,栈状态损坏
调试时栈空/栈满报错,优先查 empty() 和 size() 调用时机
运行时报 std::out_of_range 或直接崩溃,大概率是忘了在 top() / pop() 前检查 empty():
- 错误写法:
if (s.size() > 0) s.pop();——size()返回size_type(通常是unsigned),和0比较没问题,但语义不如empty()清晰,且某些自定义容器size()开销大 - 正确习惯:
if (!s.empty()) { auto x = s.top(); s.pop(); }——empty()是 O(1),意图明确 - 更安全的做法:把
top()+pop()合并成一个带返回的try_pop()封装(需自己写),避免竞态或重复检查
真正麻烦的是多线程场景:empty() 返回 true 瞬间,另一线程 push 了,接着本线程仍按空栈处理就崩了。这种时候 std::stack 本身不提供线程安全,得靠外部锁或改用并发栈(如 tbb::concurrent_stack)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











