在使用 C++ 編程中,最重要的庫就是 C++ 標準庫。C++ 標準庫的核心是泛型容器和泛型算法,庫中的這一子集通常稱爲標準模板庫(Standard Template Library, STL),因爲這一部分大量使用了模板。STL 的威力在於提供了泛型容器和泛型算法,使大部分算法可用於大部分容器,而無論容器中保存的數據類型是什麼。性能是 STL 中非常重要的一部分。STL 的目標是要讓 STL 容器和算法和手工編寫的代碼速度相當快。
編碼原則
標準庫大量使用了C++的模板特性和運算符重載特性,標準模板庫也是這樣。
C++標準庫概述
字符串
從技術角度,C++ string實際上是basic_string
模板char
實例化的typedef
名稱。然而,不需要關注這些細節。只要像非模板類那樣使用string即可。
正則表達式
I/O流
I/O功能在幾個頭文件中定義:
<fstream>
<iomanip>
<ios>
<iosfwd>
<iostream>
<istream>
<ostream>
<sstream>
<streambuf>
<strstream>
智能指針
C++用智能指針unique_ptr
、shared_ptr
和week_ptr
解決內存管理問題。
shared_ptr
和week_ptr
是線程安全的,都在<memory>
中定義。
異常
C++標準庫提供了一個異常的類層次結構,在程序中可以使用這些類,也可以通過繼承方式創建自己的異常類型。
異常在以下頭文件中定義:
<exception>
<stdexcept>
<system_error>
數學工具
雖然這些類模板化了,可以用於任何類型,但是一般不認爲這些類是標準模板庫的一部分。
-
<complex>
提供了一個複數類complex
,提供了對實部和虛部的數的操作抽象。 -
<ratio>
有理數運算庫,可以精確地表示任何由分子和分母定義的有限有理數。 -
<valarray>
提供了valarray
,和vector
類似,但是對高性能數值應用做了特別的優化,提供了表示矢量切片概念的相關類。通過這些構件,可以構建執行矩陣數學運算的類。不過,沒有內建的矩陣類。Boost這樣的第三方庫提供了矩陣支持。
獲取數值極限的標準方式。
<limts>
中的numeric_limits
模板。
cout << "Max int value: " << numeric_limits<int>::max() << endl;
cout << "Min int value: " << numeric_limits<int>::min() << endl;
cout << "Lowest int value: " << numeric_limits<int>::lowest() << endl;
cout << "Max double value: " << numeric_limits<double>::max() << endl;
cout << "Min double value: " << numeric_limits<double>::min() << endl;
cout << "Lowest double value: " << numeric_limits<double>::lowest() << endl;
時間工具
<chrono>
簡化了時間相關的操作,特定時間間隔的定時操作和定時相關的操作執行。
隨機數
srand()
和rand()
只提供了非常初級的隨機數,也無法修改生成隨機數的分佈。
C++11添加了一個完善的隨機數庫<random>
,帶有隨機數引擎、隨機數引擎適配器以及隨機數分佈。
通過<random>
可以生成特定問題域的隨機數,例如正態分佈、負指數分佈。
初始化列表
<initializer_list>
編寫參數數目可變的函數。
Pair和Tuple
-
<utility>
定義了pair
模板,可以用於存儲兩種不同類型的元素。這稱爲存儲異構元素。 -
<tuple>
定義的tuple
是pair
的一種泛化,是一個固定大小的序列,元組的元素可以是異構的。
函數對象
實現函數調用運算符的類稱爲函數對象。函數對象可以用作某些STL算法的謂詞。
<functional>
多線程
單個線程可以用<thread>
建立。
多線程代碼,需要考慮:幾個線程不能同時讀寫同一個數據。爲了避免這種情形,可以使用<atomic>
定義的原子性。
<condition_vaiable>
和<mutex>
提供了其他線程同步機制。
如果只需計算某個數據,得到結果,並具有相應的異常處理,使用<future>
頭文件定義的async
和future
要比thread
簡單。
類型特質
類型特質在<type_traits>
中定義,提供了編譯期間的類型信息。編寫高級模板的時候可以使用它。
標準模板庫
C++ STL(Standard Template Library, STL)容器是同構的,每種容器只允許一種類型的元素。
注意:C++標準定義了每個容器和算法的接口(interface),沒有定義實現。因此,不同的供應商可以自由提供不同的實現。不過,作爲接口的一部分,標準定義了性能需求。
STL容器
vector
如果在程序中需要快速訪問元素,但是不會頻繁在中間添加或刪除元素,則應該使用vector
。
在任何可能的情況下使用vector而不是C風格的數組
vector<bool>
有特定優化過的模板,不過標準沒有規定如何實現優化空間。
list
雙向鏈表
forward_list(C++11)
只支持單向遍歷的單鏈表。內存需求比list
小。
deque
雙端隊列(double-ended queue)。不過行爲更像vector
,而不是queue
。
提供了快速的元素訪問(常量時間),在序列兩端還提供了快速的插入和刪除(攤還常量時間),但是在序列中間插入和刪除的速度較慢(線性時間)。
如果需要在兩頭快速插入或刪除元素,還要求快速訪問所有元素,那麼應該使用deque
而不是vector
。
大部分場景並不滿足這種需求,因此大部分情況下,vector
或list
足以滿足需求。
array
標準C風格數組的替代品。特別適用於大小固定的集合,而且沒有vector
的開銷。
使用array
比C風格數組有幾點好處:
-
總能知道自己的大小;
-
不會自動轉換爲指針類型,避免某些bug;
-
大小固定,允許堆棧上分配內存,不需要像
vector
一樣需要堆訪問權限。
vector
、list
、deque
、array
和forward_list
容器都稱爲順序容器(sequential container),因爲它們保存的是元素的序列。
queue
先進先出的隊列
priority_queue
優先級隊列。插入和刪除比簡單的queue
插入和刪除慢。
stack
先入後出
從技術角度,
queue
、priority_queue
和stack
容器都是容器適配器(adapter)。它們只是構建在某種標準順序容器(vector、list或deque)上的簡單接口。
set和multiset
類似數學上的集合。不過set
中按照一定的順序保存。排序的原因是當客戶枚舉元素時,能夠以operator<
或用戶自定義的比較器的順序出現。
set
提供了對數時間的插入、刪除和查找。如果要存儲重複元素,就必須使用 <set>
頭文件定義的 multiset
。
map和multimap
map
保存的是鍵/值對。map
按照排好的順序保存元素,排序的依據是鍵值而非對象值。在其他所有方面,map
和 set
是一致的。如果需要關聯鍵和值,就應該使用 map
。
multimap
也在 <map>
中定義,它和 map
的關係等同於 multiset
和 set
的關係。確切地講,multimap
是一個允許重複鍵的 map
。
set
和map
容器都是關聯容器,因爲它們關聯了鍵和值。在set
中,鍵本身就是值。這些容器會對元素進行排序,因此這些容器稱爲排序或者有序關聯容器。
無序關聯容器/哈希表
哈希表也稱爲無序(unordered associative container)。有4個無序關聯容器:
- unordered_map
- unordered_set
- unordered_multimap
- unordered_multiset
在C++11之前,哈希表不屬於C++標準庫的一部分,因此很多第三方庫都用hash
作爲前綴。因此C++標準委員會決定使用unordered
而不是hash
,避免名稱衝突。
bitset
bitset
不是常規意義的容器,不能插入和刪除元素。固定大小,不支持迭代器。可以想象成一個可以讀寫的布爾值序列。
bitset
不侷限於int
或者其他原始類型的大小。可以在聲明時指定:bitset<N>
。
vector
應該是默認使用的容器。實際上,vector
中的插入和刪除常常快於list
或者forward_list
。這是現代CPU上內存和緩存的工作方式,而list
或forward_list
需要先移動到要插入或刪除元素的位置上。list
或者forward_list
的內存可能是碎片化的,所以迭代慢於vector
。
STL算法
STL採取了分離數據(容器)和功能(算法)的方式。看上去有點違背了面向對象編程的思想,但是爲了在STL中支持泛型編程,有必要這麼做。
正交性的指導原則使算法和容器分離開,(幾乎)所有算法都可以用於(幾乎)所有容器。
有些容器以類方法提供了某些算法,因爲泛型算法在特定容器上表現不出色。**比如,set
提供了自己的find()
算法,比泛型的find()
算法快。**如果類提供了特定的算法實現,應該使用容器特定方法的形式。
泛型算法並不是直接對容器操作。泛型算法使用迭代器(iterator)作爲中介。
函數名 | 概要 |
---|---|
begin(), end() | 第一個元素和最後一個元素後面的元素,返回非const迭代器 |
以下是C++14新增的迭代器 | |
cbegin(), cend() | 第一個元素和最後一個元素後面的元素,返回const迭代器 |
rbegin(), rend() | 非const的反向迭代器 |
crbegin(), crend() | const的反向迭代器 |
非修改順序算法
有了這些算法後,幾乎不再需要編寫for
循環來迭代值序列了。【就是暴力枚舉的封裝形式】
算法名稱 | 算法概要 | 複雜度 |
---|---|---|
adjacent_find() | 查找第一個兩個連續元素相等或匹配謂詞的實例 | 線性 |
find()、find_if() | 查找第一個匹配值或使謂詞返回true 的元素 |
線性 |
find_first_of() | 與find 類似,只是同時搜索多個元素中的一個 |
二次 |
find_if_not() | 查找第一個使謂詞返回false 的元素 |
線性 |
search()、find_end() | 在序列中查找第一個(search() )或最後一個(find_end() )匹配另一個序列的子序列,或者這個子序列的元素和謂詞指定的一致 |
線性 |
搜索算法
不需要元素有序
比較算法
不要求有序,最差複雜度爲線性複雜度
算法名稱 | 算法概要 |
---|---|
equal() | 檢查相應元素是否相等或匹配謂詞,以此判斷兩個序列是否相等 |
mismatch() | 返回每個序列第一個出現的和其他序列同一位置元素不匹配的元素 |
lexicographical_compare() | 比較兩個序列,判斷這兩個序列的“詞典順序”。將第一個序列中的每一個元素和第二個序列中對應的元素進行比較。如果一個元素小於另一個元素,那麼這個序列按照詞典順序在前面。如果兩個元素相等,則按順序比較下一個元素 |
工具算法
算法名稱 | 算法概要 |
---|---|
all_of() | 序列爲空或對所有謂詞返回true ,則返回true ,否則返回false |
any_of() | 序列爲空或至少謂詞判斷有一次true ,則返回true ,否則返回false |
none_of() | 序列爲空或者謂詞對所有元素返回false ,則返回true ,否則返回false |
count()、count_if() | 計算匹配一個值或者使謂詞返回true 的元素個數 |
修改序列算法
算法名稱 | 算法概要 |
---|---|
copy()、copy_backward() | 將一個序列的元素複製到另一個序列 |
copy_if() | 將謂詞返回true 的元素複製到另一個序列 |
copy_n() | 複製n個元素到另一個序列 |
fill() | 所有元素設置爲一個新值 |
fill_n() | 前n個元素設置爲一個新值 |
generate() | 調用指定函數,爲序列中每一個元素生成新值 |
generate_n() | 調用指定函數,爲序列中前n個元素生成一個新值 |
move(),move_backward() | 將一個序列的元素移到另一個序列。使用了高效的移動語義 |
remove()、remove_if()、remove_copy()、remove_copy_if() | 刪除匹配給定值或使謂詞返回true 的元素,就地刪除或將結果複製到另一個不同的序列 |
replace()、replace_if()、replace_copy()、replace_copy_if() | 將匹配特定值或導致謂詞返回true 的所有元素替換爲新元素,就地替換或將結果複製到新序列 |
reverse()、reverse_copy() | 掉轉序列中的元素,原地操作或者複製到新序列 |
rotate()、rotate_copy() | 交換序列中前半部分和後半部分,原地操作或者複製到新序列。兩個要交換的子序列不一定要一樣大 |
shuffle()、random_shuffle() | 打亂元素的順序,random_shuffle() 在C++14之後被廢棄 |
transform() | 對序列中的每個元素調用一元函數,或對兩個隊列中的對應元素調用二元函數 |
unique()、unique_copy() | 在序列中刪除連續出現的重複元素,原地刪除或複製到新序列 |
操作算法
for_each
:對每個元素執行函數
交換算法
算法名稱 | 算法概要 |
---|---|
iter_swap() 、swap_ranges() |
交換兩個元素,或交換兩個元素的序列 |
swap() |
交換兩個值,在<utility> 頭文件中定義 |
分區算法
排序算法
算法名稱 | 算法概要 | 複雜度 |
---|---|---|
is_sorted()、is_sorted_until() | 檢查一個序列是否排序,或者哪個子序列已經排序 | 線性 |
nth_element() | 重定位序列中的第n個元素,使第n個位置的元素就是排好序之後第n個位置的元素。該函數會重新安排所有元素,使第n個元素前面的所有元素都小於新的第n個元素 | 線性 |
partial_sort()、partial_sort_copy() | 只排序序列中的一部分元素:只有前n個元素(由迭代器指定)排序,其餘元素不排序。在原位置排序,或者複製到新的序列 | 線性對數 |
sort()、stable_sort() | 在原位置排序,保留重複元素的順序或不保留 | 線性對數 |
二叉搜索樹算法
算法名稱 | 算法概要 | 複雜度 |
---|---|---|
lower_bound()、upper_bound()、equal_range() | 查找包含給定元素的範圍的頭(lower_bound())、尾(upper_bound())或兩端(equal_range()) | 對數 |
binary_search() | 在序列中查找一個值 | 對數 |
集合算法
這些算法最適合set
,但是也能操作大部分容器排序後的序列。【歸併排序】
算法名稱 | 算法概要 | 複雜度 |
---|---|---|
inplace_merge() | 在原位置將兩個排好序的序列合併 | 線性對數 |
merge() | 合併兩個排好序的序列,將兩個序列複製到新的序列 | 線性 |
includes() | 確定是否序列中的每一個元素都在另一個序列中 | 線性 |
set_union()、set_intersection()、set_difference()、set_symmetric_difference()[並、交、差、補] | 在兩個排序的序列上執行特定的集合操作,將結果複製到第三個排序的序列中 | 線性 |
堆算法
堆(heap)是一個標準的數據結構,數組或序列中的元素在其中以半排序的方式排序,因此能夠快速找到“頂部”的元素。通過使用6個算法可以對序列進行堆排序。
算法名稱 | 算法概要 | 複雜度 |
---|---|---|
is_heap() |
檢查某個範圍的元素是否是一個堆 | 線性 |
is_heap_until() |
在給定範圍內的元素中查找最大的子範圍 | 線性 |
make_heap() |
從一個範圍的元素中創建堆 | 線性 |
push_heap() 、pop_heap() |
在堆中添加或刪除元素 | 對數 |
sort_heap() |
把堆轉換爲升序排列的元素範圍 | 線性對數 |
最大/最小算法
算法名稱 | 算法概要 |
---|---|
min() 、max() |
返回兩個值中最小值或最大值 |
minmax() |
以 pair 方式返回兩個或多個值中的最小值和最大值 |
min_element() 、max_element() |
返回序列中最小或最大元素 |
minmax_element |
找到序列中最小和最大元素,把結果返回爲 pair |
數值處理算法
<numeric>
,所有的算法都是線性複雜度,不要求排序源序列。
算法名稱 | 算法概要 |
---|---|
accumulate() |
“累加”一個序列中所有的元素的值,默認行爲是計算元素的和,但調用者可以提供不同的二元函數 |
adjacent_difference() |
生成一個新的序列,其中每一個元素都是對應元素和源序列中之前元素的差(或其它二元操作) |
inner_product() |
與 accumulate() 類似,但對兩個序列操作。對序列中的並行元素調用二元函數(默認做乘法),通過另一個二元函數(默認加法)累加結果值。如果序列表示數學矢量,那麼這個算法計算矢量的點積 |
置換算法
算法名稱 | 算法概要 | 複雜度 |
---|---|---|
is_permutation() | 如果一個範圍中的元素是另一個範圍中的元素的轉換,就返回true |
二次 |
next_permutation()、prev_permutation() | 修改序列,將序列轉換爲下一個或前一個排列。如果從正確排序的序列開始,連續調用可以獲得所有可能的排列。如果沒有更多排列,則返回false |
線性 |
STL中還缺什麼
-
STL不能保證任何線程安全
-
STL沒有提供任何泛型的樹結構或圖結構(不過大部分語言都沒有提供),如果編寫解析器,就需要自己實現或者尋找其他庫。
最後
STL 是可擴展的,可以編寫適用於現有算法和容器的容器和算法。如果 STL 沒有提供需要的內容,可以考慮編寫兼容 STL 的代碼。