9C++ Primer(第5版)

顺序容器

STL容器

vectorlistdeque容器适配器

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第9章:顺序容器(Sequential Containers)

一、导读

1.1 本章学习目标

第9章深入讲解C++标准库的顺序容器。本章核心目标:

  • 掌握各种顺序容器:vector、list、deque
  • 理解容器的性能差异:随机访问vs插入删除
  • 学会使用容器适配器:stack、queue、priority_queue
  • 理解迭代器失效:什么操作会导致迭代器失效
  • 掌握容器的通用操作:大小、清空、交换

1.2 为什么本章如此重要

容器是C++ STL的核心组件。选择合适的容器对程序性能有重大影响。vector是最常用的容器,但在频繁插入删除的场景下,list可能更合适。

1.3 核心问题引导

vector、list、deque的底层实现分别是什么?

什么操作会导致vector的迭代器失效?

stack和queue默认使用什么容器实现?

如何根据需求选择合适的容器?


二、核心概念详解

9.1 顺序容器概览

容器底层结构随机访问头部插入尾部插入中间插入
vector动态数组O(1)O(n)摊还O(1)O(n)
list双向链表O(n)O(1)O(1)O(1)
deque分段数组O(1)O(1)O(1)O(n)
forward_list单向链表O(n)O(1)O(n)O(1)
array固定数组O(1)---

9.2 vector详解

cpp
#include <iostream>
#include <vector>

int main() {
    // 初始化
    std::vector<int> v1;
    std::vector<int> v2 = {1, 2, 3, 4, 5};
    std::vector<int> v3(5, 42);
    std::vector<int> v4(v2);
    
    // 基本操作
    v1.push_back(10);
    v1.push_back(20);
    v1.push_back(30);
    
    std::cout << "Size: " << v1.size() << std::endl;
    std::cout << "Capacity: " << v1.capacity() << std::endl;
    
    // 访问
    std::cout << v1[0] << std::endl;       // 10
    std::cout << v1.at(1) << std::endl;    // 20(带边界检查)
    std::cout << v1.front() << std::endl;  // 10
    std::cout << v1.back() << std::endl;   // 30
    
    // 插入和删除
    v1.insert(v1.begin() + 1, 15);
    v1.erase(v1.begin());
    v1.pop_back();
    
    // 预分配
    v1.reserve(100);
    
    // shrink_to_fit
    v1.shrink_to_fit();
    
    return 0;
}

9.3 list和deque

cpp
#include <iostream>
#include <list>
#include <deque>

int main() {
    // list:双向链表
    std::list<int> l = {1, 2, 3, 4, 5};
    l.push_front(0);
    l.push_back(6);
    l.sort();
    l.reverse();
    l.unique();  // 删除相邻重复元素
    
    for (int x : l) std::cout << x << " ";
    std::cout << std::endl;
    
    // list特有操作
    l.splice(l.begin(), l, l.end());  // 移动元素
    
    // deque:双端队列
    std::deque<int> d;
    d.push_front(1);
    d.push_back(2);
    d.push_front(0);
    d.push_back(3);
    
    for (int x : d) std::cout << x << " ";
    std::cout << std::endl;
    
    // deque支持随机访问
    std::cout << d[0] << std::endl;
    std::cout << d.at(1) << std::endl;
    
    return 0;
}

9.4 容器适配器

cpp
#include <iostream>
#include <vector>
#include <stack>
#include <queue>

int main() {
    // stack:后进先出(默认使用deque)
    std::stack<int> s;
    s.push(1);
    s.push(2);
    s.push(3);
    
    std::cout << "Stack top: " << s.top() << std::endl;
    while (!s.empty()) {
        std::cout << s.top() << " ";
        s.pop();
    }
    std::cout << std::endl;
    
    // queue:先进先出(默认使用deque)
    std::queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);
    
    std::cout << "Queue front: " << q.front() << std::endl;
    while (!q.empty()) {
        std::cout << q.front() << " ";
        q.pop();
    }
    std::cout << std::endl;
    
    // priority_queue:优先队列(最大堆)
    std::priority_queue<int> pq;
    pq.push(3);
    pq.push(1);
    pq.push(4);
    pq.push(1);
    
    std::cout << "Priority queue: ";
    while (!pq.empty()) {
        std::cout << pq.top() << " ";
        pq.pop();
    }
    std::cout << std::endl;
    
    // 使用vector作为stack的底层容器
    std::stack<int, std::vector<int>> vs;
    
    return 0;
}

9.5 迭代器失效

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // push_back可能导致迭代器失效(如果扩容)
    auto it = v.begin() + 2;
    v.push_back(6);  // 可能扩容
    // *it;  // 可能崩溃!
    
    // 安全做法:重新获取迭代器
    it = v.begin() + 2;
    
    // insert使插入点之后的迭代器失效
    v.insert(v.begin() + 1, 100);
    
    // erase使被删除元素之后的迭代器失效
    it = v.begin() + 1;
    v.erase(it);
    
    // 在遍历中删除元素的正确方式
    for (auto it = v.begin(); it != v.end(); ) {
        if (*it % 2 == 0) {
            it = v.erase(it);  // erase返回下一个有效迭代器
        } else {
            ++it;
        }
    }
    
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

三、实战案例

3.1 使用栈实现括号匹配

cpp
#include <iostream>
#include <stack>
#include <string>

bool isBalanced(const std::string &s) {
    std::stack<char> st;
    
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') {
            st.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (st.empty()) return false;
            char top = st.top();
            st.pop();
            if (c == ')' && top != '(') return false;
            if (c == ']' && top != '[') return false;
            if (c == '}' && top != '{') return false;
        }
    }
    
    return st.empty();
}

int main() {
    std::cout << isBalanced("({[]})") << std::endl;  // 1
    std::cout << isBalanced("({[})]") << std::endl;  // 0
    std::cout << isBalanced("((()))") << std::endl;  // 1
    return 0;
}

四、本章小结

第9章介绍了C++顺序容器:vector(动态数组)、list(双向链表)、deque(双端队列),以及容器适配器stack、queue、priority_queue。选择容器时要考虑操作的性能需求。


五、思考与练习

思考题

vector扩容时容量增长策略是什么?

为什么deque的两端操作都是O(1)?

容器适配器的底层容器可以自定义吗?

如何在遍历过程中安全地删除元素?

编程练习

使用queue实现广度优先搜索。

使用两个栈实现一个队列。

实现一个简单的LRU缓存。


9/12顺序容器
0%