STL容器适配器详解:stack篇

1. 引言

什么是容器适配器?

STL 中,stackqueuepriority_queue 并不是独立的容器,而是容器适配器(container adapters)。

它们通过封装已有容器(如 dequevector),改变接口和行为,从而形成特定的数据结构。

通俗点说,它们是"行为转换器",比如:

stack → 后进先出(LIFO

queue → 先进先出(FIFO

priority_queue → 最大优先级优先出队

这篇文章主要讲述stack

为什么选择stack?

核心特性

后进先出 (LIFO):只允许在栈顶操作,适合需要"撤销机制"的场景

接口极简:仅 push, pop, top 等核心操作,无迭代器

默认基于 deque:也可适配 vectorlist 实现

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)
相关推荐
Echo``23 分钟前
4:点云处理—去噪、剪切、调平
c++·图像处理·人工智能·算法·机器学习·计算机视觉
muzi_liii30 分钟前
C++11
c++
易只轻松熊1 小时前
C++(1):整数常量
开发语言·c++
Dovis(誓平步青云)1 小时前
精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
c语言·开发语言·c++·笔记·算法·学习方法
孞㐑¥1 小时前
Linux之进程概念
linux·c++·经验分享·笔记
wen__xvn2 小时前
每日一题洛谷T534125 合数c++
开发语言·c++
wen__xvn3 小时前
每日一题洛谷P8615 [蓝桥杯 2014 国 C] 拼接平方数c++
c++·职场和发展·蓝桥杯
刘梓谦3 小时前
Qt获取CPU使用率及内存占用大小
开发语言·c++·qt
珊瑚里的鱼3 小时前
第八讲 | stack和queue的使用及其模拟实现
开发语言·c++·笔记·visualstudio·stl·学习方法·visual studio
yong15858553433 小时前
[SIGPIPE 错误] 一个 Linux socket 程序,没有任何报错打印直接退出程序
linux·服务器·网络·c++