STL學(xué)習(xí)筆記之容器篇

容器


條款1:仔細(xì)選擇你的容器

C++提供了很多可供程序員使用的容器:
(1) 標(biāo)準(zhǔn)STL序列容器:vector均函,string,deque和list
(2) 標(biāo)準(zhǔn)STL關(guān)聯(lián)容器:set峡蟋,multiset晶通,map和multimap
(5) vector<char>可以作為string的替代品
(6) vector作為標(biāo)準(zhǔn)關(guān)聯(lián)容器的替代品
(7) 幾種標(biāo)準(zhǔn)非STL容器璃氢,包括數(shù)組、bitset狮辽、valarray一也、stack、queue和priority_queue
又新增了一些容器喉脖,包括:array椰苟,unordered容器,還有tuple容器树叽。

不同容器有不同的優(yōu)缺點(diǎn)舆蝴,用戶需要根據(jù)實(shí)際應(yīng)用的特點(diǎn)綜合決定使用哪種容器,如:vector是一種可以默認(rèn)使用的序列類型题诵,當(dāng)很頻繁地對(duì)序列中部進(jìn)行插入和刪除時(shí)應(yīng)該用list洁仗,當(dāng)大部分插入和刪除發(fā)生在序列的頭或尾時(shí)可以選擇deque這種數(shù)據(jù)結(jié)構(gòu)


條款2:小心對(duì)“容器無(wú)關(guān)代碼”的幻想

本條款要告誡程序員:編寫與容器無(wú)關(guān)的代碼是沒(méi)有必要的性锭。
有人想編寫這樣的程序赠潦,剛開始時(shí)使用vector存儲(chǔ),之后由于需求的變化篷店,將vector改為deque或者list祭椰,其他代碼不變臭家。實(shí)際上疲陕,這基本上是做不到的。這是因?yàn)椋?strong>不同的序列容器所對(duì)應(yīng)了不同的迭代器钉赁、指針和引用的失效規(guī)則蹄殃,此外,不同的容器支持的操作也不相同你踩,如:vector支持reserve()和capacity()诅岩,而deque和list不支持讳苦;即使是相同的操作,復(fù)雜度也不一樣(如:insert)吩谦,這會(huì)讓你的系統(tǒng)產(chǎn)生意想不到的瓶頸鸳谜。

此外,鼓勵(lì)程序員在聲明容器和迭代器的時(shí)候使用typedef進(jìn)行重命名式廷,這能夠?qū)δ愕某绦蜻M(jìn)行封轉(zhuǎn)咐扭,從而使用起來(lái)更簡(jiǎn)單,如有下面一個(gè)map容器:

map<string,vectorWidget>::iterator,CIStringCompare>;

如要用const_iterator遍歷這個(gè)map滑废,你需不止一次地寫下:

map<string, vectorWidget>::iterator, CIStringCompare>::const_iterator

如果使用typedef蝗肪,會(huì)快速方便很多。


條款3:使容器里對(duì)象的拷貝操作輕量而正確

容器容納了對(duì)象蠕趁,但不是你給它們的那個(gè)對(duì)象薛闪。當(dāng)你向容器中插入一個(gè)對(duì)象時(shí),你插入的是該對(duì)象的拷貝而不是它本身俺陋;當(dāng)你從容器中獲取一個(gè)對(duì)象時(shí)豁延,你獲取的是容器中對(duì)象的拷貝
拷貝是STL的基本工作方式倔韭。當(dāng)你刪除或者插入某個(gè)對(duì)象時(shí)术浪,現(xiàn)有容器中的元素會(huì)移動(dòng)(拷s貝);當(dāng)你使用了排序算法寿酌,remove胰苏、uniquer或者他們的同類,rotate或者reverse醇疼,對(duì)象會(huì)移動(dòng)(拷貝)硕并。
一個(gè)使拷貝更高效、正確的方式是建立指針的容器而不是對(duì)象的容器秧荆,即保存對(duì)象的指針而不是對(duì)象倔毙,然而,指針的容器有它們自己STL相關(guān)的頭疼問(wèn)題乙濒,改進(jìn)的方法是采用智能指針陕赃。


