1. 程式人生 > >C++Primer——《第九章1 》“ 順序容器概述、容器定義和初始化”

C++Primer——《第九章1 》“ 順序容器概述、容器定義和初始化”

目錄

順序容器概述

確定使用哪種順序容器

容器操作

迭代器範圍

迭代器的特性

迭代器的型別

類型別名

begin 和 end 成員

容器定義和初始化

將一個容器初始化為另一個容器的拷貝

列表初始化 (C++11)

與順序容器相關的建構函式

標準庫 array 具有固定大小

賦值與swap

使用 assign(關聯容器和array除外、只有順序容器可以使用)

使用 swap

關係運算符

容器的關係運算符使用元素的關係運算符完成比較


順序容器概述


● 所有順序容器都提供了快速順序訪問元素的能力。但是,這些容器在以下方面都有不同的效能折中:

向容器新增或從容器中刪除元素的代價

非順序訪問容器中元素的代價

● 在 deque兩端新增或刪除元素與 list 或 forward_list 新增刪除的速度相當。該容器支援快速的隨機訪問。

● 與內建陣列相比,array 是一種更加安全、更容易使用的陣列型別,array物件的大小是固定的,array不支援新增刪除元素以及改變容器大小的操作。 array 也不像其他容器那樣支援高效、靈活的記憶體管理。

● 注意: forward list的設計目標是達到與最好的手寫的單向連結串列資料結構相當的效能。因此,forward_list 沒有 size 操作,因為儲存或計算其大小就會比手寫連結串列多出額外的開銷。forward_list

還不支援“反向容器的成員”。 forward_list 迭代器不支援遞減運算子

 

● forward_list 和 list 不支援 元素的隨機訪問,為了訪問一個元素 ,我們只能遍歷整個容器,vector, deque 和 array相比,這兩個容器的額外記憶體開銷也很大。

● 與 vector 和 deque 不同, list的迭代器不支援 < 運算, 只支援 遞增、遞減、以及 != 運算 。

●  標準庫容器中所有的迭代器都定義了遞增運算子,第當前元素移到下一個運算。

● 注意: forward_list 支援 empty、max_size,但不支援size。

● forward_list 不支援  reverse_iterator、const_reverse_iterator、c.rbegin(), c.rend ()、c.crbegin(), c.crend ()  這些反向容器的成員。

● forward_list不支援 push_back、emplace_back、 pop_back、insert、emplace、erase操作。

● vector 和 string 不支援 push_front、 和 emplace_front、 pop_front.

● shrink_to_fit()函式只適用於 vector 和 string。

 

vector <string> svec;

svec.insert(svec.begin(),"Hello!"); //我們可以使用insert 。將元素插入到第一個元素之前,但是可能很耗時。

● 除了array和 forward_list 之外, 每個順序容器(包括string型別)都支援push_back。

● 向一個vector、string 或 deque 插入元素會使所有指向容器的迭代器、引用和指標失效。

● 向一個vector或string新增元素可能引起整個物件儲存空間的重新分配。 重新分配一個物件的儲存空間需要分配新的記憶體, 並將元素從舊的空間移動到新的空間中。

● list、forward_list、deque這些容器還支援名為push_front 的類似操作。

● vector 、list、deque、string都支援insert成員,forward_list 支援特殊版本的insert成員。

● 包括array在內的每個順序容器都有一個front成員函式, 而除了forward_list之外的所有順序容器都有一個back成員函式。 分別返回首元素和尾元素的引用。

● array 不支援 resize操作來改變容器大小。


確定使用哪種順序容器


以下是選擇容器的基本原則:

  • 除非你有很好的理由選擇其他容器, 否則應使用vector。
  • 如果你的程式有很多小的元素,且空間的額外開銷很重要,則不要使用list或forward_list.
  • 如果程式要求隨機訪問元素,應使用vector或deque.
  • 如果程式要求在容器的中間插入或刪除元素,應使用 list 或 forward-list.
  • 如果程式需要在頭尾位置插入或刪除元素,但不會在中間位置進行插入或刪除操作,則使用deque.

