一、容器适配器设计哲学
1.1 什么是容器适配器?
容器适配器不是独立的容器,而是设计模式中的适配器模式在STL中的具体实现。它们通过封装已有的容器,提供特定的数据访问接口。
1.2 适配器模式的核心思想
cpp
// 示意图:适配器的工作方式
原始容器接口 --> 适配器 --> 特定数据结构接口
[push_back, pop_back] --> stack适配器 --> [push, pop, top]
二、stack的深度实现解析
2.1 stack的基本架构
cpp
template<class T, class Container = vector<T>>
class stack
{
private:
Container _con; // 关键:组合而非继承
public:
// 接口转换:将容器的操作适配为栈的操作
void push(const T& x) {
_con.push_back(x); // vector的push_back变为栈的push
}
void pop() {
_con.pop_back(); // vector的pop_back变为栈的pop
}
const T& top() const {
return _con.back(); // vector的back变为栈的top
}
bool empty() const {
return _con.empty();
}
size_t size() const {
return _con.size();
}
};
2.2 stack的设计要点分析
-
模板参数设计
-
T:元素类型 -
Container:底层容器类型,默认vector<T> -
允许用户自定义底层容器,只要满足栈的操作需求
-
-
const成员函数的设计
cpp
const T& top() const; // const版本,用于const对象 // 注意:标准库通常提供非const版本以允许修改栈顶 T& top() { return _con.back(); } // 可以添加非const版本 -
异常安全性
cpp
// 当前实现缺乏异常安全检查 void pop() { if (!empty()) { // 应添加检查 _con.pop_back(); } // 或者抛出异常:throw std::out_of_range("stack is empty"); }
三、queue的深度实现解析
3.1 queue的基本架构
cpp
template<class T, class Container = deque<T>>
class queue
{
private:
Container _con; // 核心:封装底层容器
public:
// 队列操作适配
void push(const T& x) {
_con.push_back(x); // 入队:尾部插入
}
void pop() {
_con.pop_front(); // 出队:头部删除
}
const T& front() const {
return _con.front(); // 获取队头
}
const T& back() const {
return _con.back(); // 获取队尾
}
bool empty() const {
return _con.empty();
}
size_t size() const {
return _con.size();
}
};
3.2 为什么queue默认使用deque?
cpp
// vector不能作为queue的底层容器,因为它没有pop_front()
// 三种可能的选择:
// 1. deque(默认选择)
// 优点:两端操作O(1),随机访问O(1)
// 缺点:内存不连续,迭代器复杂
// 2. list
// 优点:两端操作O(1),任意位置插入O(1)
// 缺点:随机访问O(n),内存开销大
// 3. 自定义循环数组
// 优点:内存连续,缓存友好
// 缺点:需要手动处理循环逻辑
四、deque的深度解析
4.1 deque的内部结构
text
deque的层次结构:
1. 中控器(map):指针数组,每个元素指向一个缓冲区
2. 缓冲区(buffer):固定大小的连续内存块
3. 迭代器:包含4个指针,用于遍历整个deque
典型实现:
+---+---+---+---+---+
| * | * | * | * | * | <- 中控器(动态数组)
+---+---+---+---+---+
| | | |
v v v v
+---+ +---+ +---+ +---+
| | | | | | | | <- 缓冲区(固定大小数组)
| | | | | | | |
+---+ +---+ +---+ +---+
4.2 deque的迭代器设计
cpp
// 简化的deque迭代器结构
template<class T>
struct deque_iterator {
T* cur; // 当前元素指针
T* first; // 缓冲区起始
T* last; // 缓冲区末尾
T** node; // 指向中控器的节点
// 前进操作
deque_iterator& operator++() {
++cur;
if (cur == last) { // 到达缓冲区末尾
set_node(node + 1); // 跳转到下一个缓冲区
cur = first;
}
return *this;
}
};
4.3 deque的性能特点
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 随机访问 | O(1) | 通过计算定位到具体缓冲区和偏移 |
| 头尾插入/删除 | O(1) | 只需操作两端缓冲区 |
| 中间插入/删除 | O(n) | 需要移动元素 |
| 内存使用 | 较高 | 需要维护中控器和多个缓冲区 |
五、自定义实现的stack和queue使用指南
5.1 基本使用示例
cpp
#include <iostream>
#include "stack.h"
#include "queue.h"
void test_stack_basic() {
std::cout << "=== Stack Basic Operations ===" << std::endl;
// 1. 创建栈
yyq::stack<int> st;
// 2. 压栈操作
std::cout << "Pushing elements: 10, 20, 30" << std::endl;
st.push(10);
st.push(20);
st.push(30);
// 3. 查看栈顶
std::cout << "Top element: " << st.top() << std::endl; // 30
// 4. 栈大小
std::cout << "Stack size: " << st.size() << std::endl; // 3
// 5. 判断是否为空
std::cout << "Is empty? " << (st.empty() ? "Yes" : "No") << std::endl; // No
// 6. 弹出元素
std::cout << "Popping elements..." << std::endl;
while (!st.empty()) {
std::cout << "Top: " << st.top() << ", Size: " << st.size() << std::endl;
st.pop();
}
std::cout << "After popping all, is empty? "
<< (st.empty() ? "Yes" : "No") << std::endl; // Yes
}
void test_queue_basic() {
std::cout << "\n=== Queue Basic Operations ===" << std::endl;
// 1. 创建队列
yyq::queue<std::string> q;
// 2. 入队操作
std::cout << "Enqueuing: Alice, Bob, Charlie" << std::endl;
q.push("Alice");
q.push("Bob");
q.push("Charlie");
// 3. 查看队头和队尾
std::cout << "Front: " << q.front() << std::endl; // Alice
std::cout << "Back: " << q.back() << std::endl; // Charlie
// 4. 队列大小
std::cout << "Queue size: " << q.size() << std::endl; // 3
// 5. 出队操作
std::cout << "Dequeuing elements..." << std::endl;
while (!q.empty()) {
std::cout << "Processing: " << q.front()
<< ", Remaining: " << q.size() << std::endl;
q.pop();
}
}
5.2 使用不同底层容器
cpp
void test_with_different_containers() {
std::cout << "\n=== Testing with Different Containers ===" << std::endl;
// 1. stack使用deque作为底层容器
yyq::stack<int, std::deque<int>> stack_with_deque;
stack_with_deque.push(100);
stack_with_deque.push(200);
std::cout << "Stack with deque - Top: " << stack_with_deque.top() << std::endl;
// 2. stack使用list作为底层容器
yyq::stack<int, std::list<int>> stack_with_list;
stack_with_list.push(300);
stack_with_list.push(400);
std::cout << "Stack with list - Top: " << stack_with_list.top() << std::endl;
// 3. queue使用list作为底层容器(必须支持pop_front)
yyq::queue<int, std::list<int>> queue_with_list;
queue_with_list.push(500);
queue_with_list.push(600);
std::cout << "Queue with list - Front: " << queue_with_list.front() << std::endl;
// 注意:queue不能使用vector,因为它没有pop_front()
// yyq::queue<int, std::vector<int>> queue_with_vector; // 编译错误
}
5.3 实际应用场景
cpp
// 应用1:表达式求值(使用栈)
double evaluate_expression(const std::string& expr) {
yyq::stack<double> values;
yyq::stack<char> ops;
for (size_t i = 0; i < expr.length(); i++) {
// 简化版,仅处理数字和加减
if (isdigit(expr[i])) {
double num = 0;
while (i < expr.length() && isdigit(expr[i])) {
num = num * 10 + (expr[i] - '0');
i++;
}
i--; // 回退一格
values.push(num);
}
else if (expr[i] == '+' || expr[i] == '-') {
ops.push(expr[i]);
}
}
// 简化计算(实际需要更复杂的优先级处理)
while (!ops.empty()) {
char op = ops.top(); ops.pop();
double b = values.top(); values.pop();
double a = values.top(); values.pop();
if (op == '+') values.push(a + b);
else if (op == '-') values.push(a - b);
}
return values.top();
}
// 应用2:广度优先搜索模拟(使用队列)
void bfs_simulation(int start, const std::vector<std::vector<int>>& graph) {
yyq::queue<int> q;
std::vector<bool> visited(graph.size(), false);
q.push(start);
visited[start] = true;
std::cout << "\nBFS Traversal: ";
while (!q.empty()) {
int current = q.front();
q.pop();
std::cout << current << " ";
for (int neighbor : graph[current]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
std::cout << std::endl;
}
int main() {
// 基本使用测试
test_stack_basic();
test_queue_basic();
// 不同容器测试
test_with_different_containers();
// 实际应用
std::string expr = "3+5-2";
std::cout << "\nExpression: " << expr << " = "
<< evaluate_expression(expr) << std::endl;
// BFS测试
std::vector<std::vector<int>> graph = {
{1, 2}, // 节点0的邻居
{0, 3, 4}, // 节点1的邻居
{0, 5}, // 节点2的邻居
{1}, // 节点3的邻居
{1}, // 节点4的邻居
{2} // 节点5的邻居
};
bfs_simulation(0, graph);
return 0;
}
5.4 性能对比测试
cpp
void performance_test() {
std::cout << "\n=== Performance Test ===" << std::endl;
const int N = 1000000;
// 测试stack性能
auto start = std::chrono::high_resolution_clock::now();
yyq::stack<int> st;
for (int i = 0; i < N; i++) {
st.push(i);
}
for (int i = 0; i < N; i++) {
st.pop();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Stack push/pop " << N << " elements: "
<< duration.count() << " ms" << std::endl;
// 测试queue性能
start = std::chrono::high_resolution_clock::now();
yyq::queue<int> q;
for (int i = 0; i < N; i++) {
q.push(i);
}
for (int i = 0; i < N; i++) {
q.pop();
}
end = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Queue push/pop " << N << " elements: "
<< duration.count() << " ms" << std::endl;
}
六、实现改进建议
6.1 添加异常安全
cpp
// 改进的stack实现
template<class T, class Container = vector<T>>
class safe_stack {
private:
Container _con;
public:
void pop() {
if (empty()) {
throw std::out_of_range("stack::pop(): stack is empty");
}
_con.pop_back();
}
T& top() {
if (empty()) {
throw std::out_of_range("stack::top(): stack is empty");
}
return _con.back();
}
const T& top() const {
if (empty()) {
throw std::out_of_range("stack::top() const: stack is empty");
}
return _con.back();
}
// ... 其他成员函数
};
6.2 支持移动语义
cpp
template<class T, class Container = vector<T>>
class modern_stack {
private:
Container _con;
public:
// 移动版本的push
void push(T&& x) {
_con.push_back(std::move(x));
}
// 完美转发
template<typename... Args>
void emplace(Args&&... args) {
_con.emplace_back(std::forward<Args>(args)...);
}
// ... 其他成员函数
};
6.3 添加比较操作符
cpp
template<class T, class Container>
bool operator==(const yyq::stack<T, Container>& lhs,
const yyq::stack<T, Container>& rhs) {
return lhs._con == rhs._con; // 需要将_con改为public或提供访问接口
}
template<class T, class Container>
bool operator!=(const yyq::stack<T, Container>& lhs,
const yyq::stack<T, Container>& rhs) {
return !(lhs == rhs);
}
七、总结与最佳实践
7.1 stack使用总结
-
默认选择 :
vector作为底层容器,适合大多数场景 -
特殊需求 :需要频繁在两端操作时选择
deque -
避免 :不要使用
list,除非有特殊迭代器需求
7.2 queue使用总结
-
必须选择 :支持
pop_front()的容器(deque或list) -
推荐 :默认
deque,性能均衡 -
考虑 :
list在频繁中间插入时更好
7.3 性能优化建议
-
预分配空间:如果可以预估大小,预先分配
-
批量操作:避免频繁的小规模操作
-
选择合适的容器:根据实际访问模式选择
通过深入理解容器适配器的实现原理,我们可以更好地利用STL提供的工具,也能在需要时自定义适合特定场景的数据结构。这种"知其然,知其所以然"的理解,是成为高级C++开发者的关键。