條款4:用empty來(lái)代替檢查size()是否為0

對(duì)于任意容器c,寫下

if (c.size() == 0)

本質(zhì)上等價(jià)于寫下

if (c.empty())

但是為什么第一種方式比第二種優(yōu)呢颁股?理由很簡(jiǎn)單:對(duì)于所有的標(biāo)準(zhǔn)容器么库,empty是一個(gè)常數(shù)時(shí)間的操作,但對(duì)于一些list實(shí)現(xiàn)甘有,size花費(fèi)線性時(shí)間诉儒。
這什么造成list這么麻煩?為什么不能也提供一個(gè)常數(shù)時(shí)間的size亏掀?如果size是一個(gè)常數(shù)時(shí)間操作忱反,當(dāng)進(jìn)行增加/刪除操作時(shí)每個(gè)list成員函數(shù)必須更新list的大小泛释,也包括了splice,這會(huì)造成splice的效率降低(現(xiàn)在的splice是常量級(jí)的)温算,反之怜校,如果splice不必修改list大小,那么它就是常量級(jí)地注竿,而size則變?yōu)榫€性復(fù)雜度韭畸,因此,設(shè)計(jì)者需要權(quán)衡這兩個(gè)操作的算法:一個(gè)或者另一個(gè)可以是常數(shù)時(shí)間操作蔓搞。


條款5:盡量使用區(qū)間成員函數(shù)代替單元素操作

給定兩個(gè)vector胰丁,v1和v2,怎樣使v1的內(nèi)容和v2的后半部分一樣喂分?
可行的解決方案有:
(1)使用區(qū)間函數(shù)assign:

v1.assign(v2.begin() + v2.size() / 2, v2.end());

(2)使用單元素操作:

vector<Widget>::const_iterator ci = v2.begin() + v2.size() / 2;
ci != v2.end();
++ci) 
v1.push_back(*ci);

(3)使用copy區(qū)間函數(shù)

v1.clear();
copy(v2.begin() + v2.size() / 2, v2.end(), back_inserter(v1));

(4)使用insert區(qū)間函數(shù)

v1.insert(v1.end(), v2.begin() + v2.size() / 2, v2.end());

最優(yōu)的方案是assign方案锦庸,理由如下:
首先,使用區(qū)間函數(shù)的好處是:
● 一般來(lái)說(shuō)使用區(qū)間成員函數(shù)可以輸入更少的代碼蒲祈。
● 區(qū)間成員函數(shù)會(huì)導(dǎo)致代碼更清晰更直接了當(dāng)甘萧。
使用copy區(qū)間函數(shù)存在的問(wèn)題是:
【1】 需要編寫更多的代碼,比如:v1.clear()梆掸,這個(gè)與insert區(qū)間函數(shù)類似
【2】 copy沒(méi)有表現(xiàn)出循環(huán)扬卷,但是在copy中的確存在一個(gè)循環(huán),這會(huì)降低性能
使用insert單元素版本的代碼對(duì)你征收了三種不同的性能稅酸钦,分別為:
【1】 沒(méi)有必要的函數(shù)調(diào)用怪得;
【2】 無(wú)效率地把v中的現(xiàn)有元素移動(dòng)到它們最終插入后的位置的開銷
【3】 重復(fù)使用單元素插入而不是一個(gè)區(qū)間插入必須處理內(nèi)存分配卑硫。

下面進(jìn)行總結(jié):
說(shuō)明:參數(shù)類型iterator表示容器的迭代器類型徒恋,也就是container::iterator,參數(shù)類型InputIterator表示可以接受任何輸入迭代器欢伏。
【1】 區(qū)間構(gòu)造
所有標(biāo)準(zhǔn)容器都提供這種形式的構(gòu)造函數(shù):

