C++ STL

C++ STL

前言

C++ STL(Standard Template Library,标准模板库)是 C++ 内置泛型数据结构+算法库,基于模板实现,开箱即用,是C++开发、后端、客户端面试核心必考内容。


一、STL 六大核心组件

  1. 容器 Containers:存储数据的各类数据结构
  2. 迭代器 Iterators:容器与算法的桥梁,统一遍历接口
  3. 算法 Algorithms:排序、查找、拷贝、去重、遍历等通用算法
  4. 函数对象 Functors :重载()的类,用于算法自定义比较规则
  5. 适配器 Adapters:对现有容器封装,提供 stack/queue 专属接口
  6. 空间配置器 Allocators:底层负责内存申请、释放、内存池优化

二、STL 容器整体分类

  1. 序列式容器vectordequelistarrayforward_list
  2. 关联式容器set/multisetmap/multimap(底层红黑树
  3. 无序关联容器unordered_set/unordered_map 等(底层哈希表
  4. 容器适配器stackqueuepriority_queue

三、迭代器 Iterator 详解

1. 迭代器本质

迭代器 = 封装后的智能指针,屏蔽不同容器底层差异,提供统一遍历语法。

2. 迭代器五类

输入迭代器、输出迭代器、单向迭代器、双向迭代器、随机访问迭代器。

3. 常用迭代器类型

  • iterator:可读可写
  • const_iterator:只读,不可修改元素
  • reverse_iterator:反向遍历
  • auto:C++11 自动类型推导,简化写法

4. 高频面试题 + 标准答案

1)迭代器失效是什么?哪些操作会导致失效?

答案

迭代器失效指容器元素地址、内存布局发生变化,原有迭代器、指针、引用变成野指针 ,解引用会崩溃。

常见失效操作:

  • vector:扩容、insert、erase、clear
  • deque:中间插入删除、部分扩容
  • map/set:仅被删除的那个迭代器失效,其余不变
  • list:仅被删除节点迭代器失效

2)vector、list、map 迭代器失效区别?

答案

  • vector:插入/删除/扩容,全部迭代器失效
  • list:erase 仅当前迭代器失效,其他全部有效
  • map/set:erase 仅被删元素迭代器失效,其余不变

3)const_iterator 和 iterator 区别?

答案

  • iterator:可读可写,能修改指向元素
  • const_iterator:只读,禁止修改元素,更安全,适合只读遍历

4)反向迭代器原理?

答案

反向迭代器是正向迭代器适配器,以左闭右开 区间反向遍历,rbegin() 指向末尾,rend() 指向开头。

5. 易错点总结

  1. 遍历中erase元素后,原迭代器立即失效,不能再 ++
  2. const_iterator遍历时尝试修改元素,直接编译报错
  3. list/map 是双向迭代器,不支持 it + n,只有 vector/deque 随机访问迭代器支持

四、vector 动态数组

核心特点

  • 连续内存、随机访问 O(1)
  • 尾部增删高效,中间插入删除需元素移位,效率低
  • 独有 size / capacity / reserve / resize

高频面试题 + 标准答案

1)vector size 和 capacity 区别?

答案

  • size():容器实际已存储元素个数
  • capacity():容器已分配内存可容纳最大元素数
    size ≤ capacity;capacity 不随元素增减实时变化,只在扩容时变大。

2)reserve 和 resize 区别?

答案

  • reserve(n):只预分配内存,不创建元素、不改变 size,只增大 capacity
  • resize(n):改变实际元素个数,多余位置默认初始化,会修改 size

3)vector 扩容机制?默认扩容多少?

答案

vector 内存满时触发扩容:

  1. 开辟一块更大新内存
  2. 旧元素拷贝到新空间
  3. 释放旧内存
  4. 更新迭代器与内部指针
    扩容策略:
  • GCC:1.5 倍扩容
  • VS:2 倍扩容

4)vector 迭代器什么时候失效?

答案

  • 容量扩容:所有迭代器全部失效
  • insert/erase 中间位置:当前及后面所有迭代器失效
  • clear、swap 也会导致原有迭代器失效

5)vector 底层实现原理?

答案

底层维护三个指针:

  • _First:起始地址
  • _Last:当前末尾元素
  • _End:内存容量末尾
    靠三块指针实现 size、capacity、遍历、扩容。

6)为什么 vector 中间插入效率低?

答案

连续内存,中间插入需要后续元素整体后移 ;删除需要后续元素前移,数据拷贝开销大,时间复杂度 O(n)。

易错点总结

  1. reserve 只分配内存,无有效元素,不能直接用 [] 访问
  2. 循环大量 push_back 不提前 reserve,频繁扩容严重影响性能
  3. insert/erase 后原有迭代器、引用、指针全部失效
  4. [] 不做越界检查,越界直接崩溃;at() 越界抛异常
  5. clear() 只清空元素,不释放容量内存

五、deque 双端队列

