C++ 學習筆記之(9)-順序容器及適配器
順序容器就是元素的順序與其加入容器時的位置相對應,而 關聯容器中元素的位置由元素相關聯的關鍵字值決定。所有容器類都共享公共的藉口,只是不同的容器會按不同方式對其進行擴展。
順序容器概述
下表列出標準庫中的順序容器,所有順序容器都提供了快速順序訪問元素的能力,只是在兩方面有不同的性能折中
- 向容器添加或從容器中刪除元素的代價
非順序訪問容器中元素的代價
除了固定大小的
array
外,其他容器都提供高效、靈活的內存管理string
和vector
將元素保存在連續的內存空間中,故支持隨機訪問,但中間位置添加和刪除操作非常耗時list
和forward_list
在任何位置添加和刪除操作都很快速,但不支持隨機訪問deque
支持隨機訪問,但中間位置添加和刪除元素代價很高,但兩端添加或刪除元素很快forward_list
和array
是C++11增加的類型,array
大小固定,不支持添加和刪除以及其他改變容器大小的操作。
容器庫概覽
迭代器
- 迭代器範圍(iterator range):由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者是尾元素之後的位置,通常被稱爲
being
和end
範圍是左閉合區間[begin, end)
容器類型成員
容器定義了多個類型,如圖所示
begin
和end
成員
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
容器定義和初始化
每個容器類型都定義了一個默認構造函數,除array
外,其他容器的默認構造函數都會創建一個指定類型的空容器,且都可以接受指定容器大小和元素初始值的參數
將一個容器初始化爲另一個容器的拷貝
兩種方法
直接拷貝整個容器,要求兩個容器的類別及其元素類型必須相同
拷貝由一個迭代器對指定的元素範圍,
array
除外, 不要求容器類別相同,元素類型也可以不用,但要能互相轉換list<string> authors = {"Milton", "Shakespeare", "Austen"}; vector<const char *> articles = {"a", "an", "the"}; list<string> list2(authors); // 正確:類型匹配 deque<string> authList(authors); // 錯誤:容器類型不匹配 vector<string> words(articles); // 錯誤:容器類型必須匹配 forward_list<string> words(articles.begin(), articles.end()); // 正確:元素類型可以轉換
標準庫
array
的大小是類型的一部分,定義array
時,除了指定元素類型,還要指定容器大小array<int, 42>; // 類型爲:保存 42 個 int 的數組 array<int, 10>::site_type i; // 數組類型包括元素類型和大小 array<int>::sizt_type j; // 錯誤: array<int> 不是一個類型
內置數組類型無法進行拷貝或對象賦值操作,但
array
可以
賦值和 swap
下表列出的與賦值相關的運算符可用於所有容器
swap
操作交換兩個相同類型容器的內容, 元素本身併爲交換, 只是交換了兩個容器的內部數據結構,故可在常數時間內完成,且指向容器的迭代器、引用和指針在swap
操作後不會失效swap
兩個array
會真正交換他們的元素,所需時間與array
中元素數目成正比,且雖然指針、引用和迭代器綁定的元素保持不變,但元素值已經交換了。
容器大小操作
每個容器類型都有三個大小相關操作
- 成員函數
size
:返回容器中元素的數目, 但forward_list
不支持size
empty
:當size
爲0
時,返回true
max_size
:返回一個大於或等於該類型容器所能容納的最大元素數的值
關係運算符
每個容器類型都支持相等運算符==
和!=
, 除了無序關聯容器
外的所有容器都支持關係運算符>、>=、<、<=
。
- 若兩個容器大小相同且元素相等,則相等
- 若大小不同,但較小容器每個元素都等於較大容器的對應元素時,較小容器大於較大容器
- 若兩個容器都不是另一個容器的前綴子序列,結果取決於第一個不相等的元素的比較結果
容器的關係運算符使用元素的關係運算符完成比較
順序容器操作
順序容器和關聯容器的不同之處在於組織元素的方式,所以導致元素的存儲、訪問、添加和刪除的不同。下面介紹順序容器特有的操作
向順序容器中添加元素
對於
vector
和string
的尾部之外的位置添加元素,都需要移動元素,可能還會導致整個對象存儲空間的重新分配。重新分配對象的存儲空間需要分配新的內存,並將元素從舊空間移動到新空間中對於
deque
首尾之外的任何位置添加元素,都要移動元素,當用對象初始化容器或插入到容器中時,實際上放入到容器中的是對象值的拷貝,並不是對象本身。
記住,
insert
插入在元素之前,並且返回指向新插入元素的迭代器emplace
操作將參數傳遞給元素類型的構造函數,直接在容器管理的內存空間中構造函數。而push
或insert
操作是傳遞元素類型對象,然後拷貝到容器中// 在 c 的末尾構造一個 Sales_data 對象,使用 Sales_data 的三個參數的構造函數 c.emplace_back("978-0590353403", 25, 15.99); //錯誤:沒有接受三個參數的 push_back 版本 c.push_back("978-0590353403", 25, 15.99); // 正確:創建一個臨時的 Sales_data 對象傳遞給 push_back c.push_back(Sales_data("978-0590353403", 25, 15.99));
訪問元素
- 訪問成員函數(即
front
、back
、下標和at
)返回的都是引用。若容器是const
對象,則返回值是const
引用 - 下標運算符不檢查下標是否合法,若想確保下標合法,可使用
at
成員函數,越界會拋出out_of_range
異常
刪除元素
特殊的forward_list
操作
由於在forward_list
中,添加或刪除元素都會改變後繼元素,所以添加和刪除的操作都是通過改變給定元素之後的元素完成的。
改變容器大小
如圖所示,可使用resize
增大或縮小容器。array
不支持
容器操作可能是迭代器失效
對容器進行添加或刪除元素的操作可能會使指向容器元素的指針、引用或迭代器失效
添加元素
vector
或string
- 重新分配存儲空間:迭代器、指針和引用都會失效
- 未重新分配存儲空間:指向插入位置之前的元素的迭代器、指針和引用仍有效,指向之後的將失效
deque
- 插入到除首尾位置之外的任何位置:都會導致迭代器、指針和引用失效
- 若在首尾位置添加元素:迭代器失效,但指針和引用有效
list
和forward_list
- 指向容器的迭代器(包括尾後迭代器和首前迭代器)、指針和引用仍有效
刪除元素(指向被刪除元素的迭代器、指針和引用都會失效)
vector
和string
- 指向被刪除元素之前的迭代器、引用和指針:仍有效
deque
- 若在首尾之外的任何位置刪除元素:都會失效
- 若刪除尾元素:尾後迭代器也會失效,其他不受影響
- 刪除首元素:都有效
list
和forward_list
- 都有效
vector
對象是如何增長的
vector
爲了支持快速隨機訪問,故將元素連續存儲。當空間不足時,就會重新分配空間,將元素從舊空間移動到新空間,然後添加新元素,釋放舊存儲空間。 vector
和string
通常會分配比新空間需求更大的內存空間,預留些空間做備用,可以減少容器空間重新分配的次數
* vector
的實現策略是每次分配新內存空間時將當前容量翻倍
* shrink_to_fit
請求vector
將超出當前大小的多餘內存退回給系統,但標準庫並不保證
額外的string
操作
構造string
的其他方法
C++ 學習筆記之(3)-字符串、向量和數組 介紹過string
的構造函數,以及與其他順序容器相同的構造函數外,string
還有其他三個構造函數,如表所示
- 若用
const char *
創建string
時,指針執行的數組必須以空字符結尾,拷貝操作遇到空字符時停止
substr
操作
改變string
的其他方法
replace
草市是調用erase
和insert
的一種簡寫形式
string
搜索操作
string::npos
:static
成員,類型爲const string::size_type
類型,初始值爲-1
,無符號類型
compare
函數
除了關係運算符外,string
類型還提供了一組compare
函數,與C
標準庫的strcmp
函數很相似
數值轉換
新標準引入了多個函數,用來實現數值數據與標準庫string
之間的轉換。
轉換爲數值的
string
中第一個非空白字符必須是數值中可能出現的字符string s2 = "pi = 3.14"; // 轉換 s2 中以數字開始的第一個子串,結果 d = 3.14 d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
若
string
不能轉換爲一個數值,則函數拋出invalid_argument
異常。若轉換得到的數值無法用任何類型表示,則拋出out_of_range
異常
容器適配器
除順序容器外,標準庫還定義了三個容器適配器:stack
, queue
和priority_queue
。
- 適配器(adaptor):標準庫的通用概念。容器、迭代器和函數都有適配器,其本質上是一種機制,可以是某種事物的行爲表現成另一種事物。
默認情況下,
stack
和queue
基於deque
實現的,priority_queue
是在vector
之上實現的所有適配器都要求容器具有添加和刪除元素的能力,故適配器不能構造在
array
之上
棧適配器
stack
只要求push_back
、pop_back
和back
操作,故可使用除array
和forward_list
之外的任何容器構造
隊列適配器
queue
適配器要求back
、push_back
、front
和push_front
, 故可構造於list
或queue
之上,但不能基於vector
構造priority_queue
除了front
、push_back
和pop_back
操作外,還要求隨機訪問能力, 故可構造於vector
和deque
之上,但不能基於list
構造
結語
- 標準庫容器是模板類型,用來保存給定類型的對象。順序容器中元素是順序存放的,通過位置來訪問
- 所有容器(除
array
外)都提供高效的動態內存管理 - 在容器中執行添加和刪除操作後,要注意這些操作可能是其迭代器、引用和指針失效