深入解析C++容器适配器:stack、queue与deque的实现与应用

一、容器适配器设计哲学

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的设计要点分析

  1. 模板参数设计

    • T:元素类型

    • Container:底层容器类型,默认vector<T>

    • 允许用户自定义底层容器,只要满足栈的操作需求

  2. const成员函数的设计

    cpp

    复制代码
    const T& top() const;  // const版本,用于const对象
    // 注意:标准库通常提供非const版本以允许修改栈顶
    T& top() { return _con.back(); }  // 可以添加非const版本
  3. 异常安全性

    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()的容器(dequelist

  • 推荐 :默认deque,性能均衡

  • 考虑list在频繁中间插入时更好

7.3 性能优化建议

  1. 预分配空间:如果可以预估大小,预先分配

  2. 批量操作:避免频繁的小规模操作

  3. 选择合适的容器:根据实际访问模式选择

通过深入理解容器适配器的实现原理,我们可以更好地利用STL提供的工具,也能在需要时自定义适合特定场景的数据结构。这种"知其然,知其所以然"的理解,是成为高级C++开发者的关键。

相关推荐
2401_844221322 小时前
C++类型推导(auto/decltype)
开发语言·c++·算法
2201_753877792 小时前
高性能计算中的C++优化
开发语言·c++·算法
2501_945425152 小时前
分布式系统容错设计
开发语言·c++·算法
IMPYLH2 小时前
Linux 的 basename 命令
linux·运维·服务器·ssh·bash
EnCi Zheng2 小时前
Linux基础技术专栏
linux·运维·服务器
阿成学长_Cain2 小时前
Linux 命令:ldconfig —— 动态链接库管理命令
java·开发语言·spring
2401_884563242 小时前
C++代码重构实战
开发语言·c++·算法
技术小甜甜2 小时前
[Python实战] 用 pathlib 彻底统一文件路径处理,比字符串拼接稳得多
开发语言·人工智能·python·ai·效率化
小王不爱笑1322 小时前
二叉排序树从入门到实践:攻克构建与遍历核心逻辑
开发语言·python·算法