笔试选择、代码手撕、性能分析核心考点,容器底层、区别对比、迭代器失效为面试重灾区,考点固定、踩分点明确,直接背诵使用。
1. vector 底层原理与扩容机制
1.1 底层结构
基于动态连续数组 实现,内存空间连续,使用三段指针管理:_start、_finish、_end_of_storage。
- 容量 (capacity):总分配内存大小
- 大小 (size):当前有效元素个数
1.2 扩容机制
- 当元素个数达到容量上限,触发自动扩容。
- 扩容规则:
- Linux/GCC:原容量 2 倍扩容
- Windows/VS:小于 1024 字节 2 倍扩容,超过后1.5 倍扩容
- 扩容流程:开辟新内存 → 拷贝原元素 → 释放旧内存 → 更新内部指针。
- 特性:扩容会导致迭代器、指针、引用全部失效。
1.3 优缺点
- 优点:随机访问效率高(下标访问 O(1))、内存连续、缓存命中率高。
- 缺点:头部/中间插入删除效率低(元素整体搬迁);扩容存在内存拷贝开销。
高频追问
问 :为什么不原地扩容?
答:堆内存地址不保证后方有连续空闲空间,只能异地开辟新空间。
2. string 实现与常见坑点
2.1 底层实现
本质是封装后的char型动态数组,多数 STL 实现采用短字符串优化 (SSO):短字符串直接存于对象内部,不申请堆内存;长字符串才使用堆空间。内部同样维护容量、大小指针。
2.2 常用接口
增删查改、+=拼接、c_str()获取 C 风格字符串、substr截取子串等。
2.3 高频坑点
c_str()返回指针:string 重新分配内存后,原指针失效。- 拼接大量字符串:频繁扩容拷贝,建议提前
reserve预留空间优化性能。 - 区分
size()与length():二者等价,均返回字符个数,不含末尾\0。 - 空字符
\0:string 可存储\0,不会截断;C 风格字符串遇到\0直接截断。
3. list 特点、与 vector 对比
3.1 list 底层结构
基于双向循环链表实现,内存不连续,每个节点保存数据、前驱指针、后继指针。
3.2 核心特点
- 任意位置插入、删除元素 O(1) 效率,仅修改指针。
- 不支持随机访问,仅能遍历访问,访问效率 O(n)。
- 插入/删除元素不会导致迭代器失效(被删除节点的迭代器除外)。
- 内存碎片化,CPU 缓存命中率低。
3.3 list vs vector 核心对比
- 访问:vector 支持随机访问;list 仅顺序访问。
- 增删:头尾/中间增删 list 更快;vector 元素搬迁,效率低。
- 内存:vector 连续;list 节点分散,额外存储指针,内存开销大。
- 迭代器:vector 扩容/增删易失效;list 迭代器稳定性高。
使用建议
频繁查询、少量增删选 vector;频繁任意位置增删、遍历场景选 list。
4. deque 底层结构与适用场景
4.1 底层结构
分段连续内存,由多个小块缓冲区 + 中控映射表组成。整体不连续,每一小块内部内存连续。
4.2 核心特性
- 支持首尾高效插入删除 O(1),中间增删效率低。
- 支持随机访问,效率弱于 vector。
- 扩容代价小,无需整体拷贝数据。
- 迭代器比 vector 复杂,失效规则介于 vector 与 list 之间。
4.3 适用场景
- 作为 stack、queue 的默认底层容器。
- 需要频繁在头尾操作元素,又偶尔随机访问的场景。
补充
不推荐作为常��存储容器,功能折中,综合性能不如 vector/list。
5. map 与 unordered_map 区别
5.1 底层实现
- map :基于红黑树(平衡二叉搜索树)
- unordered_map :基于哈希表(散列表)
5.2 核心差异
- 有序性 :map 按键值自动升序排序;unordered_map 无序,顺序由哈希算法决定。
- 时间复杂度:map 增删查 O(logn);unordered_map 平均 O(1),最坏 O(n)(哈希冲突)。
- 底层开销:map 节点存储父子指针,内存开销大;哈希表存在哈希桶、负载因子,有额外开销。
- 迭代器:map 迭代器遍历有序;unordered_map 遍历无序。
- 键要求 :map 需要键支持比较运算符;unordered_map 需要哈希函数 + 相等判断。
选型
追求查询速度、不要求有序 → unordered_map;要求有序遍历、稳定性优先 → map。
6. set 与 unordered_set 差异
6.1 底层实现
- set:红黑树,元素自动去重 + 有序。
- unordered_set:哈希表,元素去重、无序。
6.2 核心区别
- 有序性:set 有序;unordered_set 无序。
- 效率:set 读写 O(logn);unordered_set 平均 O(1)。
- 元素特性:二者均元素唯一、不可修改键/元素本身。
- 底层依赖:set 依赖比较函数;unordered_set 依赖哈希函数。
补充规则
set/unordered_set 不允许重复元素;若需允许重复,使用 multiset / unordered_multiset。
7. 迭代器失效问题(高频坑)
7.1 本质
迭代器可理解为容器元素的"指针",容器内存重构、元素搬迁、节点销毁,都会让迭代器指向非法空间,造成失效。
7.2 各容器失效规则
vector
- 扩容:全部迭代器失效。
- 中间/头部删除:当前及后方迭代器失效。
- 尾部删除:仅尾迭代器失效。
list
- 仅被删除节点的迭代器失效,其余全部有效。
deque
- 头尾插入:部分迭代器失效;中间增删:全部失效。
map/set(红黑树)
- 删除元素:仅当前被删元素迭代器失效,其余有效。
- 插入元素:所有迭代器均不失效。
unordered_map/unordered_set
- 哈希表重建 (rehash):全部迭代器失效。
7.3 通用规避方案
- 操作后重新获取迭代器,不复用旧迭代器。
- 尽量使用返回值接收新迭代器(如
erase返回下一个迭代器)。 - 遍历过程中避免大范围增删元素。
8. STL 空间配置器简单理解
8.1 作用
STL 容器不直接调用new/delete,而是通过空间配置器 (allocator) 统一管理内存分配、释放、构造、析构。
8.2 双层架构(SGI STL)
- 一级配置器 :直接调用系统
malloc/free,应对大块内存、异常处理。 - 二级配置器 :针对小块内存,维护内存池,减少频繁系统调用,降低内存碎片。
8.3 核心优势
- 屏蔽底层内存接口,容器与内存管理解耦。
- 小块内存使用内存池,提升分配效率、减少碎片。
- 支持自定义配置器,适配特殊内存场景。
面试结论
日常开发几乎不用手写配置器,理解内存池 + 分层管理核心思想即可。
9. 常用算法 sort、find 原理
9.1 std::sort
- 底层组合算法:内省排序 (Introsort)
- 主体:快速排序
- 递归深度过大:转为堆排序
- 数据量极小:转为插入排序
- 特性:不稳定排序;时间复杂度 O(nlogn);可自定义比较规则。
- 使用范围:支持随机访问迭代器(vector、deque),不支持 list。
9.2 std::find
- 底层:顺序遍历查找,逐个元素比对。
- 时间复杂度 O(n),所有容器通用。
- 返回值:找到返回目标元素迭代器;未找到返回容器
end()。
补充
有序容器优先使用 binary_search 二分查找(O(logn)),效率远高于 find。
10. 容器选择实战准则
快速选型口诀
- 需随机访问 → 优先 vector
- 频繁头尾操作 → deque / queue / stack
- 频繁任意位置增删 → list
- 键值对存储、要求有序 → map
- 键值对、追求查询速度、无序 → unordered_map
- 元素去重、有序 → set
- 元素去重、无序、高速访问 → unordered_set
通用原则
- 优先 vector:内存连续、缓存友好、综合性能最优。
- 哈希容器注意哈希冲突、rehash 导致迭代器失效。
- 链表容器放弃随机访问,换取增删效率。
🔥 本章综合高频追问
-
问 :vector 提前 reserve 和 resize 区别?
答:reserve 只预留容量,不创建元素;resize 改变有效元素个数,会构造/销毁元素。
-
问 :为什么 list 不支持 std::sort?
答:sort 依赖随机访问迭代器,list 仅支持双向迭代器。
-
问 :哈希冲突怎么解决?
答 :STL unordered 系列采用链地址法,冲突元素挂在哈希桶链表上。
📝 模块总结
本模块聚焦 STL 容器底层、性能、坑点与算法,是笔试和手写代码主力考点。核心掌握:容器底层结构、复杂度、迭代器失效、选型依据。