container::container(InputIterator begin,
// 區(qū)間的起點(diǎn)
InputIterator end);
// 區(qū)間的終點(diǎn)

【2】區(qū)間插入
所有標(biāo)準(zhǔn)序列容器都提供這種形式的insert:

void container::insert(iterator position,
// 區(qū)間插入的位置
InputIterator begin,
// 插入?yún)^(qū)間的起點(diǎn) 
InputIterator end);
// 插入?yún)^(qū)間的終點(diǎn)

關(guān)聯(lián)容器使用它們的比較函數(shù)來(lái)決定元素要放在哪里入挣,所以它們了省略position參數(shù)。

void container::insert(lnputIterator begin, InputIterator end);

【3】區(qū)間刪除
每個(gè)標(biāo)準(zhǔn)容器都提供了一個(gè)區(qū)間形式的erase硝拧,但是序列和關(guān)聯(lián)容器的返回類型不同径筏。序列容器提供了這個(gè):

iterator container::erase(iterator begin, iterator end);

而關(guān)聯(lián)容器提供這個(gè):

void
container::erase(iterator begin, iterator end);

為什么不同?解釋是如果erase的關(guān)聯(lián)容器版本返回一個(gè)迭代器(被刪除的那個(gè)元素的下一個(gè))會(huì)招致一個(gè)無(wú)法接受的性能下降.
注意這個(gè)特點(diǎn)在新版本的STL中已經(jīng)解決了

【4】 區(qū)間賦值
所有標(biāo)準(zhǔn)列容器都提供了區(qū)間形式的assign:

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

條款7:當(dāng)使用new得指針的容器時(shí)障陶,記得在銷毀容器前delete那些指針

條款8:永不建立auto_ptr的容器**//注意滋恬,在新版本中auto_ptr已經(jīng)不再被使用

條款9:在刪除選項(xiàng)中仔細(xì)選擇

(1)假定你有一個(gè)標(biāo)準(zhǔn)STL容器,c咸这,容納int夷恍,

Container<int> c;

而你想把c中所有值為1963的對(duì)象都去魔眨,則不同的容器類型采用的方法不同:沒(méi)有一種是通用的.

  • 如果采用連續(xù)內(nèi)存容器(vector媳维、queue和string)酿雪,最好的方法是erase-remove慣用法:
c.erase(remove(c.begin(), c.end(), 1963),c.end());
// 當(dāng)c是vector、string
// 或deque時(shí)侄刽,erase-remove慣用法是去除特定值的元素的最佳方法
  • 對(duì)于list指黎,最有效的方法是直接使用remove函數(shù):
c.remove(1963);
  • 對(duì)于關(guān)聯(lián)容器,解決問(wèn)題的適當(dāng)方法是調(diào)用erase:
c.erase(1963);
// 當(dāng)c是標(biāo)準(zhǔn)關(guān)聯(lián)容器時(shí),erase成員函數(shù)是去除特定值的元素的最佳方法

(2)讓我們換一下問(wèn)題:不是從c中除去每個(gè)有特定值的元素州丹,而是消除下面判斷式返回真的每個(gè)對(duì)象:

bool badValue( int x);
// 返回x是否是“bad”
  • 對(duì)于序列容器(vector醋安、list、deque和string)墓毒,只需要將remove換成remove_if即可:
// 當(dāng)c是vector吓揪、string
// 或deque時(shí)這是去掉badValue返回真的對(duì)象的最佳方法
c.erase(remove_if(c.begin(), c.end(), badValue),c.end());

// 當(dāng)c是list時(shí)這是去掉badValue返回真的對(duì)象的最佳方法
c.remove_if(badValue);
  • 對(duì)于關(guān)聯(lián)容器,有兩種方法處理該問(wèn)題所计,一個(gè)更容易編碼柠辞,另一個(gè)更高效≈麟剩“更容
    易但效率較低”的解決方案用remove_copy_if把我們需要的值拷貝到一個(gè)新容器中叭首,然后把原容器的內(nèi)容和新的交換:
    “更高效”的解決方案是直接從原容器刪除元素。不過(guò)踪栋,因?yàn)殛P(guān)聯(lián)容器沒(méi)有提供類似remove_if的成員函數(shù)焙格,所以我們必須寫一個(gè)循環(huán)來(lái)迭代c中的元素,和原來(lái)一樣刪除元素:
