本文主要討論C++標準庫中的順序容器及相應的容器適配器,這些內容主要涉及順序容器類型:vector、list、deque,順序容器適配器類型:stack、queue、priority_queue。
標準庫中的容器分為順序容器和關聯容器。順序容器(sequential container)內的元素按其位置儲存和訪問,顧名思義,這些內部元素是順序存放的;順序容器內的元素排列次序與元素值無關,而是由元素添加到容器裡的次序決定。而關聯容器的元素按鍵(key)排序。
容器類共用部分公用介面。標準庫定義的三種順序容器類型:vector、list、deque(double-ended queue的縮寫,發音為“deck”),它們的差別僅在訪問元素的方式,以及添加或刪除元素相關操作的代價。順序容器適配器包括:stack、queue和priority_queue。容器只定義了少量操作,大多數操作由演算法庫提供。如果兩個容器提供了相同的操作,則它們的介面(函數名和參數個數)應該相同。
| 標準容器類 |
說明 |
| 順序性容器 |
| vector |
從後面快速的插入與刪除,直接存取任何元素 |
| deque |
從前面或後面快速的插入與刪除,直接存取任何元素 |
| list |
雙鏈表,從任何地方快速插入與刪除 |
| 關聯容器 |
| set |
快速尋找,不允許重複值 |
| multiset |
快速尋找,允許重複值 |
| map |
一對多映射,基於關鍵字快速尋找,不允許重複值 |
| multimap |
一對多映射,基於關鍵字快速尋找,允許重複值 |
| 容器適配器 |
| stack |
後進先出 |
| queue |
先進先出 |
| priority_queue |
最高優先順序元素總是第一個出列 |
容器類型
| vector |
容器,支援快速隨機訪問(連續儲存) |
| list |
鏈表,支援快速插入/刪除 |
| deque |
雙端隊列,支援隨機訪問(連續儲存),兩端能快速插入和刪除 |
| stack |
棧 |
| queue |
隊列 |
| priority_queue |
優先順序隊列 |
順序容器的定義順序容器的構造和初始化下表為迭代器為所有容器類型所提供的運算:
| *iter |
傳回型別iter所指向的元素的引用 |
| iter->mem |
對iter進行解引用,並取得指定成員 |
| ++iter |
給iter加1,使其指向容器中下一個元素 |
| iter++ |
|
| --iter |
給iter減1,使其指向容器中前一個元素 |
| iter-- |
|
| iter1 == iter2 |
當兩個迭代器指向同一個容器中的同一元素,或者當它們都指向 |
| iter1 != iter2 |
同一個容器的超出末端的下一個位置時,兩個迭代器相等。 |
vector和deque容器的迭代器提供了額外的運算:迭代器的算術運算和另一些關係運算,如下表所示:
| iter + n |
在迭代器上加(減)整數值,將產生指向容器中前面(後面)第n個元素的迭代器; |
| iter - n |
新計算出來的迭代器必須指向容器中的元素或超出容器末端的下一位置。 |
| iter1 += iter2 |
複合運算:先加(減),再賦值 |
| iter1 -= iter2 |
|
| iter1 - iter2 |
只適用於vector和deque |
| >, >=, <, <= |
比較迭代器的位置關係;只適用於vector和deque |
關係操作符只適用於vector和deque容器,這是因為只有這兩種容器為其元素提供快速、隨機的訪問。它們確保可根據元素位置直接有效地訪問指定的容器元素。這兩種容器都支援通過元素位置實現的隨機訪問,因此它們的迭代器可以有效地實現算術和關係運算。迭代器範圍:[first, last)是一個左閉合區間,表示範圍從first開始,到last結束,但不包括last。注意:如果first不等於last,則對first反覆做自增運算必須能夠到達last;否則,即last位於first之前,則將發生未定義行為。
迭代器範圍使用左閉合的意義:因為這樣可以統一表示空集,就無需特別處理。 另外,使用迭代器時,要特別留意迭代器的可能的失效問題。訪問元素
| back() |
返回容器的最後一個元素的引用。如果容器為空白,則該操作未定義 |
| front() |
返回容器的第一個元素的引用。如果容器為空白,則該操作未定義 |
| c[n] |
返回下標為n的元素的引用;如果n<0 or n>=size(),則該操作未定義 (註:只適用於vector和deque容器) |
| at[n] |
返回下標為n的元素的引用;如果下標無效,則拋出異常out_of_range異常 (註:只適用於vector和deque容器) |
刪除元素
| erase(p) |
刪除迭代器p所指向的元素。返回一個迭代器,它指向被刪除的元素後面的元素。如果p指向容器內最後一個元素,則返回的迭代器指向容器的超出末端的下一個位置;如果p本身就是指向超出末端的下一個位置的迭代器,則該函數未定義 |
| erase(b, e) |
刪除[b, e)內的所有元素。返回一個迭代器,它指向被刪除元素段後面的元素。如果e本身就是指向超出末端的下一個位置的迭代器,則返回的迭代器也指向超出末端的下一個位置。 |
| clear() |
刪除容器內的所有元素,返回void |
| pop_back() |
刪除容器內的最後一個元素,返回void。如果容器為空白,則該操作未定義。 |
| pop_front() |
刪除容器內的第一個元素,返回void。如果c為空白容器,則該操作未定義 (註:只適用於list和deque容器) |
賦值與swap
| c1 = c2 |
刪除容器c1的所有元素,然後將c2的元素複製給c1。c1和c2的類型必須相同。 |
| c1.swap(c2) |
交換內容:調用該函數後,c1中存放的是c2原來的元素,c2中存放的是c1原來的元素。c1和c2的類型必須相同。該函數的執行速度通常要比將c2的元素複製到c1的操作快。 |
| c.assign(b, e) |
重新設定c的元素:將迭代器b和e標記的範圍內所有的元素複製到c中。b和e必須不是指向c中元素的迭代器。 |
| c.assign(n, t) |
將容器c重新設定為儲存n個值為t的元素。 |
注意:assign操作首先刪除容器內所有的元素,再將參數所指定的新元素插入到容器中。 swap操作不會刪除或插入任何元素,而且保證在常量時間內實現交換。由於容器內沒有移動任何元素,因此迭代器不會失效。但要注意這些迭代器指向了另一個容器中的元素。
容器的選用:vector和deque容器提供了對元素的快速存取,但付出的代價是,在容器的任意位置插入或刪除元素,比在容器尾部插入和刪除的開銷更大,因為要保證其連續儲存,需要移動元素;list類型在任何位置都能快速插入和刪除,因為不需要保證連續儲存,但付出的代價是元素的隨機訪問開銷較大。特徵如下:
1)與vector容器一樣,在deque容器的中間insert或erase元素效率比較低; 2)不同於vector容器,deque容器提供高效地在其首部實現insert和erase的操作,就像在尾部一樣; 3)與vector容器一樣而不同於list容器的是,deque容器支援對所有元素的隨機訪問。 4)在deque容器首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效。在deque容器的任何其他位置的插入和刪除操作將使指向該容器元素的所有迭代器都失效。
容器的比較:
vector (連續的空間儲存,可以使用[]操作符)快速的訪問隨機的元素,快速的在末尾插入元素,但是在序列中間歲間的插入,刪除元素要慢,而且如果一開始分配的空間不夠的話,有一個重新分配更大空間,然後拷貝的效能開銷。
deque (小片的連續,小片間用鏈表相連,實際上內部有一個map的指標,因為知道類型,所以還是可以使用[],只是速度沒有vector快)快速的訪問隨機的元素,快速的在開始和末尾插入元素,隨機的插入,刪除元素要慢,空間的重新分配要比vector快,重新分配空間後,原有的元素不需要拷貝。對deque的排序操作,可將deque先複製到vector,排序後在複製回deque。
list (每個元素間用鏈表相連)訪問隨機元素不如vector快,隨機的插入元素比vector快,對每個元素分配空間,所以不存在空間不夠,重新分配的情況。
set:內部元素唯一,用一棵平衡樹結構來儲存,因此遍曆的時候就排序了,尋找也比較快的哦。
map :一對一的映射的結合,key不能重複。
stack :適配器,必須結合其他的容器使用,stl中預設的內部容器是deque。先進後出,只有一個出口,不允許遍曆。
queue: 是受限制的deque,內部容器一般使用list較簡單。先進先出,不允許遍曆。
vector<bool> 與bitset<> ,前面的可以動態改變長度。
priority_queue: 插入的元素就有優先順序順序,top出來的就是優先順序最高的了
valarray 專門進行數值計算的,增加特殊的數學函數。
一些容器選用法則: 1)
如果程式要求隨機訪問元素,則應使用vector或deque容器; 2)
如果程式必須在容器的中間位置插入或刪除元素,則應採用list容器; 3)如果程式不是在容器的中間位置,而是在容器首部或尾部插入或刪除元素,則應採用deque容器; 4)如果只需要在讀取輸入時在容器的中間位置插入元素,然後需要隨機訪問元素,則可以在輸入時將元素讀入到一個list容器中,然後對容器排序,再將排序後的list容器複製到vector容器中。 5)如果程式既需要隨機訪問,又需要在容器的中間位置插入或刪除元素,此時應當權衡哪種操作的影響較大,從而決定選擇list容器還是vector或deque容器。註:此時若選擇使用vector或deque容器,可以考慮只使用它們和list容器所共有的操作,比如使用迭代器而不是下標,避免隨機訪問元素等,這樣在必要時,可以很方便地將程式改寫為使用list容器。容器適配器 適配器(adaptor)是標準庫中通用的概念,包括容器適配器、迭代器適配器和函數適配器。本質上,適配器是使一事物的行為類似於另一事物的行為的一種機制。容器適配器讓一種已存在的容器類型採用另一種不同的抽象類別型的工作方式實現,只是發生了介面轉換而已。
標準庫提供了三種順序容器適配器:queue, priority_queue和stack。 所有適配器都定義了兩個建構函式:預設建構函式用於建立Null 物件,而帶一個容器參數的建構函式將參數容器的副本作為其基礎值。 預設的stack和queue都基於deque容器實現,而priority_queue則在vector容器上實現。在建立適配器時,通過將一個順序容器指定為適配器的第二個型別參數,可覆蓋其關聯的基礎容器類型。例如: stack<int, vector<int> > int_stack; // 此時,int-stack棧是基於vector實現 對於給定的適配器,其關聯的容器必須滿足一定的約束條件。stack適配器所關聯的基本容器可以是任意一種順序容器類型,因為這些容器類型都提供了push_back、pop_back和back操作;queue適配器要求其關聯的基礎容器必須提供pop_front操作,因此其不能建立在vector容器上;priority_queue適配器要求提供隨機訪問功能,因此不能建立在list容器上。 兩個相同類型的適配器可以做==, !=, <, >, <=, >=這些關係運算,只要其基本元素類型支援==和<兩個操作即可。這與容器大小比較原則一致。這與容器大小比較原則一致。
棧
| s.empty() |
如果棧為這人,則true;否則返回false |
| s.size() |
返回棧中元素的個數 |
| s.pop() |
刪除棧頂元素,但不返回其值 |
| s.top() |
返回棧頂元素的值,但不刪除該元素 |
| s.push(item) |
在棧項壓入新元素 |
隊列和優先順序隊列 標準庫隊列使用了先進先出(FIFO)的儲存和檢索策略,進入隊列的元素被放置在尾部,下一個被取出的元素則取自隊列的首部。 priority_queue預設使用元素類型的 < 操作符來確定它們之間的優先順序關係,使用者也可以定義自己的優先順序關係。在優先順序隊列中,新元素被放置在比它優先順序低的元素的前面。
| q.empty() |
如果隊列為空白,則返回true;否則返回false |
| q.size() |
返回隊列中元素的個數 |
| q.pop() |
刪除隊首元素,但不返回其值 |
| q.front() |
返回隊首元素的值,但不刪除該元素 (註:該操作只適用於隊列) |
| q.back() |
返回隊尾元素的值,但不刪除該元素 (註:該操作只適用於隊列) |
| q.top() |
返回具有最高優先順序的元素值,但不刪除該元素 (註:該操作只適用於優先順序隊列。MSDN也為queue提供了該操作) |
| q.push(item) |
對於queue,在隊尾壓入一個新元素; 對於priority_queue,在基於優先順序的適當位置插入新元素 |