如果程式只有在讀取輸入時才需要在容器中間位置插入元素,隨後需要隨機訪問元素,則:

  • 首先,確定是否真的需要在容器中間位置新增元素。當處理輸入資料時,通常可以很容易地向vector追加資料,然後再呼叫標準庫的sort函式(我們將在10.2.3節介紹sort (第343頁))來重排容器中的元素,從而避免在中間位置新增元素。
  • 如果必須在中間位置插入元素,考慮在輸入階段使用list,一旦輸入完成,將 list中的內容拷貝到一個vector中。

如果程式既需要隨機訪問元素,又需要在容器中間位置插入元素,那該怎麼辦?

答案取決於在list或forwardlist中訪問元素與vector或deque中插入/刪除元素的相對效能。一般來說,應用中占主導地位的操作(執行的訪問操作更多還是插入/刪除更多)決定了容器型別的選擇。在此情況下,對兩種容器分別測試應用的效能可能就是必要的了。

注意 : 如果你不確定應該使用哪種容器,那麼可以在程式中只使用vector和list公共的操作:使用迭代器,不使用下標操作,避免隨機訪問。這樣,在必要時選擇使用vector或list都很方便。

 


容器操作


 


迭代器範圍


容器迭代器支援的所有操作

● 注意:forward_list  不支援遞減運算子。 

迭代器支援的算術運算

● 注意:只有“string、 vector 、deque 、array、”這些順序容器型別能夠使用迭代器支援的算術運算, 我們不能將它們用於其他任何容器型別的迭代器。


● 一個迭代器範圍由一對迭代器表示, 兩個迭代器分別指向同一個容器中的元素或者是尾元素之後的位置。 通常被稱為 begin 和 end 或者是 first 和 last (指的是最後一個元素的下一個位置), 它們標記了容器中元素的一個範圍。

● 注意: begin 和 end 必須指向相同的容器。 end 可以與begin 指向相同的位置, 但不能指向begin 之前的位置

這裡寫圖片描述

 


迭代器的特性


 假定begin和end構成一個合法的迭代器範圍,則:

  • 如果begin與end相等,則範圍為空
  • 如果begin與end不等,則範圍至少包含一個元素, 且begin指向該範圍中的第一個元素
  • 我們可以對begin遞增若干次,使得begin=end
