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 字符)直接在栈存储,避免堆分配:cppstd::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的区别?特性 vectorlist底层结构 动态数组(连续内存) 双向链表(非连续内存) 随机访问 O(1)(支持下标访问)O(n)(需遍历)头部插入/删除 O(n)(移动元素)O(1)尾部插入/删除 均摊 O(1)O(1)中间插入/删除 O(n)(移动元素)O(1)(定位后操作)内存占用 较少(仅需存储数据) 较高(每个节点含两个指针) 缓存友好性 高(连续内存) 低(内存碎片化) 扩容开销 需重新分配内存和拷贝 无扩容(动态分配节点) -
string和char*的区别?特性 stringchar*内存管理 自动分配/释放(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::mapstd::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)?关联容器(
set/map等)在模板参数中指定自定义比较类型:
cpp// 方法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对象序列容器(
vector/list等)在排序操作时传入比较函数:
cppstd::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);优先队列(
priority_queue)在模板参数中指定比较类型:
cpp// 小顶堆示例 struct Greater { bool operator()(int a, int b) const { return a > b; // 小值优先 } }; std::priority_queue<int, std::vector<int>, Greater> minHeap;关键规则
1.严格弱序要求:
-
需满足:
cpp!comp(a, a) // 非自反 comp(a, b) => !comp(b, a) // 非对称 comp(a, b) && comp(b, c) => comp(a, c) // 传递性 -
违反示例(错误):
cpp[](int a, int b) { return a <= b; } // 包含相等,违反非自反性
比较对象类型:
- 函数对象:需重载
operator()且为const成员函数 - Lambda:推荐捕获列表为空(无状态)
性能影响:
- 关联容器:比较函数复杂度需为 O(1),否则树操作退化为 O(n log n)
- 排序操作:比较函数应轻量(高频调用)
-
-
无序关联容器(
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()时,桶数翻倍并重哈希
-
如何自定义容器的比较函数?
需定义 两个函数对象:
- 哈希函数(计算键的哈希值)
- 键相等比较(判断键是否相同)
cpp// 示例:自定义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(可选):cppnamespace std { template<> struct hash<Point> { /* ... */ }; }
-
-
容器配合(
std::stack,std::queue, ,std::priority_queue):-
各自的特点、底层实现、优缺点和适用场景?
适配器 特点 默认底层容器 核心操作 优点 缺点 适用场景 std::stack后进先出(LIFO) std::dequepush(),pop(),top()操作简单高效(O(1)) 只能访问顶部元素 函数调用栈/撤销操作/括号匹配 std::queue先进先出(FIFO) std::dequepush(),pop(),front()头尾操作高效(O(1)) 只能访问两端元素 消息队列/缓冲区/广度优先搜索 std::priority_queue优先级队列(最大堆) std::vectorpush(),pop(),top()高效获取极值(O(1)) 插入慢(O(log n)) 任务调度/事件处理/Dijkstra算法
底层实现详解
1.stack&queue默认容器选择-
使用
deque(双端队列)原因:-
两端插入/删除均为 O(1)
-
内存自动扩展(避免
vector扩容时的全量拷贝) -
示例:
cppstd::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())
- 随机访问(
-
堆操作:
cpp// 插入元素(上浮) vec.push_back(x); std::push_heap(vec.begin(), vec.end()); // 删除顶部(下沉) std::pop_heap(vec.begin(), vec.end()); vec.pop_back();
自定义底层容器
cpp// 使用 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()
性能与选择指南
1.stack/queuevs 原生容器- 优先用适配器:语义明确 + 防止误操作
- 需要中间访问时 → 改用
deque
priority_queue优化-
自定义比较器实现最小堆:
cppstd::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; -
复杂类型场景:
cppstruct 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:cppstd::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稳定排序(保持相等元素顺序) 性能略低于 sortstd::unique删除连续重复元素 需先排序;返回新逻辑终点 std::reverse反转序列元素顺序 list有成员函数reverse()删除元素标准写法:
cpp// 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)(优于纯快排)
- 避免递归过深(堆排序兜底)
- 小数据局部性优(插入排序)
-
示例代码:
cppstd::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()反向迭代器注意:
cppstd::vector<int> v = {1,2,3}; auto rit = v.rbegin(); // 指向 3 *rit = 4; // v = {1,2,4} -
迭代器故障问题,何时会发生,如何避免?
-
何时发生:
容器 导致失效的操作 vector/string插入/删除(导致扩容或元素移动) deque首尾外插入/删除、扩容 list/forward_list仅删除时使被删元素迭代器失效 关联容器 仅删除时使被删元素迭代器失效 -
避免方法:
-
插入后更新迭代器:
cppauto it = vec.begin(); it = vec.insert(it, 10); // 获取新迭代器 -
删除时用返回值更新:
cppfor (auto it = lst.begin(); it != lst.end(); ) { if (*it % 2 == 0) it = lst.erase(it); // 更新为下一个元素 else ++it; } -
避免失效区间操作:
cpp// 错误:删除导致后续迭代器失效 for (auto it = vec.begin(); it != vec.end(); ++it) { if (cond) vec.erase(it); // UB! }
-
关键原则:
- 序列容器(
vector/deque)修改后所有迭代器可能失效 - 链表/关联容器修改后仅被删元素迭代器失效
-
4. 函数对象 (Functors) / Lambda 表达式
*问题类型:
-
函数对象的概念和作用?
-
概念 :重载了
operator()的类对象,可像函数一样调用 -
作用:
- 可携带状态(通过成员变量)
- 可作模板参数(编译期多态)
- 比函数指针更高效(可内联优化)
-
示例:
cppstruct Compare { bool operator()(int a, int b) const { return a > b; // 实现自定义比较 } }; std::sort(v.begin(), v.end(), Compare());
-
-
Lambda 表达式的语法和捕获列表(Capture List)?
-
语法:
cpp[捕获列表](参数) -> 返回类型 { 函数体 } -
捕获列表(Capture List):
捕获方式 效果 []不捕获外部变量 [x]按值捕获变量 x(副本)[&x]按引用捕获变量 x[=]按值捕获所有外部变量(不推荐!) [&]按引用捕获所有外部变量(不推荐!) [this]捕获当前类对象的 this指针[x = expr]C++14:用表达式初始化捕获(移动捕获) -
示例:
cppint base = 100; auto add = [base](int x) { return x + base; }; std::cout << add(5); // 输出 105
-
-
Lambda 表达式的本质?
-
编译器行为 :
将 Lambda 转换为匿名函数对象类 (含重载的
operator()) -
转换示例:
cpp// 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:隐式匿名类型,适合一次性简单逻辑