AssocContainer<int> c;
...
for(AssocContainer<int>::iterator i = c.begin();i != c.end();){
    if(badValue(*i))
         c.erase(i++);
    else
         ++i;
}
  • 對(duì)于vector夷都、string眷唉、list和deque,必須利用erase的返回值囤官。那個(gè)返回值正是我們需要的:一旦刪除完成厢破,它就是指向緊接在被刪元素之后的元素的有效迭代器。換句話說(shuō)治拿,我們這么寫:
for(SeqContainer<int>::iterator i = c.begin(); i != c.end();){
    if(badValue(*i)){
        i = c.erase(i);
      // 通過(guò)把erase的返回值賦給i來(lái)保持i有效
    } 
   else
       ++i;
}
條款12:對(duì)STL容器線程安全性的期待現(xiàn)實(shí)一些

在工程中多線程操作STL的場(chǎng)景應(yīng)該還是比較常見的摩泪,一個(gè)典型的例子就是用其來(lái)做生產(chǎn)者——消費(fèi)者模型的隊(duì)列或者其他共享隊(duì)列,這樣為了應(yīng)對(duì)線程安全問(wèn)題我們必須自己對(duì)容器操作進(jìn)行封裝劫谅。這是我自己實(shí)現(xiàn)的的封裝類:

template< typename Container>    
// 獲取和釋放容器的互斥量的類的模板核心见坑;
class Lock {                    
    public :                         
    // 忽略了很多細(xì)節(jié)
       Lock( const Containers container) : c(container){  
          // 在構(gòu)造函數(shù)獲取互斥量       
          getMutexFor(c); 
       }
      ~Lock(){
           // 在析構(gòu)函數(shù)里釋放它
           releaseMutexFor(c); 
       }
  private:
      const Container& c;
};

原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明: 轉(zhuǎn)載自董的博客
本文鏈接地址: http://dongxicheng.org/cpp/effective-stl-part1/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捏检,一起剝皮案震驚了整個(gè)濱河市荞驴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贯城,老刑警劉巖熊楼,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異能犯,居然都是意外死亡鲫骗,警方通過(guò)查閱死者的電腦和手機(jī)犬耻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)执泰,“玉大人枕磁,你說(shuō)我怎么就攤上這事∈趿撸” “怎么了计济?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)排苍。 經(jīng)常有香客問(wèn)我沦寂,道長(zhǎng),這世上最難降的妖魔是什么淘衙? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任凑队,我火速辦了婚禮,結(jié)果婚禮上幔翰,老公的妹妹穿的比我還像新娘漩氨。我一直安慰自己,他們只是感情好遗增,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布叫惊。 她就那樣靜靜地躺著,像睡著了一般做修。 火紅的嫁衣襯著肌膚如雪霍狰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天饰及,我揣著相機(jī)與錄音蔗坯,去河邊找鬼。 笑死燎含,一個(gè)胖子當(dāng)著我的面吹牛宾濒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屏箍,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼绘梦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了赴魁?” 一聲冷哼從身側(cè)響起卸奉,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颖御,沒(méi)想到半個(gè)月后榄棵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年疹鳄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拧略。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尚辑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盔腔,到底是詐尸還是另有隱情杠茬,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布弛随,位于F島的核電站瓢喉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舀透。R本人自食惡果不足惜栓票,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愕够。 院中可真熱鬧走贪,春花似錦、人聲如沸惑芭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遂跟。三九已至逃沿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間幻锁,已是汗流浹背凯亮。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哄尔,地道東北人假消。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像岭接,于是被迫代替她去往敵國(guó)和親置谦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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