Effective STL 學(xué)習(xí)筆記 —— Part 1.容器

第一章.容器


  • 條款1.慎重選擇容器類型

標(biāo)準(zhǔn)STL序列容器:vector、string蔓搞、deque和list
標(biāo)準(zhǔn)STL關(guān)聯(lián)容器:set骤星、map吃沪、multiset和multimap
非標(biāo)準(zhǔn)的關(guān)聯(lián)容器:hash_set跃巡、hash_multiset危号、hash_map和hash_multimap
標(biāo)準(zhǔn)的非STL容器:數(shù)組牧愁、bitset素邪、valarray、stack猪半、queue和priority_queue

另一種分類:
連續(xù)內(nèi)存容器(元素存放在一塊或多塊內(nèi)存中兔朦,每塊內(nèi)存中存有多個元素):vector、string和deque
基于節(jié)點的容器(每塊內(nèi)存存放一個元素):list和所有標(biāo)準(zhǔn)的關(guān)聯(lián)容器


  • 條款2.不要試圖編寫?yīng)毩⒂谌萜黝愋偷拇a

序列容器與關(guān)聯(lián)容器數(shù)據(jù)結(jié)構(gòu)不同 磨确,所提供的操作也不同沽甥。
序列容器支持push_frontpush_back,但關(guān)聯(lián)容器不支持乏奥。
關(guān)聯(lián)容器提供對數(shù)時間復(fù)雜度的lower_bound摆舟、upper_boundequal_range成員函數(shù),但序列容器卻沒有。
寫既要和序列容器又要和關(guān)聯(lián)容器一起工作的代碼并沒有什么意義恨诱。很多成員函數(shù)(包括運算符重載)只存在于其中一類容器中媳瞪。


  • 條款3.確保容器中的對象拷貝正確且高效

當(dāng)你向容器中添加一個對象(比如通過insertpush_back等),進(jìn)入容器的是你指定的對象的拷貝照宝,
如果你從vector蛇受、string或deque中插入或刪除了什么或使用了任何排序算法,現(xiàn)有的容器元素會移動(拷貝)厕鹃。
因此把一個派生類對象插入基類對象的容器會導(dǎo)致派生部分被刪除兢仰,而且容器中如果放的對象拷貝過程很昂貴,元素的移動會成為性能瓶頸剂碴,
所以使拷貝更高效把将、正確而且對分割問題免疫的簡單的方式是建立指針的容器而不是對象的容器。但是指針容器也有自己的問題汗茄,詳見條款7和條款33秸弛。


  • 條款4.調(diào)用empty()而不是檢查size()是否為0

empty的典型實現(xiàn)是一個返回size()==0結(jié)果的內(nèi)聯(lián)函數(shù)
對于所有的標(biāo)準(zhǔn)容器洪碳,empty是一個常數(shù)時間的操作递览,但對于一些list實現(xiàn),size花費線性時間瞳腌。
list中有一個變量用于記錄元素個數(shù)绞铃。特殊的是,list::splice()用于拼接兩個list嫂侍,為了達(dá)到splice的高效率儿捧,在splice時可能不更新size,而在調(diào)用size時遍歷list計算size挑宠,
這就會導(dǎo)致size()花費線性時間而不是常數(shù)時間菲盾。
但書中沒有說empty()為什么一定是常數(shù)時間。
所以我看了下我所用的QT中l(wèi)ist::empty()的實現(xiàn)方式各淀,發(fā)現(xiàn)list是由循化鏈表實現(xiàn)的懒鉴,empty()的實現(xiàn)是判斷頭節(jié)點的下個節(jié)點是否還是頭結(jié)點,因此為常數(shù)時間碎浇。
所以我的理解是:由于STL的實現(xiàn)方式不同临谱,empty()的效率比0 == size()更加穩(wěn)定(如循環(huán)鏈表實現(xiàn)的List)。


  • 條款5.區(qū)間成員函數(shù)優(yōu)先于與之對應(yīng)的單元素成員函數(shù)

