《C++ Primer》第9章的标题通常是 “顺序容器(Sequential Containers)”,这是 C++ 标准库中非常核心的一部分内容。本章主要介绍了标准库提供的用于存储有序数据集合的容器类型,比如 vectorlistdeque 等,并深入讲解了它们的特性、用法、选择策略以及相关的操作。

下面是对第9章的详细讲解与内容梳理


🧩 第9章 顺序容器(Sequential Containers)—— 内容概览

一、什么是顺序容器?

顺序容器(Sequential Containers) 是一种用来存储一组对象的容器,这些对象在容器中按照它们被添加进去的顺序进行存储,也就是说,元素的逻辑顺序与它们在容器中的物理顺序是一致的。

顺序容器不提供基于键(key)的快速查找(那是关联容器的功能,比如 mapset),而是更关注元素之间的顺序关系和高效的插入/删除操作

C++ 标准库提供了几种常用的顺序容器:

容器 头文件 特点简述
vector <vector> 动态数组,支持快速随机访问,尾部插入/删除高效,中间或头部插入/删除相对低效
deque <deque> 双端队列,支持首尾高效插入/删除,也支持随机访问
list <list> 双向链表,任意位置插入/删除都很快,但不支持随机访问
forward_list (C++11) <forward_list> 单向链表,更节省空间,只支持单向遍历,插入/删除快,但功能更有限
array <array> 固定大小的数组,不是动态的,但比 C 风格数组更安全、更易用(C++11 引入)

注意:array 虽然是容器,但它大小固定,不属于“动态”顺序容器,更多是替代原生数组的一种更安全的方案。


二、顺序容器概述与选择

1. 容器的共性操作

所有顺序容器都支持一些公共接口,比如:

  • 添加元素:push_back()push_front()insert()

  • 删除元素:pop_back()pop_front()erase()clear()

  • 访问元素:[]at()front()back()

  • 获取大小:size()

  • 判断是否为空:empty()

  • 迭代器支持:begin()end() 等,用于遍历

但不同容器对某些操作的效率支持不同,有些操作甚至可能不被支持(如 list 不支持 [] 随机访问)。

2. 如何选择合适的顺序容器?

应用场景 推荐容器 原因
需要随机访问,主要在尾部增删 vector 随机访问最快,尾部操作高效
需要随机访问,同时在头尾高效增删 deque 头尾插入/删除都很快,也支持随机访问
需要频繁在任意位置插入/删除 listforward_list 插入/删除效率高,不涉及元素移动
内存紧凑、只需单向遍历且插入/删除多在某位置 forward_list list 更省空间,但功能有限
元素数量固定,追求性能与安全 array 替代 C 数组,更安全,但不可动态调整大小

三、容器详解

1. vector

