当前位置: 首页 > news >正文

【经典书籍】C++ Primer 第9章顺序容器精华讲解

《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_listlist 更省空间,但功能有限
元素数量固定,追求性能与安全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)在尾部添加元素 tvector, deque, list, forward_list(用 push_front)
pop_back()删除尾部元素vector, deque, list(forward_list 无 pop_back)

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

操作含义支持容器
push_front(t)在头部插入 tdeque, 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章也会提及)

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

适配器底层默认容器特点
stackdeque(可换成 vector / list)后进先出 LIFO
queuedeque先进先出 FIFO
priority_queuevector(可换成 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)

 

 

http://www.dtcms.com/a/513009.html

相关文章:

  • 基于MATLAB的光学相干断层扫描图像处理与定量分析算法研究
  • 怎么做发卡网站购买网站app制作
  • 淘客做网站怎么备案手机上网站
  • 解决IAR编译路径空格报错
  • 【JUnit实战3_05】第三章:JUnit 的体系结构(上)
  • Oracle 打补丁指南
  • 蓝奥声智能插座:从“用电计量”到“设备健康预测”的跨越
  • 网站域名到期广东网站建站公司
  • 林地变化检测技术拆解:基于语义分割的双时相卫星影像比对实现方案
  • 网站建设明细报价表 xlsiis发布网站的教程
  • Vue3 大文件上传实战:切片上传 / 断点续传 / 秒传 / 暂停恢复 / 全局并发控制
  • Qt qmlplugindump浅谈
  • 网站后台登陆代码表白网站制作源码
  • 广告设计用到的软件福州网站seo优化公司
  • 微分运算电路(波形转换电路)
  • 阿里云网站目录建立网站得多少钱
  • (二)React开发环境搭建与目录结构分析(零基础友好)
  • wordpress取消评论审核南京seo公司教程
  • 时间敏感网络(TSN)
  • 三轴云台之多算法协同技术篇
  • 深圳html5网站建设价格用记事本做电影介绍的网站
  • 延吉网站建设多少钱江苏泰州海陵区建设局网站
  • 建站行业的发展前景做个淘宝客网站怎么做
  • 建站哪家好 discuz网页设计专业设计课程
  • 站长工具 网站改版怎么建设公益网站
  • face_recognition :一个 “开箱即用” 的人脸识别工具
  • 赣州培训学做网站自己做电影下载网站
  • 如何建立自己的企业网站无锡网站建设套餐
  • 天水做网站的如何选择镇江网站优化
  • 网站开发时遇到的问题php建网站