C++ STL
前言
C++ STL(Standard Template Library,标准模板库)是 C++ 内置泛型数据结构+算法库,基于模板实现,开箱即用,是C++开发、后端、客户端面试核心必考内容。
一、STL 六大核心组件
- 容器 Containers:存储数据的各类数据结构
- 迭代器 Iterators:容器与算法的桥梁,统一遍历接口
- 算法 Algorithms:排序、查找、拷贝、去重、遍历等通用算法
- 函数对象 Functors :重载
()的类,用于算法自定义比较规则 - 适配器 Adapters:对现有容器封装,提供 stack/queue 专属接口
- 空间配置器 Allocators:底层负责内存申请、释放、内存池优化
二、STL 容器整体分类
- 序列式容器 :
vector、deque、list、array、forward_list - 关联式容器 :
set/multiset、map/multimap(底层红黑树) - 无序关联容器 :
unordered_set/unordered_map等(底层哈希表) - 容器适配器 :
stack、queue、priority_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. 易错点总结
- 遍历中
erase元素后,原迭代器立即失效,不能再 ++ - 用
const_iterator遍历时尝试修改元素,直接编译报错 - 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 内存满时触发扩容:
- 开辟一块更大新内存
- 旧元素拷贝到新空间
- 释放旧内存
- 更新迭代器与内部指针
扩容策略:
- GCC:1.5 倍扩容
- VS:2 倍扩容
4)vector 迭代器什么时候失效?
答案:
- 容量扩容:所有迭代器全部失效
- insert/erase 中间位置:当前及后面所有迭代器失效
- clear、swap 也会导致原有迭代器失效
5)vector 底层实现原理?
答案 :
底层维护三个指针:
- _First:起始地址
- _Last:当前末尾元素
- _End:内存容量末尾
靠三块指针实现 size、capacity、遍历、扩容。
6)为什么 vector 中间插入效率低?
答案 :
连续内存,中间插入需要后续元素整体后移 ;删除需要后续元素前移,数据拷贝开销大,时间复杂度 O(n)。
易错点总结
- reserve 只分配内存,无有效元素,不能直接用 [] 访问
- 循环大量 push_back 不提前 reserve,频繁扩容严重影响性能
- insert/erase 后原有迭代器、引用、指针全部失效
[]不做越界检查,越界直接崩溃;at()越界抛异常- clear() 只清空元素,不释放容量内存
五、deque 双端队列
核心特点
- 分段连续内存,由多个小缓冲区 + 中控数组管理
- 头尾增删 O(1),支持随机访问
- 无 capacity 概念,不占用整块连续大内存
高频面试题 + 标准答案
1)deque 底层实现原理?
答案 :
采用分段缓冲区 + 中控映射表:
- 多个固定大小小缓冲区存数据
- 中控数组保存每个缓冲区地址
- 头尾增删只需新开/释放缓冲区,无需整体拷贝
2)deque 和 vector 区别、适用场景?
答案:
- vector:整块连续内存,随机访问最快,适合大量随机访问、尾部操作
- deque:分段内存,头尾增删极快,适合频繁头尾插入删除
3)为什么 stack/queue 默认底层用 deque?
答案:
- deque 头尾操作效率极高
- 无需连续大块内存,内存利用率高
- 支持动态扩容,不需要像 vector 那样整体拷贝
易错点总结
- 虽支持随机访问,但内存分段,访问效率略低于 vector
- 不适合中间频繁插入删除
- 无 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,采用归并排序,适配链表结构,无需内存拷贝。
易错点总结
- 不能用
[]下标访问,编译直接报错 - 遍历只能用 ++/--,禁止 it+1、it+2 这种写法
- 链表每个节点有额外指针开销,内存占用比 vector 大
七、set / multiset 集合(红黑树)
核心特点
- 底层红黑树,元素自动升序排序
- set 元素唯一;multiset 允许重复元素
- 元素默认 const,不可直接修改
高频面试题 + 标准答案
1)set 底层为什么用红黑树?
答案 :
红黑树是自平衡二叉搜索树:
- 保证插入删除后仍大致平衡,查找、插入、删除稳定 O(logn)
- 相比普通二叉搜索树,避免极端退化成链表
- 平衡代价低于 AVL 树,旋转次数少,性能更优
2)set 和 multiset 区别?
答案:
- set:元素唯一不可重复
- multiset:允许多个相同元素,其余底层、接口完全一致
3)set 元素为什么不能修改?
答案 :
set 按元素值排序,若允许修改元素值,会破坏红黑树有序性和平衡结构,导致容器逻辑错乱;如需修改只能先删除再插入新值。
4)set 查找时间复杂度?
答案 :
底层红黑树,查找、插入、删除均为 O(logn)。
易错点总结
- 遍历结果自动有序,和插入顺序无关
- 遍历中尝试修改 set 元素,编译报错
- multiset 可用 count 统计重复元素个数
- 不能用 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 优先选红黑树。
易错点总结
- 慎用
mp[key],不存在会自动插入脏数据 - 遍历只能修改 p.second,严禁修改 p.first
- 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
易错点总结
- 遍历顺序既不按插入顺序,也不按 key 大小
- 自定义结构体作 key,必须重载哈希函数 + == 运算符
- rehash 后所有迭代器全部失效
- 有序业务场景不要强行用无序容器
十、容器适配器 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
易错点
- unique 只做逻辑去重,必须先 sort 才能真正去重
- sort 默认升序,自定义排序需传仿函数/lambda
- stack、queue 无迭代器,不能直接用 STL 算法
十二、容器选择速查表(面试必背)
| 业务场景 | 首选容器 |
|---|---|
| 大量随机访问、只尾部增删 | vector |
| 头尾频繁插入删除 | deque |
| 任意位置频繁增删、不随机访问 | list |
| 元素自动排序、去重存储 | set |
| 键值对、需要按键有序遍历 | map |
| 高频查找、不要求有序、追求性能 | unordered_map |
| 后进先出栈结构 | stack |
| 先进先出队列结构 | queue |
| 取极值、堆排序任务 | priority_queue |