核心特点

  • 分段连续内存,由多个小缓冲区 + 中控数组管理
  • 头尾增删 O(1),支持随机访问
  • 无 capacity 概念,不占用整块连续大内存

高频面试题 + 标准答案

1)deque 底层实现原理?

答案

采用分段缓冲区 + 中控映射表

  • 多个固定大小小缓冲区存数据
  • 中控数组保存每个缓冲区地址
  • 头尾增删只需新开/释放缓冲区,无需整体拷贝

2)deque 和 vector 区别、适用场景?

答案

  • vector:整块连续内存,随机访问最快,适合大量随机访问、尾部操作
  • deque:分段内存,头尾增删极快,适合频繁头尾插入删除

3)为什么 stack/queue 默认底层用 deque?

答案

  1. deque 头尾操作效率极高
  2. 无需连续大块内存,内存利用率高
  3. 支持动态扩容,不需要像 vector 那样整体拷贝

易错点总结

  1. 虽支持随机访问,但内存分段,访问效率略低于 vector
  2. 不适合中间频繁插入删除
  3. 无 capacity、reserve 概念,不能预分配容量

六、list 双向循环链表

核心特点

  • 不连续内存,双向循环链表
  • 任意位置插入删除 O(1)
  • 不支持随机访问,无 []、不支持迭代器 +n

高频面试题 + 标准答案

1)list 底层结构?为什么不支持随机访问?

答案

底层双向循环链表,每个节点分散在堆内存,只能通过前驱后继指针逐个遍历,无法通过偏移量直接寻址,故不支持随机访问。

2)list 迭代器会失效吗?什么情况失效?

答案

list 迭代器极不容易失效

只有 erase 当前节点时,该节点迭代器失效,其余所有迭代器、引用完全不受影响。

3)list 和 vector 怎么选择?

答案

  • 频繁随机访问、元素遍历多 → 选 vector
  • 任意位置频繁插入删除、不在乎随机访问 → 选 list

4)list sort 为什么不用全局 sort?

答案

全局 sort 要求随机访问迭代器 ,list 只有双向迭代器,不满足;

list 提供成员函数 sort,采用归并排序,适配链表结构,无需内存拷贝。

易错点总结

  1. 不能用 [] 下标访问,编译直接报错
  2. 遍历只能用 ++/--,禁止 it+1、it+2 这种写法
  3. 链表每个节点有额外指针开销,内存占用比 vector 大

七、set / multiset 集合(红黑树)

核心特点

  • 底层红黑树,元素自动升序排序
  • set 元素唯一;multiset 允许重复元素
  • 元素默认 const,不可直接修改

高频面试题 + 标准答案

1)set 底层为什么用红黑树?

答案

红黑树是自平衡二叉搜索树

  1. 保证插入删除后仍大致平衡,查找、插入、删除稳定 O(logn)
  2. 相比普通二叉搜索树,避免极端退化成链表
  3. 平衡代价低于 AVL 树,旋转次数少,性能更优

2)set 和 multiset 区别?

答案

  • set:元素唯一不可重复
  • multiset:允许多个相同元素,其余底层、接口完全一致

3)set 元素为什么不能修改?

答案

set 按元素值排序,若允许修改元素值,会破坏红黑树有序性和平衡结构,导致容器逻辑错乱;如需修改只能先删除再插入新值。

4)set 查找时间复杂度?

答案

底层红黑树,查找、插入、删除均为 O(logn)

易错点总结

  1. 遍历结果自动有序,和插入顺序无关
  2. 遍历中尝试修改 set 元素,编译报错
  3. multiset 可用 count 统计重复元素个数
  4. 不能用 std::sort 对 set 排序,本身已有序

八、map / multimap 键值对(红黑树)

核心特点

  • key-value 结构,按 key 自动排序
  • map:key 唯一;multimap:key 可重复
  • key 不可修改,value 可自由修改

高频面试题 + 标准答案

1)map 底层红黑树原理?

答案

和 set 一样底层红黑树,只是存储结构从单个元素改成 pair<K,V>,按 pair 的 first(key)排序、维护平衡。

2)map [] 运算符底层做了什么?

答案

若 key 存在:返回对应 value 引用;

若 key 不存在:自动插入该 key,并给 value 赋默认初始值,再返回引用。

3)map 和 multimap 区别?

答案

  • map:key 唯一,不允许重复键,支持 []
  • multimap:允许同一个 key 对应多个 value,不支持 [] 运算符

4)map 迭代器失效场景?

答案

erase 时仅被删除的迭代器失效,其他迭代器、引用全部有效;插入不会导致任何迭代器失效。

5)红黑树相比 AVL 树优势?

答案

红黑树是弱平衡 ,旋转调整次数更少,插入删除性能更高;

AVL 树高度平衡,旋转多,查找略快、修改代价大;STL 优先选红黑树。

易错点总结

  1. 慎用 mp[key],不存在会自动插入脏数据
  2. 遍历只能修改 p.second,严禁修改 p.first
  3. multimap 没有重载 [],只能用 insert / find 取值

