1. 引言
什么是容器适配器?
在 STL
中,stack
、queue
和 priority_queue
并不是独立的容器,而是容器适配器(container adapters)。
它们通过封装已有容器(如 deque
、vector
),改变接口和行为,从而形成特定的数据结构。
通俗点说,它们是"行为转换器",比如:
stack
→ 后进先出(LIFO
)
queue
→ 先进先出(FIFO
)
priority_queue
→ 最大优先级优先出队
这篇文章主要讲述stack
。
为什么选择stack
?
核心特性
后进先出 (LIFO
):只允许在栈顶操作,适合需要"撤销机制"的场景
接口极简:仅 push
, pop
, top
等核心操作,无迭代器
默认基于 deque
:也可适配 vector
或 list
实现
O(1) 时间复杂度:所有操作均常数时间完成
2. stack
的底层实现
默认实现(基于deque
)
cpp
template<typename T, typename Container = std::deque<T>>
class stack {
protected:
Container c; // 底层容器
public:
void push(const T& x) { c.push_back(x); }
void pop() { c.pop_back(); }
T& top() { return c.back(); }
};
可选的底层容器
cpp
std::stack<int> s1; // 默认deque
std::stack<int, std::vector<int>> s2; // 改用vector(小心扩容失效)
std::stack<int, std::list<int>> s3; // 极少数场景需要
性能对比
操作 | deque 实现 | vector 实现 | list 实现 |
---|---|---|---|
push | 均摊 O(1) | 均摊 O(1) | O(1) |
pop | O(1) | O(1) | O(1) |
内存连续性 | 分段连续 | 完全连续 | 分散 |
3. stack
核心接口与经典用法
基础操作
cpp
std::stack<int> s;
s.push(1); // 栈: [1]
s.push(2); // 栈: [1, 2]
std::cout << s.top(); // 输出2
s.pop(); // 栈: [1]
典型应用场景
场景1:括号匹配检查
目标:判断一个字符串中的括号是否成对、嵌套正确。
cpp
#include<iostream>
#include<stack>
#include<string>
using namespace std;
bool isValidBrackets(const string& str) {
stack<char> stk;
for (char c : str) {
if (c == '(' || c == '{' || c == '[') { //遇到左括号
stk.push(c); //压栈操作
}else {
if (stk.empty())return false;
char top = stk.top();
if ((c == ')' && top != '(') ||
(c == '}' && top != '{') ||
(c == ']' && top != '[')) {
return false;
}
stk.pop(); //匹配成功,弹出左括号
}
}
return stk.empty(); //栈空,全部匹配
}
int main() {
string s1 = "{[()]}";
string s2 = "([)]";
string s3 = "{}()[]";
cout << isValidBrackets(s1) << endl; //输出:1 (true)
cout << isValidBrackets(s2) << endl; //输出:0 (false)
cout << isValidBrackets(s3) << endl; //输出:1 (true)
}
场景2:逆波兰表达式计算器
目标:给定一个后缀表达式(逆波兰表达式),计算其结果。
例如表达式2 1+3*表示(2+1)*3,结果是9
cpp
#include <stack>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
int evalRPN(const vector<string>& tokens) {
stack<int> stk;
for (const string& token : tokens) {
if (token == "+" || token == "-" ||token == "*" ||token == "/") {
int b = stk.top();
stk.pop();
int a = stk.top();
stk.pop();
if (token == "+")stk.push(a + b);
else if (token == "-")stk.push(a - b);
else if (token == "*")stk.push(a * b);
else stk.push(a / b);
}else {
stk.push(stoi(token));//将字符串转换为整数压入栈中
}
}
return stk.top();
}
int main() {
vector<string> tokens = { "2","1","+","3","*" };
cout << evalRPN(tokens) << endl; //输出: 9
}
场景3:线程安全的调用栈记录
作用:在多线程环境中追踪函数调用的顺序。
cpp
#include <iostream>
#include <stack>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include<chrono>
using namespace std;
//调用栈记录类
class CallStack {
private:
//每个线程独享一份调用栈,互不干扰
static thread_local stack<string> thread_stack;
public:
//进入函数时调用,压入函数名
void push(const string& func_name) {
thread_stack.push(func_name);
}
//离开函数时调用,弹出函数名
void pop() {
if (!thread_stack.empty()) {
thread_stack.pop();
}
}
//获取当前线程的调用栈信息(从顶到底)
vector<string> getTrace() {
vector<string> trace;
stack<string> temp = thread_stack; //拷贝副本,避免修改原栈
while (!temp.empty()) {
trace.push_back(temp.top()); //栈顶到栈底依次取出函数名
temp.pop();
}
return trace; //返回调用轨迹(从当前函数往上)
}
};
//定义线程局部的静态成员变量,每个线程一份
thread_local stack<string> CallStack::thread_stack;
//模拟业务逻辑函数 funcB
void funcB(CallStack& cs) {
cs.push("funcB"); //进入funcB,压栈
this_thread::sleep_for(chrono::milliseconds(100)); //模拟耗时操作
cs.pop(); //离开funcB,弹栈
}
//模拟业务逻辑函数 funcA
void funcA(CallStack& cs) {
cs.push("funcA");
funcB(cs);
cs.pop();
}
//控制多个线程同时输出时不打乱
mutex console_mutex;
//每个线程运行的函数
void worker(CallStack& cs, const string& name) {
cs.push(name); //将线程名称作为入口函数名压栈
funcA(cs);
//获取调用栈轨迹并输出
auto trace = cs.getTrace();
//加锁防止多线程输出交叉
lock_guard<mutex> lock(console_mutex);
cout << "Thread " << name << " Trace:";
for (const auto& s : trace) {
cout << s << "->";
}
cout << "END" << endl;
cs.pop(); //线程名出栈
}
int main() {
CallStack cs;
//启动线程
//ref将变量cs包装成一个引用传递给线程函数
thread t1(worker, ref(cs), "T1");
thread t2(worker, ref(cs), "T2");
thread t3(worker, ref(cs), "T3");
t1.join();
t2.join();
t3.join();
}
输出示例:

