【C++】面试:STL容器与算法

笔试选择、代码手撕、性能分析核心考点,容器底层、区别对比、迭代器失效为面试重灾区,考点固定、踩分点明确,直接背诵使用。


1. vector 底层原理与扩容机制

1.1 底层结构

基于动态连续数组 实现,内存空间连续,使用三段指针管理:_start_finish_end_of_storage

  • 容量 (capacity):总分配内存大小
  • 大小 (size):当前有效元素个数

1.2 扩容机制

  1. 当元素个数达到容量上限,触发自动扩容。
  2. 扩容规则:
    • Linux/GCC:原容量 2 倍扩容
    • Windows/VS:小于 1024 字节 2 倍扩容,超过后1.5 倍扩容
  3. 扩容流程:开辟新内存 → 拷贝原元素 → 释放旧内存 → 更新内部指针。
  4. 特性:扩容会导致迭代器、指针、引用全部失效

1.3 优缺点

  • 优点:随机访问效率高(下标访问 O(1))、内存连续、缓存命中率高。
  • 缺点:头部/中间插入删除效率低(元素整体搬迁);扩容存在内存拷贝开销。

高频追问

:为什么不原地扩容?

:堆内存地址不保证后方有连续空闲空间,只能异地开辟新空间。


2. string 实现与常见坑点

2.1 底层实现

本质是封装后的char型动态数组,多数 STL 实现采用短字符串优化 (SSO):短字符串直接存于对象内部,不申请堆内存;长字符串才使用堆空间。内部同样维护容量、大小指针。

2.2 常用接口

增删查改、+=拼接、c_str()获取 C 风格字符串、substr截取子串等。

2.3 高频坑点

  1. c_str()返回指针:string 重新分配内存后,原指针失效。
  2. 拼接大量字符串:频繁扩容拷贝,建议提前reserve预留空间优化性能。
  3. 区分size()length():二者等价,均返回字符个数,不含末尾\0
  4. 空字符\0:string 可存储\0,不会截断;C 风格字符串遇到\0直接截断。

3. list 特点、与 vector 对比

3.1 list 底层结构

基于双向循环链表实现,内存不连续,每个节点保存数据、前驱指针、后继指针。

3.2 核心特点

  1. 任意位置插入、删除元素 O(1) 效率,仅修改指针。
  2. 不支持随机访问,仅能遍历访问,访问效率 O(n)。
  3. 插入/删除元素不会导致迭代器失效(被删除节点的迭代器除外)。
  4. 内存碎片化,CPU 缓存命中率低。

3.3 list vs vector 核心对比

  1. 访问:vector 支持随机访问;list 仅顺序访问。
  2. 增删:头尾/中间增删 list 更快;vector 元素搬迁,效率低。
  3. 内存:vector 连续;list 节点分散,额外存储指针,内存开销大。
  4. 迭代器:vector 扩容/增删易失效;list 迭代器稳定性高。

使用建议

频繁查询、少量增删选 vector;频繁任意位置增删、遍历场景选 list。


4. deque 底层结构与适用场景

4.1 底层结构

分段连续内存,由多个小块缓冲区 + 中控映射表组成。整体不连续,每一小块内部内存连续。

4.2 核心特性

  1. 支持首尾高效插入删除 O(1),中间增删效率低。
  2. 支持随机访问,效率弱于 vector。
  3. 扩容代价小,无需整体拷贝数据。
  4. 迭代器比 vector 复杂,失效规则介于 vector 与 list 之间。

4.3 适用场景

  1. 作为 stack、queue 的默认底层容器。
  2. 需要频繁在头尾操作元素,又偶尔随机访问的场景。

补充

不推荐作为常��存储容器,功能折中,综合性能不如 vector/list。


5. map 与 unordered_map 区别

5.1 底层实现

  • map :基于红黑树(平衡二叉搜索树)
  • unordered_map :基于哈希表(散列表)

5.2 核心差异

  1. 有序性 :map 按键值自动升序排序;unordered_map 无序,顺序由哈希算法决定。
  2. 时间复杂度:map 增删查 O(logn);unordered_map 平均 O(1),最坏 O(n)(哈希冲突)。
  3. 底层开销:map 节点存储父子指针,内存开销大;哈希表存在哈希桶、负载因子,有额外开销。
  4. 迭代器:map 迭代器遍历有序;unordered_map 遍历无序。
  5. 键要求 :map 需要键支持比较运算符;unordered_map 需要哈希函数 + 相等判断。

选型