本文以insert的單元素版本和區(qū)間版本說明奴璃,區(qū)間成員函數(shù)優(yōu)點有三:

  1. 省去了沒有必要的函數(shù)調(diào)用悉默,調(diào)用1次與調(diào)用n次,即使將單元素版本聲明為內(nèi)聯(lián)苟穆,也有可能不會成為內(nèi)聯(lián)抄课。
  2. 每次insert單元素唱星,要將插入位置后的所有元素進(jìn)行移動,進(jìn)行拷貝跟磨,區(qū)間insert則先算好一共要插入的總數(shù)魏颓,然后將插入位置后的元素只整體挪動一次
  3. 看原因3前需要先看Part2.條款14中,了解序列容器插入元素時內(nèi)存的重新分配機(jī)制吱晒。多次插入單元素可能導(dǎo)致內(nèi)存多次重新分配甸饱,而區(qū)間插入則一次性分配足夠的空間,然后進(jìn)行插入仑濒。

常用區(qū)間成員函數(shù)整理:
區(qū)間構(gòu)造:

container::container(InputIterator begin, InputIterator end); 

begin和end為舊容器中叹话,被拷貝區(qū)間的起始

區(qū)間插入:

序列容器:void container::insert(iterator position,InputIterator begin, InputIterator end); 
關(guān)聯(lián)容器:void container::insert(lnputIterator begin, InputIterator end);//關(guān)聯(lián)容器使用自己的比較函數(shù)決定插入元素放在哪

begin和end為舊容器中,要插到新容器中的區(qū)間起始
position為新容器中墩瞳,要插入位置的迭代器驼壶,新元素插入到該迭代器之前

區(qū)間刪除:

C++11以上: iterator container::erase(iterator begin, iterator end);
C++11以下: 關(guān)聯(lián)容器erase()返回值void

將容器中[begin, end)前閉后開區(qū)間內(nèi)的元素刪除

區(qū)間賦值:

void container::assign(InputIterator begin, InputIterator end);

begin和end為舊容器中,被拷貝區(qū)間的起始

  • 條款6.當(dāng)心C++編譯器最煩人的分析機(jī)制

按照條款5中所說喉酌,嘗試使用list的區(qū)間構(gòu)造热凹,以一個文件中的全部內(nèi)容構(gòu)造一個list對象data

ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());

上面這段代碼乍一看好像沒有問題,從文件的begin開始泪电,一直迭代到NULL般妙,即文件末尾,以此區(qū)間內(nèi)的元素構(gòu)造對象data相速。
但是編譯器會將第二句代碼解析為一個函數(shù)聲明碟渺。問題出在構(gòu)造時使用了一個匿名迭代器
目前最好的解決方式突诬,是在數(shù)據(jù)聲明中避免使用匿名迭代器對象苫拍。

ifstream dataFile("ints.dat");
istream_iterator<int> dataBegin(dataFile);
istream_iterator<int> dataEnd;
list<int> data(dataBegin, dataEnd);

  • 條款7.如果在容器中包含了通過new操作創(chuàng)建的指針,切記在容器對象析構(gòu)前將指針delete掉

可以通過遍歷容器釋放指針旺隙,這樣做能行绒极,但不是異常安全的。
如果在向容器中放入和釋放指針時有異常拋出蔬捷,同樣會有資源泄露垄提。
所以最安全的做法是用引用計數(shù)智能指針(如Boost::shared_ptr)容器代替指針容器。
ps.不要創(chuàng)建auto_ptr的容器抠刺,并指望其中的指針被自動刪除塔淤,詳見下一條款


  • 條款8.切勿創(chuàng)建包含auto_ptr的容器對象

auto_ptr最大的古怪在于它的拷貝構(gòu)造和賦值操作符摘昌,會將被拷貝的指針置為NULL速妖。
STL算法中的sort()采用的快速排序算法,會用臨時對象拷貝vector中的值作為基準(zhǔn)值聪黎。
這將導(dǎo)致vector<auto_ptr>調(diào)用sort()時罕容,其中的值被臨時對象拷貝备恤,vector中的值被置為NULL,臨時對象在作用域結(jié)束時釋放了該auto_ptr锦秒。
ps.C++標(biāo)準(zhǔn)委員會做了很多使vector<auto_ptr>不被編譯通過露泊,并最終在C++11中移除了auto_ptr.


  • 條款9.慎重選擇刪除元素的方法

9.1.1. 對于連續(xù)內(nèi)存的容器(vector、deuqe和string)旅择,刪除元素的最好辦法是使用erase-remove
vec.erase( std::remove(vec.begin(), vec.end(), value), vec.end());

erase-remove講解:先要說一下erase和remove惭笑,