4.注意事项与自定义栈
陷阱预警
未检查空栈时调用top
/pop
cpp
std::stack<int> s;
// s.top(); // 未定义行为!
// s.pop(); // 崩溃风险
if (!s.empty()) s.pop(); // 正确写法
优化建议
预分配空间(仅vector
底层有效):
cpp
std::stack<int, std::vector<int>> vs;
vs.c.reserve(100); // 直接访问底层容器
自定义栈实现
cpp
#include <vector>
#include <mutex>
#include <stdexcept>
#include <iostream>
#include <string>
#include <thread>
using namespace std;
template<typename T>
class FastStack {
private:
vector<T> data; // 存储栈元素的底层容器
mutable mutex mtx; // 用于线程安全访问
public:
// 默认构造
FastStack() = default;
// 禁止拷贝构造和赋值(也可以自定义)
FastStack(const FastStack&) = delete;
FastStack& operator=(const FastStack&) = delete;
// 支持移动构造和赋值
FastStack(FastStack&&) = default;
FastStack& operator=(FastStack&&) = default;
// 入栈(支持完美转发)
template<typename U>
void push(U&& val) {
lock_guard<mutex> lock(mtx);
data.push_back(forward<U>(val));
}
// 出栈(返回栈顶元素并移除)
T pop() {
lock_guard<mutex> lock(mtx);
if (data.empty()) {
throw out_of_range("Pop from empty stack");
}
T val = move(data.back());
data.pop_back();
return val;
}
// 查看栈顶元素(不移除)
T top() {
lock_guard<mutex> lock(mtx);
if (data.empty()) {
throw out_of_range("Top from empty stack");
}
return data.back();
}
// 检查是否为空
bool empty() const {
lock_guard<mutex> lock(mtx);
return data.empty();
}
// 获取元素数量
size_t size() const {
lock_guard<mutex> lock(mtx);
return data.size();
}
// 清空栈
void clear() {
lock_guard<mutex> lock(mtx);
data.clear();
}
};
示例用法和测试用例
cpp
void testSingleThread() {
FastStack<int> stk;
stk.push(10);
stk.push(20);
stk.push(30);
cout << "栈顶元素: " << stk.top() << endl; // 30
cout << "出栈: " << stk.pop() << endl; // 30
cout << "当前大小: " << stk.size() <<endl; // 2
}
void testMultiThread() {
FastStack<string> stk;
auto worker = [&stk](const string& name) {
for (int i = 0; i < 5; ++i) {
stk.push(name + " #" + to_string(i));
}
};
thread t1(worker, "线程A");
thread t2(worker, "线程B");
t1.join();
t2.join();
cout << "\n所有元素(出栈顺序):\n";
while (!stk.empty()) {
cout << stk.pop() << endl;
}
}
int main() {
cout << "单线程测试:\n";
testSingleThread();
cout << "\n多线程测试:\n";
testMultiThread();
return 0;
}
输出结果:

5.如何选择适合容器适配器
选择 stack | 选择其他容器 |
---|---|
需要严格的LIFO逻辑 | 需要随机访问(用vector/deque) |
只需要最简单的push/pop操作 | 需要遍历中间元素(用deque) |
与递归/回溯算法配合 | 需要高频中间插入(用list) |