追求查询速度、不要求有序 → unordered_map;要求有序遍历、稳定性优先 → map。


6. set 与 unordered_set 差异

6.1 底层实现

  • set:红黑树,元素自动去重 + 有序。
  • unordered_set:哈希表,元素去重、无序。

6.2 核心区别

  1. 有序性:set 有序;unordered_set 无序。
  2. 效率:set 读写 O(logn);unordered_set 平均 O(1)。
  3. 元素特性:二者均元素唯一、不可修改键/元素本身
  4. 底层依赖: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 通用规避方案

  1. 操作后重新获取迭代器,不复用旧迭代器。
  2. 尽量使用返回值接收新迭代器(如 erase 返回下一个迭代器)。
  3. 遍历过程中避免大范围增删元素。

8. STL 空间配置器简单理解

8.1 作用

STL 容器不直接调用new/delete,而是通过空间配置器 (allocator) 统一管理内存分配、释放、构造、析构。

8.2 双层架构(SGI STL)

  1. 一级配置器 :直接调用系统malloc/free,应对大块内存、异常处理。
  2. 二级配置器 :针对小块内存,维护内存池,减少频繁系统调用,降低内存碎片。

8.3 核心优势

  1. 屏蔽底层内存接口,容器与内存管理解耦。
  2. 小块内存使用内存池,提升分配效率、减少碎片。
  3. 支持自定义配置器,适配特殊内存场景。

面试结论

日常开发几乎不用手写配置器,理解内存池 + 分层管理核心思想即可。


9. 常用算法 sort、find 原理

9.1 std::sort

  1. 底层组合算法:内省排序 (Introsort)
    • 主体:快速排序
    • 递归深度过大:转为堆排序
    • 数据量极小:转为插入排序
  2. 特性:不稳定排序;时间复杂度 O(nlogn);可自定义比较规则。
  3. 使用范围:支持随机访问迭代器(vector、deque),不支持 list

9.2 std::find

  1. 底层:顺序遍历查找,逐个元素比对。
  2. 时间复杂度 O(n),所有容器通用。
  3. 返回值:找到返回目标元素迭代器;未找到返回容器end()

补充

有序容器优先使用 binary_search 二分查找(O(logn)),效率远高于 find。


10. 容器选择实战准则

快速选型口诀

  1. 需随机访问 → 优先 vector
  2. 频繁头尾操作 → deque / queue / stack
  3. 频繁任意位置增删 → list
  4. 键值对存储、要求有序 → map
  5. 键值对、追求查询速度、无序 → unordered_map
  6. 元素去重、有序 → set
  7. 元素去重、无序、高速访问 → unordered_set

通用原则

  • 优先 vector:内存连续、缓存友好、综合性能最优。
  • 哈希容器注意哈希冲突、rehash 导致迭代器失效。
  • 链表容器放弃随机访问,换取增删效率。

🔥 本章综合高频追问

  1. :vector 提前 reserve 和 resize 区别?

    :reserve 只预留容量,不创建元素;resize 改变有效元素个数,会构造/销毁元素。

  2. :为什么 list 不支持 std::sort?

    :sort 依赖随机访问迭代器,list 仅支持双向迭代器。

  3. :哈希冲突怎么解决?

    :STL unordered 系列采用链地址法,冲突元素挂在哈希桶链表上。


📝 模块总结

本模块聚焦 STL 容器底层、性能、坑点与算法,是笔试和手写代码主力考点。核心掌握:容器底层结构、复杂度、迭代器失效、选型依据

相关推荐
凡人叶枫1 小时前
Effective C++ 条款33:避免遮掩继承而来的名字
linux·服务器·开发语言·c++·嵌入式开发
10岁的博客1 小时前
NOIP2010普及组「接水问题」详解:模拟算法与优先队列解法
开发语言·c++·算法
凡人叶枫1 小时前
Effective C++ 条款31:将文件间的编译依存关系降至最低
linux·开发语言·c++·php·嵌入式开发·effective c++
彼岸星光ぐ>1 小时前
排序算法对比
数据结构·算法·排序算法
liulilittle1 小时前
整数溢出陷阱:用除法安全比较乘积
c++
dengyuezhe80602 小时前
《C++ 异常机制与智能指针:从原理到实现》
android·java·c++
于指尖飞舞2 小时前
java后端面试题(常用集合极简)
java·开发语言·面试
YHHLAI2 小时前
LeetCode 1.两数之和 | 从暴力枚举到线性优化
算法·leetcode·职场和发展