第9章 順序容器
順序容器概述(Overview of the Sequential Containers)
順序容器類型:
類型 | 特性 |
---|---|
vector |
可變大小數組。支持快速隨機訪問。在尾部之外的位置插入/刪除元素可能很慢 |
deque |
雙端隊列。支持快速隨機訪問。在頭尾位置插入/刪除速度很快 |
list |
雙向鏈表。只支持雙向順序訪問。在任何位置插入/刪除速度都很快 |
forward_list |
單向鏈表。只支持單向順序訪問。在任何位置插入/刪除速度都很快 |
array |
固定大小數組。支持快速隨機訪問。不能添加/刪除元素 |
string |
類似vector ,但用於保存字符。支持快速隨機訪問。在尾部插入/刪除速度很快 |
forward_list
和array
是C++11新增類型。與內置數組相比,array
更安全易用。forward_list
沒有size
操作。
容器選擇原則:
- 除非有合適的理由選擇其他容器,否則應該使用
vector
。 - 如果程序有很多小的元素,且空間的額外開銷很重要,則不要使用
list
或forward_list
。 - 如果程序要求隨機訪問容器元素,則應該使用
vector
或deque
。 - 如果程序需要在容器頭尾位置插入/刪除元素,但不會在中間位置操作,則應該使用
deque
。 - 如果程序只有在讀取輸入時才需要在容器中間位置插入元素,之後需要隨機訪問元素。則:
- 先確定是否真的需要在容器中間位置插入元素。當處理輸入數據時,可以先向
vector
追加數據,再調用標準庫的sort
函數重排元素,從而避免在中間位置添加元素。 - 如果必須在中間位置插入元素,可以在輸入階段使用
list
。輸入完成後將list
中的內容拷貝到vector
中。
- 先確定是否真的需要在容器中間位置插入元素。當處理輸入數據時,可以先向
- 不確定應該使用哪種容器時,可以先只使用
vector
和list
的公共操作:使用迭代器,不使用下標操作,避免隨機訪問。這樣在必要時選擇vector
或list
都很方便。
容器庫概覽(Container Library Overview)
每個容器都定義在一個頭文件中,文件名與類型名相同。容器均爲模板類型。
迭代器(Iterators)
forward_list
類型不支持遞減運算符--
。
一個迭代器範圍(iterator range)由一對迭代器表示。這兩個迭代器通常被稱爲begin
和end
,分別指向同一個容器中的元素或尾後地址。end
迭代器不會指向範圍中的最後一個元素,而是指向尾元素之後的位置。這種元素範圍被稱爲左閉合區間(left-inclusive interval),其標準數學描述爲[begin,end)
。迭代器begin
和end
必須指向相同的容器,end
可以與begin
指向相同的位置,但不能指向begin
之前的位置(由程序員確保)。
假定begin
和end
構成一個合法的迭代器範圍,則:
- 如果
begin
等於end
,則範圍爲空。 - 如果
begin
不等於end
,則範圍內至少包含一個元素,且begin
指向該範圍內的第一個元素。 - 可以遞增
begin
若干次,令begin
等於end
。
while (begin != end) { *begin = val; // ok: range isn't empty so begin denotes an element ++begin; // advance the iterator to get the next element }
容器類型成員(Container Type Members)
通過類型別名,可以在不瞭解容器元素類型的情況下使用元素。如果需要元素類型,可以使用容器的value_type
。如果需要元素類型的引用,可以使用reference
或const_reference
。
begin和end成員(begin and end Members)
begin
和end
操作生成指向容器中第一個元素和尾後地址的迭代器。其常見用途是形成一個包含容器中所有元素的迭代器範圍。
begin
和end
操作有多個版本:帶r
的版本返回反向迭代器。以c
開頭的版本(C++11新增)返回const
迭代器。不以c
開頭的版本都是重載的,當對非常量對象調用這些成員時,返回普通迭代器,對const
對象調用時,返回const
迭代器。
list<string> a = {"Milton", "Shakespeare", "Austen"}; auto it1 = a.begin(); // list<string>::iterator auto it2 = a.rbegin(); // list<string>::reverse_iterator auto it3 = a.cbegin(); // list<string>::const_iterator auto it4 = a.crbegin(); // list<string>::const_reverse_iterator
當auto
與begin
或end
結合使用時,返回的迭代器類型依賴於容器類型。但調用以c
開頭的版本仍然可以獲得const
迭代器,與容器是否是常量無關。
當程序不需要寫操作時,應該使用cbegin
和cend
。
容器定義和初始化(Defining and Initializing a Container)
容器定義和初始化方式:
將一個容器初始化爲另一個容器的拷貝時,兩個容器的容器類型和元素類型都必須相同。
傳遞迭代器參數來拷貝一個範圍時,不要求容器類型相同,而且新容器和原容器中的元素類型也可以不同,但是要能進行類型轉換。
// each container has three elements, initialized from the given initializers list<string> authors = {"Milton", "Shakespeare", "Austen"}; vector<const char*> articles = {"a", "an", "the"}; list<string> list2(authors); // ok: types match deque<string> authList(authors); // error: container types don't match vector<string> words(articles); // error: element types must match // ok: converts const char* elements to string forward_list<string> words(articles.begin(), articles.end());
C++11允許對容器進行列表初始化。
// each container has three elements, initialized from the given initializers list<string> authors = {"Milton", "Shakespeare", "Austen"}; vector<const char*> articles = {"a", "an", "the"};
定義和使用array
類型時,需要同時指定元素類型和容器大小。
array<int, 42> // type is: array that holds 42 ints array<string, 10> // type is: array that holds 10 strings array<int, 10>::size_type i; // array type includes element type and size array<int>::size_type j; // error: array<int> is not a type
對array
進行列表初始化時,初始值的數量不能大於array
的大小。如果初始值的數量小於array
的大小,則只初始化靠前的元素,剩餘元素會被值初始化。如果元素類型是類類型,則該類需要一個默認構造函數。
可以對array
進行拷貝或賦值操作,但要求二者的元素類型和大小都相同。
賦值和swap(Assignment and swap)
容器賦值操作:
賦值運算符兩側的運算對象必須類型相同。assign
允許用不同但相容的類型賦值,或者用容器的子序列賦值。
list<string> names; vector<const char*> oldstyle; names = oldstyle; // error: container types don't match // ok: can convert from const char*to string names.assign(oldstyle.cbegin(), oldstyle.cend());
由於其舊元素被替換,因此傳遞給assign
的迭代器不能指向調用assign
的容器本身。
swap
交換兩個相同類型容器的內容。除array
外,swap
不對任何元素進行拷貝、刪除或插入操作,只交換兩個容器的內部數據結構,因此可以保證快速完成。
vector<string> svec1(10); // vector with ten elements vector<string> svec2(24); // vector with 24 elements swap(svec1, svec2);
賦值相關運算會導致指向左邊容器內部的迭代器、引用和指針失效。而swap
操作交換容器內容,不會導致迭代器、引用和指針失效(array
和string
除外)。
對於array
,swap
會真正交換它們的元素。因此在swap
操作後,指針、引用和迭代器所綁定的元素不變,但元素值已經被交換。
array<int, 3> a = { 1, 2, 3 }; array<int, 3> b = { 4, 5, 6 }; auto p = a.cbegin(), q = a.cend(); a.swap(b); // 輸出交換後的值,即4、5、6 while (p != q) { cout << *p << endl; ++p; }
對於其他容器類型(除string
),指針、引用和迭代器在swap
操作後仍指向操作前的元素,但這些元素已經屬於不同的容器了。
vector<int> a = { 1, 2, 3 }; vector<int> b = { 4, 5, 6 }; auto p = a.cbegin(), q = a.cend(); a.swap(b); // 輸出交換前的值,即1、2、3 while (p != q) { cout << *p << endl; ++p; }
array
不支持assign
,也不允許用花括號列表進行賦值。
array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9}; array<int, 10> a2 = {0}; // elements all have value 0 a1 = a2; // replaces elements in a1 a2 = {0}; // error: cannot assign to an array from a braced list
新標準庫同時提供了成員和非成員函數版本的swap
。非成員版本的swap
在泛型編程中非常重要,建議統一使用非成員版本的swap
。
容器大小操作(Container Size Operations)
size
成員返回容器中元素的數量;empty
當size
爲0時返回true
,否則返回false
;max_size
返回一個大於或等於該類型容器所能容納的最大元素數量的值。forward_list
支持max_size
和empty
,但不支持size
。
關係運算符(Relational Operators)
每個容器類型都支持相等運算符(==
、!=
)。除無序關聯容器外,其他容器都支持關係運算符(>
、>=
、<
、<=
)。關係運算符兩側的容器類型和保存元素類型都必須相同。
兩個容器的比較實際上是元素的逐對比較,其工作方式與string
的關係運算符類似:
- 如果兩個容器大小相同且所有元素對應相等,則這兩個容器相等。
- 如果兩個容器大小不同,但較小容器中的每個元素都等於較大容器中的對應元素,則較小容器小於較大容器。
- 如果兩個容器都不是對方的前綴子序列,則兩個容器的比較結果取決於第一個不等元素的比較結果。
vector<int> v1 = { 1, 3, 5, 7, 9, 12 }; vector<int> v2 = { 1, 3, 9 }; vector<int> v3 = { 1, 3, 5, 7 }; vector<int> v4 = { 1, 3, 5, 7, 9, 12 }; v1 < v2 // true; v1 and v2 differ at element [2]: v1[2] is less than v2[2] v1 < v3 // false; all elements are equal, but v3 has fewer of them; v1 == v4 // true; each element is equal and v1 and v4 have the same size() v1 == v2 // false; v2 has fewer elements than v1
容器的相等運算符實際上是使用元素的==
運算符實現的,而其他關係運算符則是使用元素的<
運算符。如果元素類型不支持所需運算符,則保存該元素的容器就不能使用相應的關係運算。
順序容器操作(Sequential Container Operations)
向順序容器添加元素(Adding Elements to a Sequential Container)
除array
外,所有標準庫容器都提供靈活的內存管理,在運行時可以動態添加或刪除元素。
push_back
將一個元素追加到容器尾部,push_front
將元素插入容器頭部。
// read from standard input, putting each word onto the end of container string word; while (cin >> word) container.push_back(word);
insert
將元素插入到迭代器指定的位置之前。一些不支持push_front
的容器可以使用insert
將元素插入開始位置。
vector<string> svec; list<string> slist; // equivalent to calling slist.push_front("Hello!"); slist.insert(slist.begin(), "Hello!"); // no push_front on vector but we can insert before begin() // warning: inserting anywhere but at the end of a vector might be slow svec.insert(svec.begin(), "Hello!");
將元素插入到vector
、deque
或string
的任何位置都是合法的,但可能會很耗時。
在新標準庫中,接受元素個數或範圍的insert
版本返回指向第一個新增元素的迭代器,而舊版本中這些操作返回void
。如果範圍爲空,不插入任何元素,insert
會返回第一個參數。
list<string> 1st; auto iter = 1st.begin(); while (cin >> word) iter = 1st.insert(iter, word); // same as calling push_front
新標準庫增加了三個直接構造而不是拷貝元素的操作:emplace_front
、emplace_back
和emplace
,其分別對應push_front
、push_back
和insert
。當調用push
或insert
時,元素對象被拷貝到容器中。而調用emplace
時,則是將參數傳遞給元素類型的構造函數,直接在容器的內存空間中構造元素。
// construct a Sales_data object at the end of c // uses the three-argument Sales_data constructor c.emplace_back("978-0590353403", 25, 15.99); // error: there is no version of push_back that takes three arguments c.push_back("978-0590353403", 25, 15.99); // ok: we create a temporary Sales_data object to pass to push_back c.push_back(Sales_data("978-0590353403", 25, 15.99));
傳遞給emplace
的參數必須與元素類型的構造函數相匹配。
forward_list
有特殊版本的insert
和emplace
操作,且不支持push_back
和emplace_back
。vector
和string
不支持push_front
和emplace_front
。
訪問元素(Accessing Elements)
每個順序容器都有一個front
成員函數,而除了forward_list
之外的順序容器還有一個back
成員函數。這兩個操作分別返回首元素和尾元素的引用。
在調用front
和back
之前,要確保容器非空。
順序容器的元素訪問操作:
在容器中訪問元素的成員函數都返回引用類型。如果容器是const
對象,則返回const
引用,否則返回普通引用。
可以快速隨機訪問的容器(string
、vector
、deque
和array
)都提供下標運算符。保證下標有效是程序員的責任。如果希望確保下標合法,可以使用at
成員函數。at
類似下標運算,但如果下標越界,at
會拋出out_of_range
異常。
vector<string> svec; // empty vector cout << svec[0]; // run-time error: there are no elements in svec! cout << svec.at(0); // throws an out_of_range exception
刪除元素(Erasing Elements)
順序容器的元素刪除操作: