Effective STL 讀書筆記
阿新 • • 發佈:2022-05-10
第二章 vector和string
第13條:vector和string優先於動態分配的陣列
使用new來動態分配記憶體,需要承擔以下責任
1.確保有配套的delete呼叫
2.確保delete呼叫形式正確,單個物件使用delete,陣列使用delete[]
3.delete只調用一次
每當需要動態分配一個數組時,都應該考慮使用vector和string來替代,原因是
它們自己管理記憶體
它們可以使用全部stl演算法
它們和舊程式碼可以相互轉化
只有一種情況使用動態的陣列取代string,關於string的引用計數技術
·多執行緒中使用引用計數的string,在避免記憶體分配和字元拷貝所節省下的時間比不過在背後同步控制上的時間
規避方法:
使用不使用引用計數的string
考慮使用vector<char>
{=html}而不是string
第14條:使用reserve來避免不必要的重新分配
stl容器的自動增長機制
當需要更多空間時,就呼叫realloc類似的操作
1.分配 一塊當前容量的某個倍數的新記憶體。一般是2倍
把容器的所有元素從舊的記憶體拷貝到新的記憶體
析構舊記憶體中的物件
釋放舊記憶體
這些操作十分費時,這些步驟發生時,容器所有的指標、迭代器和引用都無效
4個函式
size(),告訴你容器有多少元素,不會告訴你該容器為自己所包含的元素分配多少記憶體
capacity(),告訴你該容器已經分配的記憶體可以容納多少個元素,是容器可以容納的元素最大總數,不是還能容納的多少元素,還能容納的多少元素,capacity-size
resize(n),強迫容器改變到包含n個元素的狀態,n如果比當前size要小,尾部多餘元素會析構,如果n比當前size要大,通過預設建構函式建立的新元素將會被新增到容器的末尾,如果n比當前的capacity要大,在新增元素之前,將先重新分配記憶體
reserve(n),強迫容器把它容量變為至少是n,前提是n不小於當前大小,這通常會導致重新分配,
避免重新分配的關鍵是,儘早的使用reserve把容器的容器設為足夠大的值
第15條:注意String實現的多樣性
string大小和實現不同而不同
size資訊
capacity容量
value值
可能包括:分配子的拷貝
建立在引用計數之上的string:對值的引用計數
string型別的區別
string的值可能會被引用計數
大小的範圍值時一個char*指標大小的1-7倍
建立新的字串子可能需要零次、一次或者兩次動態分配
string型別可能共享、也可能不共享其大小和容量資訊
string 可能支援,也可能不支援針對單個物件的分配子
不同的實現對字元記憶體的最小分配單位有不同的策略
第16條:瞭解如何把 vector 和 string 資料傳給舊的 API。
vector v表示陣列的指標 &v[0]
string s.ctr() 表示char*
事實上,先讓C API把資料寫到一個vector中,再把資料拷貝到STL容器中,這思想總是可行的
可以先把其他容器轉換為vector再使用
第17條:使用swap技巧除去多餘的容量
shrink to fit思想(壓縮至適當大小)
vector<contestant>
{=html}(contestants).swap(contestants);
vector<contestant>
{=html}(contestants)建立一個臨時的vector,是contestants的拷貝,只為所拷貝的元素分配了所需要的記憶體,swap後臨時變數的容量是原先contestants的容量,臨時變數執行完後被析構,而contestants擁有合適的記憶體值
vector<contestant>
{=html}().swap(v);// 清除v並把它的容量變為最小
swap不僅兩個容器的內容被交換,同時它們的迭代器、指標和引用也將被交換
第18條:避免使用vector<bool>
{=html}
它不是一個STL容器
STL容器的一個條件:
T *p = &c[0] 如果operator[取得了Container<t>
{=html}中的一個T物件,那麼你可以通過取它的地址得到一個指向該物件的指標。
它不儲存bool
它儲存的是bool的緊湊表示,而非真正的bool,使用了bitfield思想
使用deque<bool>
{=html} 或bitset代替
第三章 關聯容器
equivalence(等價)而不是equality(相等)來對待自己的內容
第19條:理解相等(equality)和等價(equivalence)的概念
相等:以operator==為基礎 :find
等價:以operator< insert>
{=html}
相等不一定所有資料成員都有相同值
等價關係:"在已排序的區間中物件值的相對順序",針對oprator <
!(w1 < w2) && !(w2 < w1)這兩個值就是等價的
使用者判別式(predicate):比較函式
標註關聯容器通過key_comp成員函式可被外部使用
!c.key_comp()(x,y) && !c.key_comp()(y,x) // 在c的排列順序中,x不在y前,y也不在x之前
例子:自定義set<string>
{=html} 不區分大小寫
使用set的find成員函式,查詢含義僅大小寫不同的string的set裡面,可以成功查詢(基於等價),使用非成員的find演算法就不會成功(基於相等)
標註關聯容器使用等價的原因
容器總是保持排列順序的,那麼必須要實現比較相對大小,如果使用相等,需要多定義一個操作符
第20條:為包含指標的關聯容器制定比較型別
set<string*>
{=html} ssp;
是下面程式碼的縮寫:set<string*,less<string*>
{=html}> spp;
最精確的:set<string*,less<string*>
{=html},allocator<string*>
{=html}> spp;
定義比較函式子類
set 比較不需要函式,而是需要一個型別,在內部通過它建立一個函式
其他容器包含的物件與指標的行為類似:比如智慧指標和迭代器
第21條:總是讓比較函式在等值情況下返回false
比較函式的返回值表明的按照該函式定義的排列順序,相等的值從來不會有前後順序關係,比較函式應當始終返回false
第22條:切勿直接修改set或multiset中的鍵
為什麼set或者multiset中的元素不能是const的
針對物件,如果用物件中某個引數表示key,其他引數表示value,如果設定const,則無法改變value
更改鍵部分(key part):這部分資訊會影響容器的排序性,可能破壞容器
強制型別轉換是危險的,只要您能避免使用它就應用避免使用
第23條:考慮用排序的vector替代關聯容器
需要可提供快速查詢功能的資料結構時,可以選擇關聯容器
考慮查詢速度,非標準的雜湊容器是值得的
標準關聯容器比vector效率還低的情況並不少見
標準關聯容器通常被實現為平衡二叉查詢樹
平衡二叉樹對插入、刪除和查詢的混合操作做了優化,總的來說就是沒辦法預測針對這棵樹的下個操作是什麼
常見的資料結構過程
設定階段:幾乎所有操作都是插入和刪除
查詢階段:幾乎所有操作都是查詢
重組階段:改變改資料結構,再插入新的的數,和第1階段類似
這樣的情況下,vector可能比關聯容器提供了更好的效能,必須是排序的vector
排序的vector效能強的原因
大小問題:關聯容器中一個widget伴隨的空間至少是3個指標,更非記憶體
但是需要對每個元素都需要排序
第24條:當效率至關重要時,請在map::operator[]與map::insert之間謹慎做出選擇
map的operator[]與眾不同,它的設計目的是提高"新增和更新"
如果鍵k沒有的map中,需要先初始化一個物件給其拷貝賦值,效率低,應該使用insert
第25條:熟悉非標註的雜湊容器
目前應該有std::map等實現
第四章 迭代器
第26條:Iterator優先於const_iterator、reverse_iterator以及const_reverse_iterator
四個迭代器
iterator 相當於T*,
const_iterator相當於const T*
iterator、const_iterator 遞增效果:頭部到尾部
reverse_iterator、const_reverse_iterator相當於T、const T,遞增效果:尾部到頭部
類似的引數基本引數型別為:Iterator
不同迭代器的轉換:base()轉換
const 轉普通的Iterator不能直接得到
第27條:使用distance和advance將容器的const_iterator轉換成iterator
const_iterator無法強制轉換為Iterator
第28條:正確理解由reverse_iterator的base()成員函式所產生的Iterator的用法
例子
vector<int>
{=html} v = {1,2,3,4,5};
vector<int>
{=html}::reverse_iterator ri =
find(v.rbegin(),v.rend(),3); vector<int>
{=html}::iterator i (ri.base()
圖例
對於插入來說:直接使用ri和ri.base()是等價的
對於刪除來說:如果需要刪除ri所指的元素,必須刪除i前面的元素:v.erase(--ri.base())
對於vector和string的很多實現來說,Iterator和const_iterator是以內建指標的方式來實現的,這樣--ri.base()的表示式就無法通過編譯。必須使用 v.erase((++ri).base())
第29條:對於逐個字元的輸入請考慮使用istreambuf_iterator
istream_iterator<char>
{=html}物件使用oprator>>從輸入流中讀取單個字元:每呼叫一次operator>>操作符,都需要執行許多附近的操作
istreambuf_iterator<char>
{=html} 直接從流的緩衝區中讀取下一個字元
第五章 演算法
第30條:確保目標區間足夠大
在vector尾部新增物件
失敗案例
成功案例
如果插入的目標容器是vector和string,預先呼叫reserve,可以提高插入操作的效能。
reserve和insert建議同時呼叫
需要牢記的是:如果使用的演算法需要一個目標區間,那麼必須確保目標區間足夠大,或者確保它會隨著演算法的執行而增大。
要在演算法執行過程中增大目標區間,使用插入型迭代器
第31條:瞭解各種與排序有關的選擇
nth_element演算法:
排序一個區間,使得位置n上的元素正好是全排列情況下的第n個元素,當nth_element返回時,所有按全排列(sort的結果)排在位置n之前的元素也都排在位置n之前,而所有按照全排序規則排在位置n之後的元素則都排在位置n之後。
例子:將最好的20個元素放在容器前列,而不關心他們的具體排序
nth_element,沒有對位置1-20中的元素排序
用途:找到一個區間的中間值,或者特定百分比上的值
排序演算法的穩定性:排序後,等價的值,先後次序穩定
sort、stable_sort和partial_sort
partition演算法
sort、stable_sort、partial_sort和nth_element演算法都要求隨機訪問迭代器
list是唯一一個需要排序卻無法使用這些排序演算法的容器,只能sort完全排序
對於標準關聯容器的元素進行排序無意義
partion和stable_partion只要求雙向迭代器就能完成工作
總結
完全排序使用sort
等價性前n個元素排序,使用partial_sort
需要找到前n個元素但又不進行排序,nth_element
標準容器需要按照滿足某個特定的條件區分開,使用partion和stable_partion
list中只能實現list::sort,使用其他演算法需要別的轉化
第32條:如果確實需要刪除元素,則需要在remove這一類演算法之後呼叫erase
用remove刪除容器元素,容器中的元素數目不會因此減少
remove不是真正意義上的刪除,因為它做不到
例子
呼叫remove之前:
呼叫之後
remove只是移動了區間中的元素,把不用刪除的元素移到了區間的前部
真正刪除值
list.remove() 唯一一個命名為remove而確實刪除了容器元素的函式
類似的函式:remove_if和unique,同時unique和list::unique和remove的關係一致
第33條:對包含指標的容器使用remove這一類演算法時要特別小心
例子:
對於vector<widget*>
{=html} v
刪除操作有問題,在remove_if中就出現
呼叫之前
remove_if呼叫之後
erase執行完
容器中存放的是指向動態分配物件指標的時候
避免使用remove和類似演算法,使用partition演算法時不錯的選擇
如果是智慧指標,問題就不存在
第34條:瞭解哪些演算法要求使用排序的區間作為引數
有些演算法需要排序的區間:違反這一規則並不會導致編譯器錯誤,而會導致執行時錯誤
有些演算法在排序的區間上,演算法會更加有效
要求排序區間的演算法
binary_search
lower_bound/upper_bound
equal_range
set_union/set_intersection
set_difference/set_symmetric_difference
merge/inplace_merge
includes
不要求區間排序,但一般和排序區域一起使用的
unique/unique_copy
需要用二分法查詢資料
binary_search、lower_bound/upper_bound、equal_range
如果區域時排序好的,承諾對數時間的查詢效率
只有接受隨機迭代器的時候,才能保證此效率
如果不支援隨機迭代器,只能保證線性時間
提供線性時間效率的集合操作
set_union/set_intersection、set_difference/set_symmetric_difference
不使用排序區間,無法保證線上性時間完成
提供線性時間的合併和排序聯合操作
merge/inplace_merge
讀入兩個排序的區間、然後合併為一個新的排序區間
不使用排序演算法,變得很慢
includes
對於未排序區間有很好行為
unique和unique_copy
unique如果想要刪除區間的重複元素,必須保證所有相等的元素都是連續存放的
這樣需要
一個區間被排序的含義
區間可能有不同的排序比較函式,演算法也有比較函式,需要保證兩者有一致的行為
正確用法
第35條:通過mismatch或lexicograhical_compare實現簡單的忽略大小寫的字串比較
該條例實現簡單的英語字串的比較
忽略大小寫的字串比較功能
實現類似strcmp介面
實現與operator<類似介面>
{=html}
strcmp類似介面
第一種實現
比較函式
c1、c2的char強制轉換為unsigned char