特点:

  • 动态数组,内存通常连续分配

  • 支持快速的随机访问(通过下标或迭代器)

  • 尾部插入 / 删除效率高(push_back, pop_back

  • 中间或头部插入 / 删除效率较低,因为需要移动后续元素

常用操作:

std::vector<int> vec;
vec.push_back(10);    // 尾部插入
vec.pop_back();       // 尾部删除
vec.size();           // 元素个数
vec.empty();          // 是否为空
vec[0];               // 访问第一个元素(不检查边界)
vec.at(0);            // 访问第一个元素(会检查边界,越界抛异常)
vec.insert(vec.begin() + 1, 20); // 在指定位置插入
vec.erase(vec.begin()); // 删除第一个元素

2. deque

特点:

  • 双端队列,支持头部和尾部高效的插入/删除

  • 也支持随机访问(类似 vector)

  • 内部通常由多个固定大小的块组成,不必连续存储

适用场景: 需要像 vector 一样随机访问,但同时又想在头部快速插入/删除

常用操作:

std::deque<int> dq;
dq.push_back(1);  // 尾插
dq.push_front(2); // 头插
dq.pop_back();    // 尾删
dq.pop_front();   // 头删

3. list

特点:

  • 双向链表,每个元素单独分配,元素之间通过指针连接

  • 不支持随机访问(没有 operator[]

  • 任意位置的插入和删除都非常高效(只需修改指针)

  • 相对于 vector 和 deque,占用内存稍多,缓存局部性较差

常用操作:

std::list<int> lst;
lst.push_back(10);
lst.push_front(20);
lst.pop_back();
lst.pop_front();
lst.insert(++lst.begin(), 15); // 在第二个位置前插入15
lst.erase(--lst.end());        // 删除最后一个元素

4. forward_list (C++11)

特点:

  • 单向链表,只能从头到尾单向遍历

  • list 更节省内存,但功能受限

  • 只支持前向插入和删除,不支持反向操作

适用场景: 对内存要求苛刻,只需要单向遍历和简单插入/删除


5. array (C++11)

特点:

  • 固定大小的数组,编译期确定大小

  • 比 C 风格数组更安全(比如支持赋值、有边界方法等)

  • 不支持动态扩容,没有 push_back 等动态操作

常用操作:

std::array<int, 5> arr = {1, 2, 3, 4, 5};
arr[0] = 10;
int x = arr.at(1);

四、迭代器与遍历

所有顺序容器都支持迭代器,可以用来遍历容器中的元素:

for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";
}

或者使用更现代的 范围 for 循环(C++11)

for (int x : vec) {
    std::cout << x << " ";
}

注意:不是所有容器都支持所有类型的迭代器。例如:

  • vectordeque 支持随机访问迭代器

  • listforward_list 只支持双向或前向迭代器,不支持随机访问(如 it + 3 不合法)


五、容器适配器(不属于顺序容器,但常被一起讨论)

虽然严格来说不是顺序容器,但 C++ 标准库还提供了几个基于顺序容器的容器适配器,如:

  • stack(栈):默认基于 deque,也可基于 vectorlist

  • queue(队列):默认基于 deque

  • priority_queue(优先队列):默认基于 vector

这些适配器提供了更高级的接口,隐藏了底层容器的细节,适合特定场景使用。


六、管理容器的注意事项

1. 容量与大小

  • size():当前容器中有多少元素

  • capacity()(vector/deque):当前分配的内存能容纳多少元素(无需重新分配)

  • reserve(n):提前分配至少能容纳 n 个元素的空间,避免多次扩容

  • resize(n):改变容器中元素的数量,可以增加或减少

2. 添加/删除元素的影响

  • 在 vector 或 deque 中间插入/删除元素会导致其后所有元素的位置移动,开销较大

  • 迭代器在插入/删除后可能会失效,特别是 vector,需要特别注意!


七、总结:如何选择顺序容器?

需求 推荐容器
需要随机访问,主要在尾部操作 vector ✅ 最常用
需要头尾都高效操作 + 随机访问 deque
需要频繁在任意位置插入/删除 listforward_list
元素数量固定,追求安全与性能 array
只需单向遍历,极简内存 forward_list

📌 小贴士(来自 C++ Primer 的重要建议)

  1. 优先使用 vector,除非你有明确的理由选择其他容器。

  2. 避免在 vector 中间频繁插入/删除,考虑使用 list。

  3. 注意迭代器失效问题,特别是在 vector 中插入/删除后。

  4. 使用 range-based for 循环和 auto 关键字(C++11 起)让代码更简洁。

  5. 了解每种容器的优缺点及适用场合,不要盲目使用。


📘 补充:如果你想深入

  • 第9章还会介绍:

    • 如何初始化不同容器

    • 容器间的赋值与拷贝

    • 如何使用 emplace 操作(C++11,如 emplace_back,直接在容器内构造元素,避免临时对象)

    • 容器的性能比较与底层实现分析


✅ 总结

C++ Primer 第9章“顺序容器”是学习 C++ 标准库容器最重要的基础之一。掌握好 vector、list、deque 等容器的特性、用法和适用场景,能够帮助你在实际编程中做出合理的选择,写出更高效、更清晰的代码。

好的!我们接着对《C++ Primer》第9章 “顺序容器(Sequential Containers)” 进行更深入、更精细的讲解,从以下几个方面展开:


🔍 第9章 顺序容器 —— 深入详解

我们将从以下几个主题深入:

  1. 容器的初始化与赋值

  2. 添加与删除元素(重点:迭代器失效)

  3. 访问元素的方法与注意事项

  4. 容器的容量管理(size, capacity, reserve, shrink_to_fit)

  5. emplace 操作(C++11 新特性,emplace_back / emplace_front / emplace)

  6. 迭代器失效问题(非常重要!)

  7. forward_list 的特殊操作(因其单向性)

  8. 容器适配器简要回顾(stack, queue, priority_queue)

  9. 如何选择合适的容器(再次总结,结合实践)


1️⃣ 容器的初始化与赋值

所有顺序容器都支持多种初始化方式,常见如下:

基本初始化方式

// 默认初始化(空容器)
std::vector<int> v1;             
std::list<std::string> l1;

// 初始化指定数量的元素(值初始化)
std::vector<int> v2(10);         // 10个元素,每个都是0
std::vector<int> v3(10, 42);     // 10个元素,每个都是42

// 列表初始化(C++11)
std::vector<std::string> v4{"hi", "hello", "world"};
std::list<int> l2{1, 2, 3, 4};

// 通过迭代器范围初始化(比如从数组或其他容器中拷贝)
int arr[] = {1, 2, 3};
std::vector<int> v5(std::begin(arr), std::end(arr));

拷贝与赋值

std::vector<int> v6 = v5;            // 拷贝构造
std::vector<int> v7;
v7 = v6;                             // 拷贝赋值

⚠️ 注意:容器间的赋值或拷贝是深拷贝,两个容器之后互不影响。


2️⃣ 添加与删除元素(重点:迭代器失效)

这是顺序容器最常用的操作,但也是最容易出错的地方,尤其是涉及到迭代器失效问题。

通用操作

尾部操作(大部分容器支持)

操作 含义 支持容器
push_back(t) 在尾部添加元素 t vector, deque, list, forward_list(用 push_front)
pop_back() 删除尾部元素 vector, deque, list(forward_list 无 pop_back)

头部操作(部分容器支持)

操作 含义 支持容器
push_front(t) 在头部插入 t deque, list, forward_list
pop_front() 删除头部元素 deque, list, forward_list(vector 不支持)

任意位置插入与删除

操作 说明
insert(iter, value) 在迭代器 iter 指向的位置之前插入 value
erase(iter) 删除迭代器 iter 指向的元素
erase(start, end) 删除区间 [start, end) 中的元素

🔧 示例:

std::vector<int> vec = {1, 2, 3};
vec.push_back(4);                // vec: {1, 2, 3, 4}
vec.insert(vec.begin() + 1, 10); // 在第1个位置前插入10 → {1, 10, 2, 3, 4}
vec.erase(vec.begin());          // 删除第一个元素 → {10, 2, 3, 4}

⚠️ 迭代器失效(Iterator Invalidation)——极其重要!

迭代器失效指的是:当容器中的元素发生变动(如插入、删除)后,原来获取的迭代器可能不再有效,再使用它将导致未定义行为(通常是程序崩溃)。

vector 和 deque:

  • 任何插入或删除操作后所有指向被插入/删除位置之后元素的迭代器、指针和引用都可能失效!

  • 特别是 push_back 如果引起重新分配内存(扩容),则所有迭代器、指针、引用全部失效!

✅ 建议:在对 vector 频繁在中间插入/删除时,考虑使用 list;如果必须用 vector,注意不要保存过期的迭代器。

list 和 forward_list:

  • 插入操作不会使任何迭代器失效(包括指向其他元素的迭代器)。

  • 删除操作仅使指向被删除元素的迭代器失效,其他迭代器依旧有效。


3️⃣ 访问元素的方法

方法 说明 是否支持随机访问
v[i] 访问下标为 i 的元素(不检查边界) ✅ vector, deque
v.at(i) 访问下标为 i 的元素(会检查边界,越界抛异常 std::out_of_range ✅ vector, deque
v.front() 返回第一个元素 ✅ 所有容器(除 array 外都有)
v.back() 返回最后一个元素 ✅ 除 forward_list
*iter 通过迭代器解引用访问元素 ✅ 所有

⚠️ 注意:operator[]at() 仅适用于支持随机访问的容器(如 vector、deque),list 不支持 [] 操作!


4️⃣ 容量管理

常用操作

函数 作用 适用容器
size() 当前元素个数 所有容器
empty() 是否为空 所有容器
capacity() 当前分配的存储空间能容纳多少元素(vector/deque) vector, deque
reserve(n) 请求分配至少能容纳 n 个元素的空间(避免多次扩容) vector, deque
shrink_to_fit() (C++11) 请求减少 capacity 以匹配 size(非强制) vector, deque, string

🔍 为什么需要 reserve

  • vector 在插入元素时如果超出当前 capacity,会触发重新分配内存 + 拷贝所有元素,代价很高。

  • 提前调用 reserve(1000) 可以避免多次扩容,提升性能。


5️⃣ emplace 操作(C++11 重要特性)

传统插入如 insertpush_back 需要你先构造一个对象,然后将其拷贝或移动到容器中

emplace 系列函数(如 emplace_back)允许你直接在容器内部“就地构造”对象,省去了临时对象的构造和拷贝/移动过程。

对比:

// 传统方式:先构造对象,再拷贝到容器
std::vector<std::string> vec;
std::string s = "hello";
vec.push_back(s);             // 调用了拷贝构造函数

// emplace 方式:直接在容器中构造
vec.emplace_back("hello");    // 直接通过参数构造 string,无需临时对象

🔧 常见 emplace 方法:

  • emplace_back(args...) → 在尾部直接构造

  • emplace_front(args...)

  • emplace(iter, args...) → 在指定位置构造

✅ 推荐在 C++11 及以后尽量使用 emplace 系列,提高性能,特别是对象构造开销较大时。


6️⃣ forward_list 的特殊操作

由于 forward_list单向链表,它不支持反向遍历,也不支持随机访问。因此它提供了一些独特的插入/删除接口,比如:

std::forward_list<int> flist = {1, 2, 3};

// 在某个元素之后插入
auto prev = flist.before_begin();           // 特殊迭代器,表示第一个元素之前的位置
flist.insert_after(prev, 0);                // 在最前面插入0 → {0, 1, 2, 3}

// 在指定位置后插入
auto it = flist.begin();
std::advance(it, 2);                        // 指向3
flist.insert_after(it, 99);                 // 在3后面插入99 → {0,1,2,3,99}

// 删除某个元素之后的元素
flist.erase_after(flist.begin());           // 删除第一个元素之后的 → 删除1

✅ forward_list 没有 push_backinsert(不接受迭代器)、size() 成员函数等。


7️⃣ 容器适配器简要回顾(第9章也会提及)

虽然它们不是顺序容器,但常与顺序容器配合使用:

适配器 底层默认容器 特点
stack deque(可换成 vector / list) 后进先出 LIFO
queue deque 先进先出 FIFO
priority_queue vector(可换成 deque) 优先级最高者先出(堆结构)

这些适配器限制了接口,让你只能进行符合其逻辑的操作(比如 stack 只能 push / pop / top)。


8️⃣ 如何选择合适的容器?(最终建议总结)

需求 推荐容器 原因
通用、需要随机访问,尾部操作多 vector 最常用,效率高
头尾都频繁操作 + 随机访问 deque 双端高效 + 支持下标访问
任意位置频繁插入/删除 list 双向链表,插入删除快
内存敏感,只需单向遍历 forward_list 更省内存,但功能少
元素数量固定 array 固定大小,安全高效
栈结构 stack(基于 deque/vector/list) 后进先出
队列结构 queue(基于 deque) 先进先出
优先级队列 priority_queue(基于 vector) 堆结构,自动排序

✅ 总结 Checklist(学完本章你应该掌握):

  • [x] 理解什么是顺序容器及其与关联容器的区别

  • [x] 熟悉 vector、deque、list、forward_list、array 的特点与适用场景

  • [x] 掌握容器的基本操作:增删改查、迭代器遍历

  • [x] 理解并避免迭代器失效问题(特别是 vector)

  • [x] 会使用 emplace_back 等高效构造方法

  • [x] 会管理容器容量:size, capacity, reserve

  • [x] 能根据需求合理选择容器类型

  • [x] 了解容器适配器(stack/queue/priority_queue)

 

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