如果是 C++ / UE / 游戏客户端面试,STL 不需要把整个标准库背完,真正高频的是:容器底层结构 + 时间复杂度 + 迭代器失效 + 常用算法 + C++11 之后的移动/emplace + map/set 与 unordered 系列区别。
你可以把 STL 准备分成 6 块。
1. 先把 STL 整体框架说清楚
STL 主要由容器、迭代器、算法、函数对象、适配器、分配器等部分组成。容器负责存数据,迭代器负责统一访问,算法通过迭代器操作容器,从而做到算法和数据结构解耦。
最核心的三个:
- Container 容器
- Iterator 迭代器
- Algorithm 算法
比如:
std::vector<int> nums = {3, 1, 2};
std::sort(nums.begin(), nums.end());
这里:
vector ↓ 容器 begin/end ↓ 迭代器 sort ↓ 算法
2. 容器是绝对重点
至少要熟练这几个:
- vector
- list
- deque
- map
- set
- unordered_map
- unordered_set
- stack
- queue
- priority_queue
其中面试最重要的是:
- vector
- map / set
- unordered_map / unordered_set
3. vector:STL 面试第一大户
先记本质:
vector 底层是动态连续数组。
例如:
std::vector<int> v;
内存大致:
[1][2][3][4][5]
连续排列。
所以支持:
v[3]
O(1) 随机访问。
vector 为什么尾插快,中间插入慢?
尾部:
v.push_back(10);
如果还有容量:
直接放到最后
O(1)
中间:
v.insert(v.begin() + 2, 10);
后面的元素都得往后移动:
1 2 3 4 5
插入 9
1 2 9 3 4 5
↑ ↑ ↑
后面全部移动
所以:
- 随机访问 O(1)
- 尾插 平均 O(1)
- 中间插入 O(n)
- 中间删除 O(n)
注意是:
push_back 均摊 O(1),不是永远 O(1)。
4. vector 的扩容机制
非常高频。
假设:
v.size() == v.capacity()
再:
v.push_back(x);
容量不够。
vector 会:
- 申请更大的连续内存
- 把旧元素拷贝/移动过去
- 释放旧内存
- 插入新元素
大概:
原来: [1][2][3][4] 容量不够 ↓ 新内存: [1][2][3][4][ ][ ][ ][ ]
至于具体扩容倍率:
C++ 标准没有规定必须是 1.5 倍还是 2 倍,由具体实现决定。
这个别背死。
5. size 和 capacity 区别
v.size();
v.capacity();
size:
当前实际有多少个元素。
capacity:
当前已经申请的内存最多能放多少元素而不用再次扩容。
例如:
size = 5
capacity = 8
表示:
[1][2][3][4][5][ ][ ][ ]
6. reserve 和 resize 的区别
这是经典题。
v.reserve(100);
意思:
提前申请至少能容纳 100 个元素的容量。
通常:
- capacity 改变
- size 不变
而:
v.resize(100);
表示:
把实际元素数量调整为 100。
于是:
- size 改变
而:
v.resize(100);
表示:
把实际元素数量调整为 100。
于是:
- size 改变
如果变大,还会构造新元素。
一句话:
reserve
→ 改容量
resize
→ 改元素数量
7. vector 迭代器为什么会失效?
这一块面试特别爱问。
例如:
std::vector<int> v = {1,2,3};
auto it = v.begin();
v.push_back(4);
如果 push_back 导致扩容:
原内存释放
↓
整个 vector 搬家
那么:
it
还指向旧内存。
于是失效。
所以:
vector 发生重新分配时,原来的指针、引用、迭代器都会失效。
如果没有扩容,中间插入或删除,也可能导致插入/删除位置及之后的迭代器失效。
这个要会。
8. vector 和 list 区别
第二高频。
vector:
- 连续内存
- 随机访问 O(1)
- 中间插删 O(n)
- 缓存友好
list:
- 双向链表
- 内存不连续
- 随机访问 O(n)
- 已知位置插删 O(1)
- 缓存局部性差
画出来:
vector: [1][2][3][4] list: [1] ↔ [2] ↔ [3] ↔ [4]
面试官问:
插删很多是不是一定选 list?
不要直接说是。
现代 CPU 上 vector 的缓存局部性很好,即使理论上移动元素,很多实际场景仍然可能比 list 快。
所以更好的回答:
如果需要高频随机访问,优先 vector;如果确实需要在已知节点位置频繁插删且稳定迭代器,才考虑 list。
9. deque 是什么
deque:
双端队列。
支持:
- push_front
- push_back
- pop_front
- pop_back
通常都比较高效。
它不是简单的“一整块连续数组”。
可以粗略理解成:
索引结构 ↓ [块1] [块2] [块3] [块4]
分段连续。
所以:
deque[i]
仍然支持随机访问。
10. map / set 底层
这个必须背。
一般实现:
map
set
multimap
multiset
↓
红黑树
因此:
- 查找 O(log n)
- 插入 O(log n)
- 删除 O(log n)
并且:
元素天然有序。
11. map 和 set 区别
set:
std::set<int>
保存:
key
而 map:
std::map<int, std::string>
保存:
key-value
例如:
map<int, string> players;
players[1001] = "Tom";
本质:
1001 → Tom
12. 为什么 set 的 key 不能修改?
你之前也问过类似问题。
因为 set 通常基于有序树。
假设:
10
/ \
5 20
如果你直接把:
5 → 100
树的有序关系直接被破坏:
10
/
100
错误。
所以:
set 中元素本质上承担 key 的角色,不能随意原地修改。
map 的:
pair<const Key, Value>
key 也是 const。
13. unordered_map / unordered_set 底层
一般:
哈希表
例如:
std::unordered_map<int, string>
查找大致:
key ↓ hash(key) ↓ 计算桶位置 ↓ 在 bucket 中查找
平均复杂度:
- 查找 O(1)
- 插入 O(1)
- 删除 O(1)
但最坏情况:
O(n)
平均复杂度:
- 查找 O(1)
- 插入 O(1)
- 删除 O(1)
但最坏情况:
O(n)
14. map 和 unordered_map 区别
高频到不能再高频。
| map | unordered_map |
|---|---|
| 红黑树 | 哈希表 |
| O(log n) | 平均 O(1) |
| 有序 | 无序 |
| 不需要 hash | key 需要可哈希 |
| 内存通常相对规整 | 通常有 bucket 等额外开销 |
一句话:
需要有序遍历、范围查询用 map;主要追求平均快速查找通常考虑 unordered_map。
15. unordered_map 为什么最坏 O(n)
因为哈希冲突。
理想:
bucket0 → A bucket1 → B bucket2 → C
极端:
bucket0 → A → B → C → D → E
所有 key 都碰到同一个桶。
那么查找退化成遍历:
O(n)
16. 什么是哈希冲突?
两个不同 key:
hash(key1)
hash(key2)
最终落到同一个 bucket。
就叫:
哈希冲突。
常见解决:
- 链地址法
- 开放寻址
标准库具体实现不要求固定方案,不过常见 unordered_* 实现使用 bucket 结构。
17. load_factor 和 rehash
稍微深入一点就会问。
unordered_map
桶越来越满:
元素越来越多
↓
冲突越来越多
↓
查找性能下降
所以达到一定负载后会:
rehash
重新增加 bucket 数量,再重新分配元素位置。
常见接口:
m.load_factor();
m.max_load_factor();
m.rehash();
m.reserve();
18. map 的 operator[] 有坑
例如:
std::map<int, std::string> m;
std::cout << m[10];
即使 key 10 原来不存在:
m[10]
也会:
插入一个默认构造的 value。
所以只是想查找:
m.find(10);
或者 C++20:
m.contains(10);
更合适。
面试官经常问:
map::operator[] 和 find() 区别?
这个要会。
19. stack / queue 本质是什么?
它们严格来说是:
容器适配器。
例如:
std::stack<int>
提供:
- push
- pop
- top
不让你随便访问内部。
默认底层通常是:
deque
queue 也是类似。
20. priority_queue
高频算法题常用。
本质:
优先队列,通常基于 heap。
默认:
std::priority_queue<int>
是大顶堆。
最大元素在:
pq.top();
常见复杂度:
- top O(1)
- push O(log n)
- pop O(log n)
21. STL 的 heap 要掌握什么
至少知道:
- make_heap
- push_heap
- pop_heap
- sort_heap
以及:
priority_queue
通常基于堆。
堆不是“完全排序”。
例如:
100
/ \
80 90
/ \
20 50
只保证:
父节点 >= 子节点
大顶堆。
所以取最大值非常快。
22. 迭代器的分类
面试中级题。
常见:
- input iterator
- output iterator
- forward iterator
- bidirectional iterator
- random access iterator
重点知道:
random access iterator
重点知道:
- vector / deque → random access iterator
- list / set / map → bidirectional iterator
- forward_list → forward iterator
这就解释了为什么:
std::sort(v.begin(), v.end());
可以。
但:
std::list<int> l;
std::sort(l.begin(), l.end()); // ❌
不行。
因为 std::sort 要求随机访问迭代器。
list 自己提供:
l.sort();
23. STL 算法必须会哪些
至少:
- sort
- find
- find_if
- count
- count_if
- for_each
- lower_bound
- upper_bound
- binary_search
- remove
- remove_if
- transform
其中面试最容易问:
- sort
- lower_bound
- remove
24. std::sort 底层是什么?
这是超级高频。
不能简单回答:
快速排序。
现代标准库实现通常采用:
introsort 思想。
粗略:
先快速排序
↓
递归太深
↓
切堆排序
↓
小区间可能用插入排序
所以兼顾平均性能和最坏情况。
面试回答可以说:
std::sort 通常使用 introsort,是快速排序、堆排序和插入排序的混合策略,复杂度要求为 O(n log n)。
25. lower_bound 是什么?
对有序区间:
std::lower_bound(begin, end, x);
返回:
第一个 >= x 的位置。
upper_bound:
第一个 > x 的位置。
例如:
1 2 2 2 5 7
找 2:
lower_bound
↓
1 [2] 2 2 5 7
upper_bound
↓
1 2 2 2 [5] 7
底层思想就是二分查找。
26. remove 为什么不真正删除元素?
经典 STL 坑。
std::vector<int> v = {1,2,3,2,4};
std::remove(v.begin(), v.end(), 2);
它不会改变:
v.size()
remove 做的是:
把需要保留的元素往前挪,然后返回新的逻辑结尾。
所以常见:
v.erase(
std::remove(v.begin(), v.end(), 2),
v.end()
);
这叫:
erase-remove idiom
C++20 之后还有:
std::erase(v, 2);
更方便。
27. emplace_back 和 push_back
C++11 高频。
v.push_back(Player(100, "Tom"));
和:
v.emplace_back(100, "Tom");
emplace_back:
直接用参数在容器内部构造对象。
它支持完美转发。
大概:
template<class... Args>
void emplace_back(Args&&... args)
{
new (...) T(std::forward<Args>(args)...);
}
所以这正好和你前面学的:
&&
std::forward
完美转发
串起来。
不过现代编译器 + 移动语义下,不要把它理解成“emplace_back 永远一定比 push_back 快”。
更准确:
更准确:
emplace_back 可以避免某些临时对象构造,并且直接转发构造参数。
28. STL 与移动语义
比如:
std::vector<std::string> v;
std::string s = "hello";
v.push_back(s);
这里是:
拷贝
如果:
v.push_back(std::move(s));
可以:
移动
于是避免复制字符串资源。
这就是为什么 C++11 后 STL 性能提升很大。
29. vector 扩容为什么优先 move?
假设:
vector<Player>
扩容:
旧地址 ↓ Player Player Player 新地址 ↓ Player Player Player _ _ _
旧对象要转过去。
如果 Player 有:
Player(Player&&) noexcept;
容器就可以高效移动资源。
这就是你之前学:
移动构造
noexcept
vector
的联系。
30. allocator 要掌握到什么程度?
初级/中级面试不用写 allocator。
但至少知道:
STL 容器通过 allocator 抽象内存分配策略,把“内存分配”和“对象构造”与容器逻辑解耦。
以前经典 allocator 模型会区分:
- allocate
- construct
- destroy
- deallocate
现代标准中实现细节和接口演进很多。
游戏开发里面,自定义内存池、arena allocator 的思想也经常和 STL allocator 联系起来。
31. 面试高频:vector 和 array 区别
std::array<int, 10>
大小:
编译期固定
而:
std::vector<int>
大小:
运行时动态变化
std::array 本质上更接近固定数组包装。
没有动态扩容。
32. 面试高频:vector 和 deque 区别
vector:
- 整体连续
- 尾部插入高效
- 中间/头部插入较慢
- 缓存友好
deque:
- 分段存储
- 头尾插入都高效
- 也支持随机访问
- 整体内存不保证连续
如果需要:
&d[0] + 1 == &d[1]
不要依赖 deque 像 vector 那样整体连续。
33. 面试高频:map 为什么用红黑树,不用普通 BST?
普通 BST 极端情况下:
1
\
2
\
3
\
4
退化成链表:
O(n)
红黑树是自平衡 BST。
保证树高度近似:
O(log n)
所以:
查找 / 插入 / 删除
O(log n)
34. 面试高频:红黑树和 AVL 区别
不用背旋转细节到特别深。
知道:
AVL
→ 平衡更严格
→ 查找性能很好
→ 插入删除调整可能更多
红黑树
→ 平衡要求相对宽松
→ 插删综合性能好
STL map/set 常见实现选择红黑树,就是因为动态增删场景综合性能比较均衡。
35. 面试高频:unordered_set 为什么 key 也不能改?
你之前就碰到过这个问题。
虽然它不像 set 一样靠排序:
unordered_set
↓
hash(key)
↓
bucket
如果直接修改 key:
原来:
hash(10) → bucket 3
你把 10 改成 100
hash(100) → 理论应该在 bucket 8
但是对象还躺在 bucket 3
哈希表结构就被破坏了。
所以 key 也不能随便修改。
36. 如果是面试,优先级怎么排?
我建议你按这个顺序学:
vector
底层
我建议你按这个顺序学:
- vector
- 底层
- size/capacity
- 扩容
- reserve/resize
- 迭代器失效
- map/set
- 红黑树
- O(log n)
- 有序
- key 为什么不能修改
- unordered_map/set
- 哈希表
- 哈希冲突
- bucket
- 平均 O(1)、最坏 O(n)
- rehash
- vector / list / deque 对比
- 迭代器
- 分类
- 失效问题
- 算法
- sort
- lower_bound
- find
- remove/erase
- C++11 STL
- move
- emplace_back
- perfect forwarding
- 适配器
- stack
- queue
- priority_queue
37. 面试前最少要能回答的 15 道题
如果下面这些你都能自己讲出来,STL 基础面试已经比较稳:
- STL 有哪几个主要组成部分?
- vector 底层是什么?
- vector 为什么随机访问 O(1)?
- vector 扩容时发生了什么?
- size 和 capacity 区别?
- reserve 和 resize 区别?
- vector 什么情况下迭代器失效?
- vector 和 list 区别?
- vector 和 deque 区别?
- map/set 底层是什么?为什么 O(log n)?
- map 和 unordered_map 怎么选?
- unordered_map 为什么平均 O(1),最坏 O(n)?
- 哈希冲突是什么?rehash 是什么?
- std::sort 大致是什么算法?
- push_back 和 emplace_back 有什么区别?
然后再加 5 道进阶:
- 为什么 set 的 key 不能改?
- 为什么 unordered_set 的 key 也不能改?
- remove 为什么不真正 erase?
- 为什么 vector 扩容会导致迭代器失效?
- 为什么移动构造最好 noexcept?
这 20 道基本就是 STL 面试主干。
最后给你一张脑图式记忆
STL
│
├─ 容器
│ ├─ vector → 动态连续数组
│ ├─ list → 双向链表
│ ├─ deque → 分段连续
│ ├─ map/set → 红黑树
│ └─ unordered_* → 哈希表
│
├─ 迭代器
│ ├─ forward
│ ├─ bidirectional
│ └─ random access
│
├─ 算法
│ ├─ sort
│ ├─ find
│ ├─ lower_bound
│ └─ remove
│
├─ 适配器
│ ├─ stack
│ ├─ queue
│ └─ priority_queue
│
└─ C++11
├─ move
├─ emplace
└─ perfect forwarding

198

被折叠的 条评论
为什么被折叠?