std::remove (Itertor first, Itertor last, const T& val);
    是<algorithm>中的算法,通過傳入的迭代器確定容器遍歷區(qū)間生真,將區(qū)間中不等于val的元素依次拷貝到區(qū)間中的前端沉噩。
    完成遍歷之后即確定了一段由first起始,沒有val值的新區(qū)間柱蟀,
    最后返回該新區(qū)間后一個位置的迭代器
iterator erase (iterator first, iterator last);
    將前開后閉區(qū)間[first, last)中的元素刪除川蒙,返回last
erase-remove:
    先通過remove將容器遍歷,將不等于value值的元素放在容器前端新區(qū)間
    再將新區(qū)間后一個位置的迭代器和容器的end()傳入erase长已,將新區(qū)間以外的部分刪除
9.1.2. 對于list畜眨,erase-remove同樣適用,但list::remove()更有效(詳見條款44)
list.remove(value);
從list中移除所有值等于value的元素
9.1.3. 對于關(guān)聯(lián)容器(set术瓮、multiset康聂、map、multimap)胞四,刪除元素正確且高效的方法是調(diào)用erase(高效的原因詳見條款19)早抠,不要對關(guān)聯(lián)容器使用std::remove(詳見條款22)
set.erase(value);
從set中刪除所有值為value的元素(multiset中也是刪除所有)

ps.書中提到,序列容器erase會返回下一個位置的迭代器撬讽,而關(guān)聯(lián)容器erase返回void蕊连,所以序列容器可以通過iter = vec.erase(iter)獲得erase后有效的迭代器,而關(guān)聯(lián)容器則要通過set.erase(iter++)后置++的方式獲得游昼。
不過我在QtCreator和VisualStudio2005兩個編譯器都試了下甘苍,關(guān)聯(lián)容器erase的返回值已經(jīng)是下一個元素的迭代器了,兩種容器可以都通過iter = vec.erase(iter)有效迭代烘豌,但對于序列容器不要使用vec.erase(iter++),因為序列容器調(diào)用erase后载庭,會使被刪除元素之后所有的迭代器失效(雖然QtCreator對此有優(yōu)化,但在visual studio中的確如此廊佩,還是不要這么使用的好)囚聚。

9.2. 要刪除容器中滿足特定判別式的所有對象,序列容器使用erase-remove_if标锄,list使用list::remove_if顽铸,關(guān)聯(lián)容器使用遍歷+erase(沒有erase_if)
bool badvalue(int); //特定判別式
vec.erase( std::remove_if( vec.begin(),  vec.end(),  badvalue));
    序列容器,遍歷vec料皇,刪除使badvalue返回true的對象
list.remove_if(badvalue);
    list::remove_if是刪除使badvalue返回true的對象的最好辦法
for(auto iter = set.begin(); iter != set.end(); /*for第三個參數(shù)什么也不做*/)
{
    if(badvalue(*i))  set.erase(iter++);
    else  ++iter;
}

如有錯誤谓松,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末星压,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鬼譬,更是在濱河造成了極大的恐慌娜膘,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件优质,死亡現(xiàn)場離奇詭異竣贪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巩螃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門贾富,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牺六,你說我怎么就攤上這事颤枪。” “怎么了淑际?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵畏纲,是天一觀的道長。 經(jīng)常有香客問我春缕,道長盗胀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任锄贼,我火速辦了婚禮票灰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宅荤。我一直安慰自己屑迂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布冯键。 她就那樣靜靜地躺著惹盼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惫确。 梳的紋絲不亂的頭發(fā)上手报,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音改化,去河邊找鬼掩蛤。 笑死,一個胖子當(dāng)著我的面吹牛陈肛,可吹牛的內(nèi)容都是我干的揍鸟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼燥爷,長吁一口氣:“原來是場噩夢啊……” “哼蜈亩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起前翎,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稚配,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后港华,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體道川,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年立宜,在試婚紗的時候發(fā)現(xiàn)自己被綠了冒萄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡橙数,死狀恐怖尊流,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灯帮,我是刑警寧澤崖技,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钟哥,受9級特大地震影響迎献,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腻贰,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一吁恍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧播演,春花似錦冀瓦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至顶霞,卻和暖如春肄程,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背选浑。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工蓝厌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人古徒。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓拓提,卻偏偏與公主長得像,于是被迫代替她去往敵國和親隧膘。 傳聞我的和親對象是個殘疾皇子代态,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容