C++标准库大全(STL)
1. 容器(Containers)
*问题类型:
-
序列容器(
std::vector
,std::deque
,std::list
,std::forward_list
,std::array
,std::string
):-
各自的特点、底层实现、优缺点和适用场景?
容器 特点 底层实现 优点 缺点 适用场景 std::vector
动态数组,支持快速随机访问 连续内存 + 三指针(数据头/尾/容量尾) 随机访问 O(1);缓存友好;尾部操作高效 中间插入/删除 O(n);扩容需数据拷贝 随机访问为主;尾部增删(如数据缓存) std::deque
双端队列,头尾插入高效 分段连续数组 + 中央映射表 头尾插入/删除 O(1);支持随机访问 O(1) 中间插入 O(n);内存碎片化;访问速度略慢于 vector 队列/栈需双端操作(如任务调度) std::list
双向链表,任意位置插入高效 双向链表节点(prev/data/next) 任意位置插入/删除 O(1);无扩容开销 随机访问 O(n);内存占用高;缓存不友好 频繁中间增删(如 LRU 缓存) std::forward_list
单向链表,内存更省 单向链表节点(data/next) 内存开销极低(单指针);插入/删除 O(1) 仅单向遍历;无反向迭代器;操作需前驱节点 内存敏感场景(如嵌入式系统链表) std::array
固定大小数组,编译时确定尺寸 栈上分配的 C 风格数组封装 零内存开销;随机访问 O(1);无动态分配成本 大小不可变;无动态扩容 替代 C 数组(如固定尺寸矩阵运算) std::string
动态字符串,支持丰富操作 类 vector + SSO 优化 自动内存管理;内置操作(find/replace 等) 性能略低于 char*;SSO 有大小限制 字符串处理(如文本解析/日志拼接) -
vector
扩容- 扩容因子(1.5/2倍)平衡 均摊 O(1) 时间成本 与 内存碎片问题:
- 1.5 倍:释放的内存块可被后续扩容重用(例:释放 4MB 后,1.5 倍扩容 6MB 可重用该内存)。
- 2 倍:内存重用困难(例:4MB → 8MB → 16MB,释放的 4MB+8MB 无法用于 16MB)。
- 扩容因子(1.5/2倍)平衡 均摊 O(1) 时间成本 与 内存碎片问题:
-
deque
随机访问- 计算过程:
元素位置 = 段起始地址 + 段内偏移
,比vector
多一次地址跳转。
- 计算过程:
-
SSO(短字符串优化)
-
string
对短字符串(通常 ≤15 字符)直接在栈存储,避免堆分配:std::string s = "Short"; // 栈存储 std::string l = "Long string over 15 chars"; // 堆存储
-
-
链表选择指南
- 需双向遍历 →
list
- 仅需单向遍历 + 省内存 →
forward_list
- 高频中间插入 → 优先链表;高频随机访问 → 优先
vector/deque
。
- 需双向遍历 →
-
性能临界场景
- 超高频操作(如金融交易):优先
array/vector
(缓存友好) - 内存敏感(如嵌入式):优先
forward_list/array
。
- 超高频操作(如金融交易):优先
-
-
vector
的扩容机制?为什么是 2 倍或 1.5 倍?-
扩容机制:
- 当插入元素超过当前容量时,分配新内存(原容量的
n
倍),拷贝旧数据到新空间,释放旧内存。
- 当插入元素超过当前容量时,分配新内存(原容量的
-
扩容因子(1.5或2倍)的原因:
-
均摊时间复杂度:
- 扩容因子需保证插入操作的均摊时间复杂度为
O(1)
。数学证明:当因子k > 1
时,均摊成本为O(1)
。
- 扩容因子需保证插入操作的均摊时间复杂度为
-
内存重用:
-
1.5倍:旧内存块释放后,后续扩容可能重用(新旧内存块大小无重叠)。
例如:释放
size=M
后,新申请1.5M
,后续扩容可能使用之前释放的M
内存。 -
2倍:释放的内存块总和(
M + 2M + 4M + ...
)无法被后续更大的块重用(如8M
)。
-
-
折中策略:
- 过小(如1.1倍):扩容频繁,拷贝开销大。
- 过大(如3倍):内存浪费严重。
- 主流实现:
- GCC:2倍扩容;
- VS:1.5倍扩容。
-
-
-
vector
和list
的区别?特性 vector
list
底层结构 动态数组(连续内存) 双向链表(非连续内存) 随机访问 O(1)
(支持下标访问)O(n)
(需遍历)头部插入/删除 O(n)
(移动元素)O(1)
尾部插入/删除 均摊 O(1)
O(1)
中间插入/删除 O(n)
(移动元素)O(1)
(定位后操作)内存占用 较少(仅需存储数据) 较高(每个节点含两个指针) 缓存友好性 高(连续内存) 低(内存碎片化) 扩容开销 需重新分配内存和拷贝 无扩容(动态分配节点) -
string
和char*
的区别?特性 string
char*
内存管理 自动分配/释放(RAII) 手动管理( malloc/free
)安全性 防越界(自动扩容) 易缓冲区溢出/内存泄漏 功能扩展 丰富成员函数( find
、append
等)依赖C库函数( strcpy
、strcat
)结尾标识 可包含 \0
(长度独立管理)以 \0
结尾性能开销 略高(封装成本) 极低(直接操作内存) 兼容性 C++专用 兼容C/C++ 关键结论:
- 优先使用
string
:安全、便捷,适合大多数场景。 - 使用
char*
:需兼容C或极限性能优化(如高频交易系统)。
- 优先使用
-
-
关联容器(
std::set
,std::multiset
, ,std::map
,std::multimap
):-
各自的特点、底层实现(红黑树?)、优缺点和适用场景
容器 特点 底层实现 优点 缺点 适用场景 std::set
唯一键集合,自动排序 红黑树 有序遍历;查找/插入 O(log n) 内存占用高(每个节点额外信息) 需有序唯一键(如字典) std::multiset
键可重复,自动排序 红黑树 支持重复键;有序 同 set
需有序但键可重复(如成绩排名) std::map
键值对(键唯一),按键排序 红黑树 按键有序;范围查询高效 同 set
键值映射需有序(如学生ID→信息) std::multimap
键值对(键可重复),按键排序 红黑树 支持重复键;有序 同 set
一键多值(如作者→著作列表) 底层实现核心:红黑树
- 特性:
- 自平衡二叉搜索树(保证树高 ≈ log n)。
- 节点含颜色标记(红/黑),通过旋转和变色维持平衡。
- 操作复杂度:
- 查找、插入、删除:O(log n)。
- 额外开销:
- 每个节点存储父/子指针、颜色标记(内存占用高于哈希表)。
- 特性:
-
map
和unordered_map
的区别?特性 std::map
std::unordered_map
底层实现 红黑树(平衡二叉搜索树) 哈希表(桶数组 + 链表/红黑树) 元素顺序 按键有序(升序) 无序(取决于哈希函数) 查找复杂度 O(log n) 平均 O(1),最坏 O(n) 内存占用 较低(树结构) 较高(预分配桶 + 链表指针) 键类型要求 需支持 <
或自定义比较器需支持哈希函数和 ==
比较适用场景 需有序访问/范围查询 只需快速查找(如缓存/计数器) 关键区别:
- 顺序需求:
map
:保证顺序(遍历时按键升序)。unordered_map
:元素顺序不可控(依赖哈希函数)。
- 性能权衡:
unordered_map
:平均 O(1) 查找,但哈希冲突时退化(最坏 O(n))。map
:稳定 O(log n),无性能波动。
- 内存敏感场景:
- 优先
map
(无预分配桶开销)。
- 优先
- C++11 优化:
unordered_map
桶内改用红黑树(如 GCC),最坏复杂度优化至 O(log n)。
- 顺序需求:
-
-
如何自定义容器的比较函数(
std::less
)?在模板参数中指定自定义比较类型:
// 方法1:函数对象(推荐) struct CustomCompare { bool operator()(const T& a, const T& b) const { return /* 自定义逻辑 */; // 例:a.salary > b.salary } }; std::set<T, CustomCompare> mySet; // 方法2:Lambda(C++11+) auto comp = [](const T& a, const T& b) { /* ... */ }; std::set<T, decltype(comp)> mySet(comp); // 需传递Lambda对象
在排序操作时传入比较函数:
std::vector<T> vec; // 方法1:Lambda表达式 std::sort(vec.begin(), vec.end(), [](const T& a, const T& b) { return a.id < b.id; // 按ID升序 }); // 方法2:函数指针 bool compareFunc(const T& a, const T& b) { /* ... */ } std::sort(vec.begin(), vec.end(), compareFunc);
在模板参数中指定比较类型:
// 小顶堆示例 struct Greater { bool operator()(int a, int b) const { return a > b; // 小值优先 } }; std::priority_queue<int, std::vector<int>, Greater> minHeap;
关键规则
-
无序关联容器(
std::unordered_set
,std::unordered_multiset
,std::unordered_map
,std::unordered_multimap
):-
各自的特点、底层实现(哈希表)、优缺点和适用场景?
容器 特点 底层实现 优点 缺点 适用场景 std::unordered_set
唯一键集合,无序存储 哈希表(桶+链表/红黑树) 平均 O(1) 查找/插入 最坏 O(n);内存占用高 快速去重(如URL黑名单) std::unordered_multiset
键可重复,无序存储 同上 支持重复键 同 unordered_set
词频统计(如单词计数) std::unordered_map
键值对(键唯一),无序存储 同上 平均 O(1) 键值访问 同 unordered_set
高速键值查询(如缓存) std::unordered_multimap
键值对(键可重复),无序存储 同上 支持一键多值 同 unordered_set
多值映射(如电话簿) 底层实现核心:
- 哈希表 = 桶数组(连续内存) + 冲突解决结构(链表/红黑树)
- C++11 优化:桶内元素超阈值(如 GCC 为 8)时,链表转红黑树(最坏复杂度优化至 O(log n))
-
哈希冲突的解决方法?
方法 原理 实现示例 特点 链地址法 桶内挂链表存储冲突元素 bucket[i] = head→a→b→null
简单通用;C++标准库默认方案 开放寻址法 线性探测:冲突时找下一个空桶 bucket[(hash+1)%size]
缓存友好;需负载因子控制 罗宾汉哈希 冲突时比较探测距离,抢占更远槽位 复杂优化 减少最长探测距离 关键参数:
- 负载因子 = 元素数 / 桶数(默认 1.0)
- 扩容触发:负载因子 >
max_load_factor()
时,桶数翻倍并重哈希
-
如何自定义容器的比较函数?
需定义 两个函数对象:
- 哈希函数(计算键的哈希值)
- 键相等比较(判断键是否相同)
// 示例:自定义Point类型作为键 struct Point { int x; int y; }; // 1. 自定义哈希函数 struct PointHash { size_t operator()(const Point& p) const { return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1); } }; // 2. 自定义键相等比较 struct PointEqual { bool operator()(const Point& a, const Point& b) const { return a.x == b.x && a.y == b.y; } }; // 定义容器 std::unordered_map<Point, std::string, PointHash, PointEqual> pointMap;
关键规则:
-
哈希一致性:若
a == b
,则hash(a) == hash(b)
-
性能要求:哈希函数需高效(O(1) 复杂度)
-
特化
std::hash
(可选):namespace std { template<> struct hash<Point> { /* ... */ }; }
-
-
容器配合(
std::stack
,std::queue
, ,std::priority_queue
):-
各自的特点、底层实现、优缺点和适用场景?
适配器 特点 默认底层容器 核心操作 优点 缺点 适用场景 std::stack
后进先出(LIFO) std::deque
push()
,pop()
,top()
操作简单高效(O(1)) 只能访问顶部元素 函数调用栈/撤销操作/括号匹配 std::queue
先进先出(FIFO) std::deque
push()
,pop()
,front()
头尾操作高效(O(1)) 只能访问两端元素 消息队列/缓冲区/广度优先搜索 std::priority_queue
优先级队列(最大堆) std::vector
push()
,pop()
,top()
高效获取极值(O(1)) 插入慢(O(log n)) 任务调度/事件处理/Dijkstra算法
底层实现详解
-
stack
&queue
默认容器选择-
使用
deque
(双端队列)原因:-
两端插入/删除均为 O(1)
-
内存自动扩展(避免
vector
扩容时的全量拷贝) -
示例:
std::stack<int> s; // 等价于 std::stack<int, std::deque<int>> std::queue<char> q; // 等价于 std::queue<char, std::deque<char>>
-
-
-
priority_queue
实现原理-
基于 堆结构(完全二叉树)
-
底层容器需支持:
- 随机访问(
operator[]
)→ 故用vector
- 前端插入/删除(
push_back()
,pop_back()
)
- 随机访问(
-
堆操作:
// 插入元素(上浮) vec.push_back(x); std::push_heap(vec.begin(), vec.end());// 删除顶部(下沉) std::pop_heap(vec.begin(), vec.end()); vec.pop_back();
-
自定义底层容器
// 使用 vector 作为 stack 的底层容器 std::stack<int, std::vector<int>> vecStack; // 使用 list 作为 queue 的底层容器 std::queue<int, std::list<int>> listQueue; // 使用 deque 作为 priority_queue 的底层容器 std::priority_queue<int, std::deque<int>> dequePQ;
注意限制:
stack
:需支持push_back()
,pop_back()
,back()
queue
:需支持push_back()
,pop_front()
,front()
priority_queue
:需支持随机访问迭代器 +front()
性能与选择指南
-
stack
/queue
vs 原生容器- 优先用适配器:语义明确 + 防止误操作
- 需要中间访问时 → 改用
deque
-
priority_queue
优化-
自定义比较器实现最小堆:
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
-
复杂类型场景:
struct Task { int priority; /*...*/ }; auto comp = [](const Task& a, const Task& b) { return a.priority < b.priority; }; std::priority_queue<Task, std::vector<Task>, decltype(comp)> taskQueue(comp);
-
-
内存敏感场景
-
固定大小栈 → 用
array
替代deque
:std::stack<int, std::array<int, 100>> fixedStack;
-
-
-
2. 修改算法(算法)
*问题类型:
-
常用的非式修改序列操作(
std::for_each
,std::find
,std::count
等)?算法 功能 示例 std::for_each
对每个元素执行操作 for_each(v.begin(), v.end(), print)
std::find
查找首个匹配元素 find(v.begin(), v.end(), 42)
std::find_if
查找首个满足条件的元素 find_if(v.begin(), v.end(), isEven)
std::count
统计匹配元素数量 count(v.begin(), v.end(), 'a')
std::count_if
统计满足条件的元素数量 count_if(v.begin(), v.end(), isPrime)
std::all_of
检查所有元素满足条件 (C++11) all_of(v.begin(), v.end(), isPositive)
std::any_of
检查至少一个元素满足条件 (C++11) any_of(v.begin(), v.end(), isNegative)
-
常用的非式序列操作(
std::copy
,std::remove
,std::sort
,std::unique
,std::reverse
等)?算法 功能 注意要点 std::copy
复制序列到目标位置 目标容器需预分配空间 std::remove
逻辑删除匹配元素(移动元素) 需配合 erase
物理删除(见示例↓)std::remove_if
逻辑删除满足条件的元素 同上 std::sort
排序(默认升序) 需随机访问迭代器(不支持 list
)std::stable_sort
稳定排序(保持相等元素顺序) 性能略低于 sort
std::unique
删除连续重复元素 需先排序;返回新逻辑终点 std::reverse
反转序列元素顺序 list
有成员函数reverse()
删除元素标准写法:
// vector 删除偶数 auto newEnd = remove_if(vec.begin(), vec.end(), isEven); vec.erase(newEnd, vec.end()); // 物理删除
-
常用的数值算法(
std::accumulate
等)?算法 功能 示例 std::accumulate
累加/自定义归约操作 accumulate(v.begin(), v.end(), 0)
std::inner_product
计算内积(点积) inner_product(a.begin(), a.end(), b.begin(), 0)
std::partial_sum
生成前缀和序列 partial_sum(v.begin(), v.end(), out)
std::iota
填充递增序列 (C++11) iota(v.begin(), v.end(), 10)
// 10,11,12… -
如何使用这些算法以及它们与容器成员函数的区别?
场景 选择 原因 list
排序成员 sort()
算法 std::sort
需随机访问(不支持链表)set
查找成员 find()
算法 std::find
是 O(n),成员是 O(log n)关联容器删除 成员 erase()
算法 std::remove
破坏树结构通用序列操作 STL 算法 统一接口,可跨容器使用 关键原则:
- 关联容器/链表:优先用成员函数(性能/正确性)
- 序列容器(
vector/deque
):优先 STL 算法(通用性)
-
std::sort
底层实现?(通常是 Introsort)-
混合排序策略:
- 快速排序:主递归阶段(平均 O(n log n))
- 堆排序:当递归深度 > 2 log n 时切换(避免最坏 O(n²))
- 插入排序:小区间优化(n ≤ 16 时)
-
核心优势:
- 最坏时间复杂度 O(n log n)(优于纯快排)
- 避免递归过深(堆排序兜底)
- 小数据局部性优(插入排序)
-
示例代码:
std::vector<int> v = {5, 3, 2, 8, 1}; std::sort(v.begin(), v.end()); // 升序 std::sort(v.begin(), v.end(), std::greater<int>()); // 降序
-
3. 迭代器(Iterators)
*问题类型:
-
迭代器的概念和?
- 定义:迭代器是 STL 中用于 遍历容器元素 的通用抽象接口,行为类似指针(支持
*
、->
、++
等操作)。 - 作用:
- 解耦算法与容器(如
std::sort
可作用于所有支持随机访问迭代器的容器) - 提供统一的容器访问方式
- 解耦算法与容器(如
- 定义:迭代器是 STL 中用于 遍历容器元素 的通用抽象接口,行为类似指针(支持
-
五种迭代器作用类别(输入、输出、前向、个体、随机访问)及其特性?
类别 支持操作 容器示例 输入迭代器 只读单次遍历 ( *it
,++
,==
,!=
)istream_iterator
输出迭代器 只写单次遍历 ( *it=
,++
)ostream_iterator
前向迭代器 读写 + 多次遍历(继承输入/输出) forward_list
,unordered_*
双向迭代器 前向 + 反向遍历 ( --
)list
,set
,map
随机访问迭代器 双向 + 跳跃访问 ( it+n
,it[n]
,<
)vector
,deque
,array
层级关系:
输入 → 前向 → 双向 → 随机访问
输出 → 前向 → … -
begin()
,end()
,cbegin()
,cend()
,rbegin()
,rend()
的区别?函数 返回类型 方向 可修改性 容器示例调用 begin()
普通迭代器 正向 可读写 vec.begin()
end()
普通迭代器 正向尾后 不可解引用 vec.end()
cbegin()
const 迭代器 正向 只读 vec.cbegin()
cend()
const 迭代器 正向尾后 不可解引用 vec.cend()
rbegin()
反向迭代器 反向 可读写 vec.rbegin()
rend()
反向迭代器 反向尾后 不可解引用 vec.rend()
crbegin()
const 反向迭代器 反向 只读 vec.crbegin()
反向迭代器注意:
std::vector<int> v = {1,2,3}; auto rit = v.rbegin(); // 指向 3 *rit = 4; // v = {1,2,4}
-
迭代器故障问题,何时会发生,如何避免?
-
何时发生:
容器 导致失效的操作 vector
/string
插入/删除(导致扩容或元素移动) deque
首尾外插入/删除、扩容 list
/forward_list
仅删除时使被删元素迭代器失效 关联容器 仅删除时使被删元素迭代器失效 -
避免方法:
-
插入后更新迭代器:
auto it = vec.begin(); it = vec.insert(it, 10); // 获取新迭代器
-
删除时用返回值更新:
for (auto it = lst.begin(); it != lst.end(); ) { if (*it % 2 == 0) it = lst.erase(it); // 更新为下一个元素 else ++it; }
-
避免失效区间操作:
// 错误:删除导致后续迭代器失效 for (auto it = vec.begin(); it != vec.end(); ++it) { if (cond) vec.erase(it); // UB! }
-
关键原则:
- 序列容器(
vector/deque
)修改后所有迭代器可能失效 - 链表/关联容器修改后仅被删元素迭代器失效
-
4. 函数对象 (Functors) / Lambda 表达式
*问题类型:
-
函数对象的概念和作用?
-
概念:重载了
operator()
的类对象,可像函数一样调用 -
作用:
- 可携带状态(通过成员变量)
- 可作模板参数(编译期多态)
- 比函数指针更高效(可内联优化)
-
示例:
struct Compare { bool operator()(int a, int b) const { return a > b; // 实现自定义比较 } }; std::sort(v.begin(), v.end(), Compare());
-
-
Lambda 表达式的语法和捕获列表(Capture List)?
-
语法:
[捕获列表](参数) -> 返回类型 { 函数体 }
-
捕获列表(Capture List):
捕获方式 效果 []
不捕获外部变量 [x]
按值捕获变量 x
(副本)[&x]
按引用捕获变量 x
[=]
按值捕获所有外部变量(不推荐!) [&]
按引用捕获所有外部变量(不推荐!) [this]
捕获当前类对象的 this
指针[x = expr]
C++14:用表达式初始化捕获(移动捕获) -
示例:
int base = 100; auto add = [base](int x) { return x + base; }; std::cout << add(5); // 输出 105
-
-
Lambda 表达式的本质?
-
编译器行为:
将 Lambda 转换为匿名函数对象类(含重载的operator()
) -
转换示例:
// Lambda: [](int x) { return x * 2; } // 编译器生成类似: class __Lambda_123 { public: int operator()(int x) const { return x * 2; } };
-
-
什么时候使用函数对象或 Lambda 表达式?
场景 推荐方式 原因 简单逻辑(如比较器) Lambda 代码简洁,无需额外定义 需要携带复杂状态 函数对象 可定义多个成员变量/辅助方法 需要递归调用 函数对象 Lambda 递归需 std::function
(性能损失)需作为模板参数(如容器的比较器) 函数对象或无捕获 Lambda 无捕获 Lambda 可转换为函数指针 需复用逻辑 函数对象 避免重复定义 关键区别:
- 函数对象:显式定义类型,适合复杂/复用逻辑
- Lambda:隐式匿名类型,适合一次性简单逻辑