容器是一些特定類型對象的集合,容器類分為序列式容器和關聯容器兩種。 容器基本操作
容器類的一些基本操作如下圖:
定義和初始化
每個容器都定義了一個預設建構函式。除array之外,其他容器的預設建構函式都會建立一個指定類型的空容器,且都可以接受指定容器大小和元素初始值的參數。
將一個容器初始化為另一個容器的拷貝 將一個新容器建立為另一個容器的拷貝的方法有兩種:可以直接拷貝整個容器,或者拷貝由一個迭代器對指定的元素範圍。後者對array不適用。
用第一種方式建立一個容器為另一個容器的拷貝時,兩個容器的類型及其元素類型必須匹配。用第二種方式——傳遞迭代器參數,來拷貝時,就不要求容器類型是相同的了。而且新容器和原容器中的元素類型也可以不同,只要能將要拷貝的元素轉換為要初始化的容器的元素類型即可(例如可以將const char*元素轉換為string)。
序列式容器
序列式容器為程式員提供了控制元素儲存和順序訪問的能力。這種順序不依賴於元素的值,而是與元素加入容器時的位置相對應。
C++的序列式容器有vector、deque、list、forward_list、array和string幾種。它們都提供了快速順序訪問元素的操作。但它們在訪問順序、添加/刪除元素的代價方面存在差異。
string和vector都是可變大小的容器類,string專門用於儲存字元,兩者都將元素儲存在連續的記憶體空間中。由於元素是連續儲存的,由元素下標來計算其地址是非常快速的。但是,在這兩種容器的中間位置添加/刪除元素就會非常耗時:在一次插入/刪除操作後,需要移動插入/刪除之後的所有元素來保持連續儲存。而且,添加一個元素有時可能還需要分配額外的儲存空間,這種情況下每個元素都必須移動到新的儲存空間中。
list和forward_list兩個容器支援容器任何位置的快速插入/刪除操作,list是雙向鏈表,forward_list是單向鏈表。作為代價,這兩個容器不支援元素的隨機訪問,只能夠順序訪問。而且,與vector和deque、array相比,這兩個容器的額外記憶體開銷較大。
deque是一個雙端隊列,支援快速隨機訪問,但在除頭尾以外的位置添加/刪除元素的代價較大。但是,在deque的兩端添加/刪除元素的速度與list或forward_list的速度相當。
array是最接近原始數組的一個容器類,其對象的大小是固定的。因此,array不支援添加和刪除元素以及改變容器大小的操作。與內建數組相比,array是一種更安全、更容易使用的數群組類型。
序列式容器的特點決定我們應該在何種情況下使用何種容器。通常,使用vector是最好的選擇,除非你有很好的理由選擇其他容器。如果程式要求隨機訪問元素,不應使用list和forward_list。如果程式只需要在頭尾位置插入或刪除元素,則使用deque。一般來說,應用中佔主導地位的操作決定了容器類型的選擇。
相關操作 賦值和swap賦值運算子將其左邊容器中的全部元素替換為右邊容器中元素的拷貝:
c1 = c2;
c1 = {a, b, c};
第一個賦值運算後,左邊容器將與右邊容器相等。如果兩個容器原來大小不同,賦值運算後兩者的大小都與右邊容器的原大小相同。第二個賦值運算後,c1的size變為3,即花括弧列表中值的數目。
與內建數組不同,標準庫array類型允許賦值。賦值號左右兩邊的運算對象必須具有相同的類型。並且,由於array類型大小固定,還要求賦值號左右兩邊的運算對象的大小相等。總之,所有試圖改變原來array類型大小的操作都是不被允許的。
使用assign(僅序列式容器)
賦值運算子要求左邊和右邊的運算對象具有相同的類型。它將右邊運算對象中所有元素拷貝到左邊運算對象中。順序容器(array除外)定義了一個assign成員函數,允許我們從一個不同但相容的類型賦值,或者從容器的一個子序列賦值。assign操作用參數所指定元素的拷貝替換左邊容器中的所有元素。
list<string> names;
vector<const char*> oldstyle;
names.assign(oldstyle.cbegin(), oldstyle.cend());
上述代碼將names中的元素替換為迭代器指定的範圍中的元素的拷貝。assign的參數決定了容器中將有多少個元素以及值是什麼。這一操作是不能用賦值運算子完成的,因為兩個容器的類型不同。
使用swap swap操作交換兩個相同類型容器的內容。調用swap後,兩個容器中的元素將會交換。而且,用swap操作來交換兩個容器的速度會很快,因為元素本身並未交換,swap只是交換了兩個容器的內部資料結構。也即swap不對元素進行拷貝、刪除或插入操作,因此可以保證在常數時間內完成。
元素不會被移動的事實意味著,除string外,指向容器的迭代器、引用和指標在swap後都不會失效,它們仍然指向swap操作之前所指向的元素。但是,在swap操作後這些元素已經屬於不同的容器了。
swap操作有兩個例外,一個是string,對string調用swap會導致迭代器、引用和指標失效。另一個是array,對array調用swap操作會真正交換它們的元素。因此,對於array,swap操作後指標、引用和迭代器所綁定的元素位置保持不變,但元素值已經與另一個array中對應元素的值進行了交換。
上述對swap操作的描述都只針對非成員版本的swap,即std::swap
容器大小操作 出forward_list不支援size外,每個容器類型都有三個與大小相關的操作,這三個成員函數分別是:size、empty和max_size
序列式容器操作 添加元素
從下表可以看到,雖然某些容器不支援push_front操作,但它們對於insert操作並無類似的限制(插入開始位置)。因此我們可以使用insert將元素插入到容器的開始位置,而不必擔心容器是否支援push_front。但需要注意的是,雖然將元素插入到vector、deque和string中的任何位置都是合法的。然而,這樣做可能很耗時。
刪除元素
訪問元素
特殊的forward_list操作 當添加或刪除一個元素時,刪除或添加的元素之前的那個元素的後繼會發生改變。為了添加或刪除一個元素,我們需要訪問其前驅,以便改變前驅的連結。但是,forward_list是一個單向鏈表。在一個單向鏈表中,沒有簡單的方法來擷取一個元素的前驅。出於這個原因,在一個forward_list中添加或刪除元素的操作是通過改變給定元素之後的元素來完成的。這樣,我們總是可以訪問到被添加或刪除操作所影響的元素。
由於這些操作與其他容器上的操作的實現方式不同,forward_list並未定義insert、emplace和erase,而是定義了名為insert_after、emplace_after和erase_after的操作。
改變容器的大小
我們可以用resize來增大或縮小容器。如果當前大小大於所要求的大小,容器後部的元素會被刪除;如果當前大小小於所要求的大小,會將新元素添加到容器後部。
容器操作可能使迭代器失效
向容器中添加元素和從容器中刪除元素的操作可能會使指向容器元素的指標、引用或迭代器失效。一個失效的指標、引用或迭代器將不再表示任何元素。
在向容器添加元素後:
·如果容器是vector或string,且儲存空間被重新分配,則指向容器的迭代器、指標和引用都會失效。如果儲存空間未重新分配,指向插入位置之前的元素的迭代器、指標和引用仍有效,但指向插入位置之後元素的迭代器、指標和引用將會失效。
·對於deque,插入到除首尾位置之外的任何位置都會導致迭代器、指標和引用失效。如果在首尾位置添加元素,迭代器會失效,但指向存在的元素的引用和指標不會失效。
·對於list和forward_list,指向容器的迭代器(包括尾後迭代器和首前迭代器)、指標和引用仍有效。
從容器中刪除元素後:
·所有容器中指向被刪除元素的迭代器、指標和引用會失效。
·對於list和forward_list,指向容器其他位置的迭代器(包括尾後迭代器和首前迭代器)、引用和指標仍有效。
·對於deque,如果在首尾位置之外的任何位置刪除元素,那麼指向被刪除元素外的其他元素的迭代器、引用或指標也會失效。如果是刪除deque的尾元素,則尾後迭代器也會失效,但其他迭代器、引用和指標不受影響;如果是刪除首元素,這些也不會受影響。
·對於vector和string,指向被刪除元素之前的元素的迭代器、引用和指標仍有效。
由於向迭代器添加元素和從迭代器刪除元素的代碼可能會使迭代器失效,因此必須保證每次改變容器的操作之後都正確地重新置放迭代器。另外,最小化要求迭代器必須保持有效程式片段也是一個便於管理迭代器的好方法。
C++11開始的新標準庫比舊版本快得多,原因是新標準庫支援移動對象的操作,而舊標準庫只能拷貝對象。因此新標準庫容器的效能相較於舊標準庫有比較大的提升。
本文摘自《C++ Primer(中文版)第五版》