#include<vector>
using std::vector;
int main()
{
	int val = 20;
	vector<int> vec{ 0,1,2,3,4,5 };
	vector<int>::iterator iter1 = vec.begin();
	vector<int>::iterator iter2 = vec.end();
	while (iter1 != iter2)
	{
		*iter1 = val;
		++iter1;
	}
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

 


迭代器的型別


●  迭代器的型別 有:  size_type iterator 和 const_iterator。

大多數容器還提供反向迭代器。簡單地說, 反向迭代器就是一種反向遍歷容器的迭代器,與正向迭代器相比,各種操作的含義也都發生了顛倒。例如,對一個反向迭代器執行++操作,會得到上一個元素。

 


類型別名


● 通過類型別名, 我們可以在不瞭解容器中元素型別的情況下使用它。 如果需要元素型別, 可以使用容器的value_type。 如果需要元素型別的一個引用,可以使用reference 或 const_reference 。為了使用這些型別, 我們必須顯式使用類名。

 


begin 和 end 成員


 

	vector<string> vec{ "huang","cheng","tao" };
	auto it1 = vec.begin();  //普通迭代器
	auto it2 = vec.rbegin(); //反向迭代器
	auto  it3 = vec.cbegin();  //const 迭代器
	auto it4 = vec.crbegin(); //反向const 迭代器

● 注意: 不以c 開頭的都是過載過的函式,當我們對一個非常量物件呼叫這些成員時, 得到的是返回 iterator的版本。只有在對一個const物件呼叫這些函式時, 才會得到一個const版本。與const指標和引用類似,可以將一個普通的iterator轉換為對應的const-iterator, 但反之不行。

        vector<string> vec{ "huang","cheng","tao" };
	vector<string>::iterator it5 = vec.begin();
	vector<string>::const_iterator it6 = vec.begin();

	// 是iterator還是const_iterator 依賴於 vec 的型別
	auto it7 = vec.begin(); //僅當 vec是const時, it7是 const_iterator
	auto it6 = vec.cbegin(); //it8是 const_iterator

● 當 auto 與 begin 或 end 結合使用時, 使用 begin 時 依賴於容器型別, 如果容器不是const的, 那麼返回的是 iterator , 如果是const 型別, 那麼返回的就是 const_iterator,  使用 cbegin 不管容器型別是const的 還是 非const的,  都返回 const_iterator .


容器定義和初始化


●  每個容器型別都定義了一個預設建構函式,除array之外,其他容器的預設建構函式都會建立一個指定型別的空容器,且都可以接受指定容器大小和元素初始值的引數。
 

 


將一個容器初始化為另一個容器的拷貝


將一個新容器建立為另一個容器的拷貝的方法有兩種:

  •    可以直接拷貝整個容器  //兩個容器型別和元素型別必須匹配
  •   或者(除array外)拷貝由一個迭代器對指定的元素範圍進行拷貝。    // 不要求兩個容器型別和元素型別必須匹配,只要能將要拷貝的元素轉換為要初始化的容器的元素型別即可。
int main()
{
	list<string> authors = { "huang","cheng","tao" };
	vector<const char *>articles = { "a","an","the" };

	list<string> list2(authors); // 正確,容器型別、元素型別都匹配
	//deque<string> authList(authors); //錯誤,容器型別不匹配
	//list<int> words(authors);  //元素型別不匹配,錯誤

	forward_list<string> ft(articles.begin(), articles.end()); //正確:可以將const char*元素轉換為string

	auto it1 = authors.cbegin();
	auto it2 = authors.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	cout << endl;

	auto it3 = ft.cbegin();
	auto it4 = ft.cend();
	while (it3 != it4)
	{
		cout << *it3++ << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}


輸出結果為:

huang cheng tao
a an the

● 接受兩個迭代器引數的建構函式用這兩個迭代器表示我們想要拷貝的一個元素範圍 : 新容器的大小與範圍中元素的數目相同。 新容器中的每個元素都用範圍中對應元素的值進行初始化 .

● 由於兩個迭代器表示一個範圍, 因此可以使用這種建構函式來拷貝一個容器中的子序列:

int main()
{
	vector<string> authors = { "Milton","Shakespeare","Austen","huang","chengt"};
	auto it = authors.begin() + 2;// 現在 it 指向第三個位置
	deque<string> authVector(authors.begin(), it); //拷貝元素,直到但不包括it指向的元素

	auto it1 = authVector.cbegin();
	auto it2 = authVector.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

輸出結果為:

Milton Shakespeare

 


列表初始化 (C++11)


● 在C++11新標準中允許我們對一個容器進行列表初始化, 除了array之外的容器型別,初始化列表隱含地指定容器的大小: 容器將包含與初始值一樣多的元素個數。

vector<string> authors = { "Milton","Shakespeare","Austen","huang","chengt"}; //列表初始化

當這樣做時,我們就顯式地指定了容器中每個元素的值。對於除array之外的容器型別, 初始化列表還隱含地指定了容器的大小:容器將包含與初始值一樣多的元素。

 


與順序容器相關的建構函式


● 除了與關聯容器相同的建構函式外, 順序容器(array除外)  還提供另一個建構函式,  它接受一個 容器大小和一個(可選的)元素初始值。如果我們不提供元素初始值, 則標準庫會值初始化每個元素的值:

vector<int> ivec (10, -1);
list<string> svec(10, "hi!");
forward list<int> ivec (10);
deque<string> svec (10);

● 如果元素型別是內建型別或者是具有預設建構函式的類型別,可以只為建構函式提供一個容器大小引數。

如果元素型別沒有預設建構函式,除了大小引數外, 還必須指定一個顯式的元素初始值

● 注意: 只有順序容器的建構函式才接受大小引數,關聯容器並不支援。

 


標準庫 array 具有固定大小


● 與內建陣列一樣,標準庫array的大小也是型別的一部分。 當定義一個array時,必須指定元素型別和容器大小。

array<int, 10> arr; //指定一個標準庫 array, 型別為int, 陣列個數為10

● 由於大小是array 型別的一部分,array 不支援普通的容器建構函式。

 

● 與其他容器不同的是, 一個預設構造的array 是非空的: 它包含了與其大小一樣多的元素。 這些元素都被預設初始化。如果我們對array 進行列表初始化, 初始值的數目必須等於或小於array的大小。如果初始值數目小於array的大小,則它們被用來初始化array 靠前的元素,所有剩餘元素都會進行值初始化。  在這兩種情況下,如果元素型別是一個類型別, 那麼該類必須有一個預設建構函式, 以使值初始化能夠進行。

 

● 注意: 雖然我們不能對內建陣列進行拷貝或者物件賦值操作,但array並無此限制

	int digs[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int cpy[10] = digs; //錯誤
	array<int, 10>digits = { 0,1,2,3,4,5,6,7,8,9 };
	array<int, 10> copy = digits; //正確

與其他容器一樣, array也要求初始值的型別必須與要建立的容器型別相同。此外, array還要求元素型別和大小也都一樣,因為大小是array型別的一部分。

 


賦值與swap


這裡寫圖片描述
賦值相關的運算子可以用於所有容器

 

● 注意:  由於右邊運算物件的大小可能與左邊運算物件的大小不同, 因此array型別不支援assign。

 

 


使用 assign(關聯容器和array除外、只有順序容器可以使用)


● assign 成員: 允許我們從一個不同但相容的型別賦值,或者從容器的一個子序列賦值。assign 操作用引數所指定的元素替換(拷貝)左邊容器中的所有元素。 assign的引數決定了容器中將有多少個元素以及它們的值都是什麼。

int main()
{
	
	list<string> authors = { "huang","cheng","tao" };
	vector<const char *>articles = { "a","an","the" };

	list<string> list2(authors); // 正確,容器型別、元素型別都匹配
	authors.assign(articles.cbegin(), articles.cend()); //正確:可以將const char*元素轉換為string

	auto it1 = articles.cbegin();
	auto it2 = articles.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	system("pause");
	return 0;
}

輸出結果為:

a an the 

● 注意 : 由於舊元素被替換,因此傳遞給assign 的迭代器不能指向呼叫assign的容器
 


使用 swap


● 注意: 除array 外,交換兩個容器中的內容的操作保證會很快——元素本身並未交換,swap只是交換了兩個容器的內部資料結構。

● 注意: 除array外, swap 不對任何元素進行拷貝、刪除或插入操作,因此可以保證在常數時間內完成。

● 元素不會被移動的事實意味著, 使用 swap, 除了array外,指向容器的迭代器、引用和指標在swap之後,都不會失效。 它們仍指向swap 操作之前所指向的那些元素。但是,在swap之後,這些元素已經屬於不同的容器了。

 

注意: 與其它容器不同的是,對一個string 呼叫swap 會導致迭代器、引用和指標失效。

● 注意: 與其它容器不同, swap 兩個array會真正交換它們的元素。 交換兩個array所需的時間與array 中元素的數目成正比。

對於array,在swap操作之後 ,指標、引用和迭代器所繫結的元素保持不變, 但元素值已經與另一個array中對應元素的值進行了交換。

 

● 每個容器型別都有三個與大小相關的操作: size、empty、max_size。

注意: forward_list 支援 empty、max_size,但不支援size。

 


關係運算符


● 每個容器型別都支援相等運算子( == 和 != ); 除了無序關聯容器外的所有容器都支援關係運算符——關係運算符左右兩邊的運算物件必須是容器型別相同和元素型別相同。

● 比較兩個容器實際上是進行元素的逐對比較。


容器的關係運算符使用元素的關係運算符完成比較


● 注意: 只有當其元素型別也定義了相應的比較運算子時,我們才可以使用關係運算符比較兩個容器。

● 容器的相等運算子是使用元素的“ == ”運算子實現比較的, 而其他關係運算符是使用元素的“ < ” 運算子實現比較的。

如果元素型別不支援所需運算子, 那麼儲存這種元素的容器就不能使用相應的關係運算符。