文章目錄
- 順序容器概述
- 所有容器類型都支持的操作
- 迭代器
- 容器定義與初始化
- 將一個容器初始化為另一個容器的拷貝
- 標準庫array具有固定大小
- 賦值和swap
- 關系運算符
- 順序容器的特有操作
- 向順序容器添加元素
- 訪問元素
- 刪除元素
- 特殊的forward_list操作
- 改變容器的大小
- 容器操作可能是迭代器失效
順序容器概述
元素在順序容器中順序與其加入容器時的位置相對應。
選擇合適的順序容器:
- 除非有很好的理由選擇其他容器,否則使用vector;
- 程序有很多小的元素,且空間的額外開銷很重要,則不要使用list或forward_list;
- 要求隨機訪問,vector、deque;
- 要求在容器的中間插入或刪除,list或forward_list;
- 只在頭尾插入或刪除,deque;
- 輸入時要求在中間插入元素,隨后要求隨機訪問:如果在中間插入只是為了排序的話,可以在vector尾部添加元素之后在使用sort排序;如果必須在中間插入,則可以先使用list接收數據,在將接收的數據拷貝到vector中。
如果不確定應該使用哪種容器,那么可以在程序中只是用vector或list公共的操作:使用迭代器,不使用下標操作,避免隨機訪問。
雖然我們可以在容器中保存幾乎任何類型,但某些容器操作對元素類型有自己的特殊要求。例如順序容器的一個版本的構造函數接收容器大小參數,并使用元素類型的默認構造函數。我們可以定義保存這種類型對象的容器,但在構造這種容器時不能只傳遞元素數量:
//假定noDefault是一個沒有構造函數的類型
vector<noDefault> v1(10, init); //ok
vector<noDefault> v1(10); //err,必須提供一個元素初始化器
所有容器類型都支持的操作
迭代器
一個迭代器范圍由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者尾元素之后的位置。可以使用begin得到第一個元素位置,end得到尾元素之后的位置。
//顯式指定類型
list<string>::iterator it = a.begin();
list<string>::const_iterator it = a.begin();
//auto
auto it = a.begin(); //當a是const時,為const_iterator,否則為iterator
auto it = a.cbegin(); //const_iterator
當auto與begin或end結合使用時,獲得的迭代器類型依賴于容器類型。但與cbegin或cend結合時,獲得的是const_iterator。
容器定義與初始化
每個容器都定義了一個默認構造函數。除array之外,其他容器的默認構造函數都會創造一個指定類型的空容器。
將一個容器初始化為另一個容器的拷貝
將一個容器初始化為另一個容器的拷貝的方法有兩種:
- 可以直接拷貝整個容器,要求容器類型和元素類型必須匹配;
- 拷貝由一個迭代器對指定的元素范圍(array除外),不在要求要求容器類型和元素匹配,而要求拷貝的元素能夠轉化為目標元素即可。
list<string> authors = {"Mi", "Sh", "Au"};
vector<const char*> articles = {"a", "an", "the"};list<string> li(authors); //ok
deque<string> de(authors); //err
vector<string> words(articles); //err
//const char* 可以轉換為string
forward_list<string> words(articles.begin(), articles.end()); //ok
標準庫array具有固定大小
與內置數組一樣,標準庫array的大小也是類型的一部分。當定義一個array時,除了指定元素類型,還要指定容器大小:
array<int, 42> ia1; //42個默認初始化的int
array<int, 42> ia1 = {0}; //一個0,41個默認初始化的int
array<int, 42>::size_type i;
值得注意的是,內置數組不能進行拷貝或者賦值,但是array可以:
int digs[4] = {0, 1, 2, 3};
int cpy[4] = digs; //err,內置數組不支持拷貝或賦值
array<int, 4> digits = {0, 1, 2, 3}; //初始化后,不可使用{}賦值
array<int, 4> copy = digits; //ok,要求類型(容器類型以及大小)一致
賦值和swap
賦值運算符要求左右兩邊的運算對象具有相同的類型,它將右邊運算對象中的所有元素拷貝到左邊運算對象中。assign也可以實現類似的功能,但不在要求要求容器類型和元素匹配,而要求拷貝的元素能夠轉化為目標元素即可。
swap可以交換兩個相同類型容器的內容。除了array之外,swap不會對任何元素進行拷貝、刪除或插入操作,因此可以保證在常數時間完成,swap只是交換了兩個容器之間的內部結構。這意味著指向容器(string除外)的迭代器、引用或指針在swap之后不會失效。
例如,iter在swap之前指向svec[3],在swap之后就會指向svec2[3]:
vector<string> svec1(10);
vector<string> svec2(10);
swap(svec1, svec2);
與其他容器不同,swap會真正交換array中元素,雖然swap不會使得array中的指針、引用、迭代器失效,但是在swap之后解引用得到的結果是另外一個array相應位置的值。
關系運算符
每個容器都支持相等運算符(==、!=),除了無序關聯容器都支持關系運算符(>、>=、<、<=)。關系運算符左右兩邊運算對象必須是相同類型的容器,且必須保存相同類型的元素。并且只有當其元素也定義了相應的比較運算時,才可進行比較。
順序容器的特有操作
向順序容器添加元素
除array外,所有標準庫容器都提供靈活的內存管理。在運行時可以動態添加或刪除元素來改變容器的大小。
當我們用一個對象來初始化容器時,或將一個對象插入(push、insert)到容器時,實際上放入到容器中的是對象值得拷貝,而不是對象本身。值得注意得是,使用emplace時,則是將參數傳遞給元素得構造函數,并在容器內直接構造元素。
//使用三個參數構造函數
c.emplace_back("789", 25, 15.99);
//err
c.push_back("789", 25, 15.99);
//ok,創建一個臨時對象傳遞給push
c.push_back(Sales_data("789", 25, 15.99));
訪問元素
在容器中訪問元素的成員函數返回的都是引用:
if(c.empty())
{c.front() = 42;auto &v = c.back();v = 1024; //改變容器中的元素auto v2 c.back;v2 = 0; //無法改變容器中元素
}
刪除元素
特殊的forward_list操作
因為forward_list是單向列表,插入刪除等操作會影響目標節點之前的節點,因此以目標節點的前節點作為參數。
改變容器的大小
容器操作可能是迭代器失效
向容器中添加元素或從容器中刪除元素的操作可能會使指向容器中元素的指針、引用、迭代器失效。
添加、刪除元素:
- vector、string,且存儲空間被重新分配,則指向容器的迭代器、指針和引用全部失效。如果為重新分配存儲空間,則只失效指向插入后的元素的;
- deque,插入刪除到首尾之外的位置會導致指向其中元素的迭代器、指針和引用失效。
- list、forward_list,不會失效;
使用失效的迭代器、指針或引用是嚴重的運行時錯誤。