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(n2))
- 插入排序:小區間優化(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:隱式匿名類型,適合一次性簡單邏輯