九、unordered_set / unordered_map 无序容器(哈希表)

核心特点

  • 底层哈希表(链地址法解决冲突)
  • 增删查平均 O(1),性能优于红黑树
  • 元素无序,不自动排序

高频面试题 + 标准答案

1)unordered_map 底层哈希表实现?

答案

底层为数组 + 链表拉链法:

  • 数组每个位置是一个桶
  • 哈希值映射到桶下标
  • 同一桶冲突元素用链表串联

2)哈希冲突怎么解决?STL 用哪种?

答案

常见方案:链地址法、开放定址法、再哈希;

STL unordered_map 默认采用 链地址法(拉链法)

3)map 和 unordered_map 区别、性能对比?

答案

特性 map unordered_map
底层 红黑树 哈希表
有序性 按 key 自动有序 无序
复杂度 O(logn) 平均 O(1)
内存占用 较小 哈希桶占用更高
迭代器失效 极少失效 rehash 全部失效

4)哈希表负载因子作用?

答案

负载因子 = 元素个数 / 桶数量;

负载因子过大,哈希冲突变多、链表变长、性能下降;

超过阈值自动触发 rehash,扩容桶数量、重新映射元素。

5)什么时候用 map,什么时候用 unordered_map?

答案

  • 需要 key 有序、遍历有序 → 用 map
  • 只做高频查找、不要求有序、追求速度 → 用 unordered_map

易错点总结

  1. 遍历顺序既不按插入顺序,也不按 key 大小
  2. 自定义结构体作 key,必须重载哈希函数 + == 运算符
  3. rehash 后所有迭代器全部失效
  4. 有序业务场景不要强行用无序容器

十、容器适配器 stack / queue / priority_queue

1. stack 栈 LIFO

高频面试题 + 答案

1)stack 底层为什么默认 deque?

答案

deque 头尾操作高效、内存不连续无需大块空间、扩容无整体拷贝,适合栈的后进先出特性。

2)stack 可以用 vector 做底层吗?有什么优缺点?

答案

可以。

优点:连续内存,访问快;

缺点:vector 扩容需要整体拷贝,栈扩容性能不如 deque。

易错点

stack 只能访问栈顶元素,不支持遍历内部所有元素

2. queue 队列 FIFO

面试题:queue 为什么不选用 vector 做底层?

答案

queue 需要频繁头部出队,vector 头部删除要整体前移,O(n) 效率极低;deque 头删 O(1),更适合队列。

易错点

只能访问队首 front、队尾 back,不支持随机访问和中间遍历。

3. priority_queue 优先队列

面试题 + 答案

1)优先队列底层是堆吗?大顶堆小顶堆实现?

答案

底层基于 vector 实现二叉堆

默认是大顶堆;传入 greater 可改为小顶堆。

2)自定义结构体怎么重载比较规则?

答案

重载 < 运算符 或 自定义仿函数 / lambda,传入优先队列模板参数。

易错点

默认降序大顶堆,想要升序小顶堆必须手动指定比较器。


十一、STL 常用算法 & 易错点

头文件:#include <algorithm>

常用算法:sort、reverse、find、count、unique、max_element、min_element

易错点

  1. unique 只做逻辑去重,必须先 sort 才能真正去重
  2. sort 默认升序,自定义排序需传仿函数/lambda
  3. stack、queue 无迭代器,不能直接用 STL 算法

十二、容器选择速查表(面试必背)

业务场景 首选容器
大量随机访问、只尾部增删 vector
头尾频繁插入删除 deque
任意位置频繁增删、不随机访问 list
元素自动排序、去重存储 set
键值对、需要按键有序遍历 map
高频查找、不要求有序、追求性能 unordered_map
后进先出栈结构 stack
先进先出队列结构 queue
取极值、堆排序任务 priority_queue
相关推荐
geovindu3 小时前
go:Template Method Pattern
开发语言·后端·设计模式·golang·模板方法模式
瞎折腾啥啊3 小时前
vcpkg与CMake
linux·c++·cmake·cmakelists
wljy14 小时前
牛客每日一题(2026.4.30) 整数域二分
c语言·c++·算法·蓝桥杯·二分
卷Java4 小时前
上下文压缩
开发语言·windows·python
白晨并不是很能熬夜4 小时前
【RPC】第 4 篇:服务发现 — Zookeeper + 缓存容错
java·后端·程序人生·缓存·zookeeper·rpc·服务发现
EvenBoy4 小时前
IDEA中使用CodeX
java·ide·intellij-idea
日取其半万世不竭4 小时前
Minecraft Java版社区服搭建教程(Windows版)
java·开发语言·windows
wjs20244 小时前
HTML 文本格式化
开发语言
逸Y 仙X4 小时前
文章十六:ElasticSearch 使用enrich策略实现大宽表
java·大数据·数据库·elasticsearch·搜索引擎·全文检索