在C++标准库中,stack和queue被归类为容器适配器(Container Adapters),这是一种经典的设计模式应用。容器适配器的本质是通过封装已有的容器,提供新的接口和行为模式。这种设计有三大核心优势:
设计提示:选择底层容器时需要考虑操作的时间复杂度。例如用vector作为stack底层时,push_back()是均摊O(1),但可能触发扩容;而deque则能保证严格的O(1)时间复杂度。
stack默认使用deque作为底层容器,这是经过精心权衡的结果:
cpp复制template<class T, class Container = deque<T>>
class stack;
三种主要底层容器的对比:
| 容器类型 | 随机访问 | 中间插入 | 头部操作 | 内存分配 |
|---|---|---|---|---|
| vector | O(1) | O(n) | O(n) | 单块连续 |
| deque | O(1) | O(n) | O(1) | 分块连续 |
| list | O(n) | O(1) | O(1) | 离散分配 |
实际工程中选择建议:
cpp复制void push(const value_type& value) {
c.push_back(value); // 实际调用底层容器的push_back
}
cpp复制void pop() {
c.pop_back(); // 必须保证栈非空
}
关键陷阱:pop()操作前必须检查empty(),否则是未定义行为。这是STL设计哲学决定的——不提供运行时检查以保证最高性能。
cpp复制#include <stack>
#include <vector>
#include <iostream>
void processStack() {
// 使用vector作为底层容器
std::stack<int, std::vector<int>> s;
// 压栈操作
for(int i = 0; i < 10; ++i) {
s.push(i * 2); // 0,2,4...18入栈
}
// 遍历栈的标准方法
while(!s.empty()) {
std::cout << "Top: " << s.top() << "\n";
s.pop(); // 必须按顺序弹出
}
// 典型错误示例:
// std::cout << s.top(); // 危险!此时栈已空
}
queue的默认底层容器同样是deque:
cpp复制template<class T, class Container = deque<T>>
class queue;
选择deque的核心原因:
cpp复制void push(const value_type& value) {
c.push_back(value); // 在容器尾部插入
}
cpp复制void pop() {
c.pop_front(); // 必须从头部删除
}
cpp复制#include <queue>
#include <list>
void processQueue() {
// 使用list作为底层容器
std::queue<int, std::list<int>> q;
// 入队操作
q.push(10);
q.push(20);
q.push(30);
// 获取队列信息
std::cout << "Front: " << q.front() << "\n"; // 10
std::cout << "Back: " << q.back() << "\n"; // 30
std::cout << "Size: " << q.size() << "\n"; // 3
// 出队操作
q.pop(); // 移除10
std::cout << "New Front: " << q.front() << "\n"; // 20
}
cpp复制template<typename T, typename Container = std::deque<T>>
class MyStack {
public:
using value_type = typename Container::value_type;
// 栈操作接口
void push(const value_type& val) { c.push_back(val); }
void pop() { c.pop_back(); }
value_type& top() { return c.back(); }
bool empty() const { return c.empty(); }
size_t size() const { return c.size(); }
private:
Container c; // 底层容器
};
cpp复制template<typename T, typename Container = std::deque<T>>
class MyQueue {
public:
using value_type = typename Container::value_type;
// 队列操作接口
void push(const value_type& val) { c.push_back(val); }
void pop() { c.pop_front(); }
value_type& front() { return c.front(); }
value_type& back() { return c.back(); }
bool empty() const { return c.empty(); }
size_t size() const { return c.size(); }
private:
Container c;
};
批量操作优化:
cpp复制// 低效写法
for(int i = 0; i < 10000; ++i) {
s.push(i);
}
// 高效写法(如果底层容器支持)
std::vector<int> temp(10000);
std::iota(temp.begin(), temp.end(), 0);
std::stack<int> s(std::deque<int>(temp.begin(), temp.end()));
内存预分配:
cpp复制std::stack<int, std::vector<int>> s;
s.c.reserve(1000); // 直接访问底层容器预分配
标准容器适配器本身不是线程安全的,需要额外同步:
cpp复制#include <mutex>
template<typename T>
class ThreadSafeStack {
public:
void push(const T& val) {
std::lock_guard<std::mutex> lock(mtx);
s.push(val);
}
bool try_pop(T& val) {
std::lock_guard<std::mutex> lock(mtx);
if(s.empty()) return false;
val = s.top();
s.pop();
return true;
}
private:
std::stack<T> s;
std::mutex mtx;
};
stack的经典用例:
queue的核心应用:
实现一个固定大小的栈:
cpp复制template<typename T, size_t MaxSize>
class FixedStack {
public:
void push(const T& val) {
if(data.size() >= MaxSize)
throw std::runtime_error("Stack full");
data.push_back(val);
}
// 其他接口实现...
private:
std::vector<T> data;
};
STL容器适配器提供以下异常保证:
使用自定义分配器检测内存问题:
cpp复制template<typename T>
class DebugAllocator : public std::allocator<T> {
public:
T* allocate(size_t n) {
std::cout << "Allocating " << n << " elements\n";
return std::allocator<T>::allocate(n);
}
void deallocate(T* p, size_t n) {
std::cout << "Deallocating " << n << " elements\n";
std::allocator<T>::deallocate(p, n);
}
};
// 使用方式
std::stack<int, std::deque<int, DebugAllocator<int>>> debugStack;
在实际项目中,理解容器适配器的底层实现机制对于编写高效、可靠的C++代码至关重要。通过合理选择底层容器、注意线程安全问题和掌握边界情况处理,可以充分发挥stack和queue在算法实现和系统设计中的价值。