std::stack 是 C++ STL 中典型的容器适配器(Container Adapter),它本身不实现底层存储逻辑,而是对已有顺序容器进行封装,对外提供严格的「后进先出(LIFO,Last In First Out)」语义。它是 STL 中最简单的适配器之一,也是面试中「容器适配器」考点的核心载体。
一、标准定义与基本结构
1. 头文件与模板声明
头文件:<stack>,位于 std 命名空间。
标准模板签名:
cpp
template <class T, class Container = std::deque<T>>
class stack;
T:存储元素的类型Container:底层存储容器,默认使用std::deque<T>,可手动指定为满足要求的顺序容器
2. 底层容器的准入条件
能作为 stack 底层的容器,必须支持以下尾部操作接口:
back():访问尾部元素push_back():尾部插入元素pop_back():尾部删除元素empty():判空size():获取元素个数
STL 中天然满足要求的顺序容器有 3 个:std::deque(默认)、std::vector、std::list。
3. 核心成员变量
标准库的绝大多数实现(如 libstdc++、MSVC STL)中,stack 只有一个受保护成员:底层容器对象,命名通常为 c。
cpp
template <class T, class Container>
class stack {
protected:
Container c; // 底层容器对象,所有栈操作都委托给它
public:
// 所有对外接口都通过调用 c 的对应方法实现
};
这就是「容器适配器」的本质:完全委托底层容器实现功能,自身只做接口裁剪与语义约束,是典型的适配器设计模式。
二、为什么默认底层容器是 std::deque?
这是 STL 设计的经典面试题。stack 只在尾部操作,理论上 vector、list 都能用,最终选择 deque 是综合性能、稳定性、内存特性的最优解。
1. 对比 vector:避免扩容性能抖动
- vector 尾插是「均摊 O(1)」,但容量不足时需要重新分配整块连续内存 + 拷贝所有元素,扩容过程有明显性能开销,且会造成内存峰值。
- deque 采用分段连续存储,扩容时只需要新增一个数据块,不需要移动已有元素,
push_back是稳定 O(1),无性能抖动,更符合栈的稳定操作预期。 - vector 需要大块连续内存,大栈场景下容易出现内存分配失败;deque 分段存储,内存利用率更高。
2. 对比 list:更高的缓存与空间效率
- list 每个节点都要额外存储前后指针,内存开销大;且节点地址不连续,缓存命中率极低。
- deque 的数据块是连续的,缓存局部性远优于 list;额外指针开销远小于 list。
- stack 只操作尾部,不需要 list 的中间插入能力,用 list 属于无意义的性能浪费。
3. 综合结论
deque 完美匹配栈的「仅尾部操作」特性:稳定 O(1) 的双端操作、无扩容抖动、内存开销低、缓存表现优秀,因此成为默认底层容器。
三、核心接口的源码级实现
stack 的所有接口都是对底层容器的直接调用,没有额外复杂逻辑,下面是核心函数的等价标准实现。
1. 构造与析构
cpp
// 默认构造:调用底层容器默认构造
stack() : c() {}
// 拷贝构造:拷贝整个底层容器
stack(const stack& other) : c(other.c) {}
// 从已有容器构造:直接拷贝容器内容
explicit stack(const Container& cont) : c(cont) {}
// 析构:自动调用底层容器析构,无需手动处理内存
~stack() = default;
注意:单参数的容器构造函数加了 explicit,禁止隐式类型转换,避免语义歧义。
2. 栈顶元素操作
cpp
// 压栈:尾部插入元素
void push(const T& value) {
c.push_back(value);
}
// 弹栈:尾部删除元素,无返回值
void pop() {
c.pop_back();
}
// 访问栈顶元素:返回尾部元素的引用
T& top() {
return c.back();
}
const T& top() const {
return c.back();
}
经典面试点:pop() 为什么不返回元素?
这是典型的异常安全设计 : 如果
pop()返回元素,需要在弹出后将元素拷贝给调用方;如果拷贝构造过程中抛出异常,元素已经从栈中移除,就会永久丢失。 拆分为top()(拿引用,无拷贝风险)+pop()(只删除,不拷贝),即使拷贝失败,元素仍然保留在栈中,不会丢失。
3. 容量与判空
cpp
bool empty() const {
return c.empty();
}
size_t size() const {
return c.size();
}
4. 比较运算符
stack 的比较直接委托给底层容器的比较,按字典序比较全部元素:
cpp
template <class T, class Container>
bool operator==(const stack<T, Container>& lhs, const stack<T, Container>& rhs) {
return lhs.c == rhs.c;
}
template <class T, class Container>
bool operator<(const stack<T, Container>& lhs, const stack<T, Container>& rhs) {
return lhs.c < rhs.c;
}
四、stack 的核心特性与限制
1. 严格的 LIFO 语义,接口极度精简
stack 刻意裁剪了底层容器的绝大多数接口:
- 不支持随机访问(无
operator[]、at()) - 不支持中间插入、删除
- 不支持遍历:没有迭代器(iterator) ,也没有
begin()/end()接口
如果一定要遍历,也不是没有办法。可以通过获取到底层container的引用对象,然后操作container的方式来实现遍历。例如:
cppauto tranverse = [](const std::stack<int, std::deque<int> >& st) { const std::deque<int>& ref_st = st._Get_container(); / / 获取容器的常引用对象 std::deque<int>::size_type size = ref_st.size(); std::cout << "container size =" << st.size() << std::endl; for (std::deque<int>::size_type idx = 0; idx < size; ++idx) { std::cout << ref_st[idx] << " ,"; } std::cout << std::endl; }; std::stack<int, std::deque<int> > st; for (int i = 0; i < 5; ++i) st.push(i); tranverse(st); st.pop(); tranverse(st);
- 只能访问栈顶元素,只能在栈顶插入/删除
这种设计的目的是强制使用者遵守栈的语义,避免误用。
2. 时间复杂度
所有操作的时间复杂度与底层容器对应操作一致:
push()/pop()/top()/empty()/size():默认 deque 下均为 O(1)- 析构、拷贝赋值为 O(n),属于容器生命周期操作
3. 迭代器失效
stack 不对外暴露迭代器,因此用户层面不存在迭代器失效问题;底层容器自身的迭代器失效规则仍然存在,但被适配器完全屏蔽。
五、自定义底层容器的用法
如果有特殊需求,可以手动指定底层容器,语法如下:
cpp
// 用 vector 做底层的栈
std::stack<int, std::vector<int>> vec_stack;
// 用 list 做底层的栈
std::stack<int, std::list<int>> list_stack;
适用场景
- 元素体积小、栈深度可控,追求极致缓存性能:可选用
vector - 需要频繁插入删除大对象,不希望移动元素:可选用
list - 绝大多数通用场景:默认
deque是最优解,无需修改
六、高频面试题总结
-
Q:std::stack 是容器吗?
A:不是,它是容器适配器,本身不实现存储,只封装底层容器、提供栈语义。
-
Q:stack 默认底层容器是什么?为什么选它?
A:默认是
std::deque。原因:尾部操作稳定 O(1)、无扩容抖动、内存开销低、缓存表现优于 list。 -
Q:pop() 为什么设计成 void 不返回值?
A:出于异常安全考虑。如果返回值时拷贝构造抛出异常,元素已弹出会永久丢失;拆分为 top() 读引用 + pop() 删除,保证异常安全。
-
Q:stack 支持迭代器吗?为什么?
A:不支持。栈的语义是「只能访问栈顶」,遍历不符合 LIFO 语义,因此刻意不提供迭代器接口。
-
Q:stack 的底层可以用哪些容器?需要满足什么条件?
A:支持 back()、push_back()、pop_back()、empty()、size() 的顺序容器都可以,STL 原生支持 deque、vector、list。