C++三種容器:list、vector和deque的區別

塵虛緣_KY發表於2016-05-06

           在寫C++程式的時候會發現STL是一個不錯的東西,減少了程式碼量,使程式碼的複用率大大提高,減輕了程式猿的負擔。還有一個就是容器,你會發現要是自己寫一個連結串列、佇列,或者是陣列的時候,既要花時間還要操心怎麼去維護,裡面的指標啊,記憶體夠不夠用啊,長度問題,有沒有可能溢位啊等等一系列的問題等著我們去解決,還是比較頭疼的。所以容器的出現解決了這一個問題,它將這些資料結構都封裝成了一個類,只需要加上標頭檔案,我們就可以輕鬆的應用,不用那麼複雜,就連指標也被封裝成了迭代器,用起來更方便,更人性化,方便了我們的程式設計,對於程式設計師來說還是一大福音!!

          C++中的容器類包括“順序儲存結構”和“關聯儲存結構”,前者包括vector,list,deque等;後者包括set,map,multiset,multimap等。若需要儲存的元素數在編譯器間就可以確定,可以使用陣列來儲存,否則,就需要用到容器類了。

1、vector
    連續儲存結構,每個元素在記憶體上是連續的;支援高效的隨機訪問和在尾端插入/刪除操作,但其他位置的插入/刪除操作效率低下;相當於一個陣列,但是與陣列的區別為:記憶體空間的擴充套件。vector支援不指定vector大小的儲存,但是陣列的擴充套件需要程式設計師自己寫。
    vector的記憶體分配實現原理:
    STL內部實現時,首先分配一個非常大的記憶體空間預備進行儲存,即capacity()函式返回的大小,當超過此分配的空間時再整體重新放分配一塊記憶體儲存(VS6.0是兩倍,VS2005是1.5倍),所以這給人以vector可以不指定vector即一個連續記憶體的大小的感覺。通常此預設的記憶體分配能完成大部分情況下的儲存。
    擴充空間(不論多大)都應該這樣做:
   (1)配置一塊新空間
   (2)將舊元素一一搬往新址
   (3)把原來的空間釋放還給系統
    注:vector 的資料安排以及操作方式,與array 非常相似。兩者的唯一差別在於空間的利用的靈活性。Array 的擴充空間要程式設計師自己來寫。
    vector類定義了好幾種建構函式,用來定義和初始化vector物件:
    vector<T>  v1;  vector儲存型別為T的物件。預設建構函式v1為空。
    vector<T> v2(v1);  v2是v1的一個副本。
    vector<T> v3(n, i);  v3包含n個值為i的元素。
    vector<T> v4(n);   v4含有值初始化的元素的n個副本。
2、deque
    連續儲存結構,即其每個元素在記憶體上也是連續的,類似於vector,不同之處在於,deque提供了兩級陣列結構, 第一級完全類似於vector,代表實際容器;另一級維護容器的首位地址。這樣,deque除了具有vector的所有功能外,還支援高效的首/尾端插入/刪除操作。
    deque   雙端佇列 double-end queue
    deque是在功能上合併了vector和list。
    優點:(1) 隨機訪問方便,即支援[ ]操作符和vector.at()
                (2) 在內部方便的進行插入和刪除操作
                (3) 可在兩端進行push、pop
    缺點:佔用記憶體多
   使用區別:
     (1)如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector
     (2)如果你需要大量的插入和刪除,而不關心隨機存取,則應使用list
     (3)如果你需要隨機存取,而且關心兩端資料的插入和刪除,則應使用deque
3、list
    非連續儲存結構,具有雙連結串列結構,每個元素維護一對前向和後向指標,因此支援前向/後向遍歷。支援高效的隨機插入/刪除操作,但隨機訪問效率低下,且由於需要額外維護指標,開銷也比較大。每一個結點都包括一個資訊快Info、一個前驅指標Pre、一個後驅指標Post。可以不分配必須的記憶體大小方便的進行新增和刪除操作。使用的是非連續的記憶體空間進行儲存。
   優點:(1) 不使用連續記憶體完成動態操作。
               (2) 在內部方便的進行插入和刪除操作
               (3) 可在兩端進行push、pop
   缺點:(1) 不能進行內部的隨機訪問,即不支援[ ]操作符和vector.at()
               (2) 相對於verctor佔用記憶體多
   使用區別:
             (1)如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector
             (2)如果你需要大量的插入和刪除,而不關心隨機存取,則應使用list
             (3)如果你需要隨機存取,而且關心兩端資料的插入和刪除,則應使用deque
4、vector VS. list VS. deque:
    a、若需要隨機訪問操作,則選擇vector;
    b、若已經知道需要儲存元素的數目,則選擇vector;
    c、若需要隨機插入/刪除(不僅僅在兩端),則選擇list
    d、只有需要在首端進行插入/刪除操作的時候,還要兼顧隨機訪問效率,才選擇deque,否則都選擇vector。
    e、若既需要隨機插入/刪除,又需要隨機訪問,則需要在vector與list間做個折中-deque。
    f、當要儲存的是大型負責類物件時,list要優於vector;當然這時候也可以用vector來儲存指向物件的指標,
       同樣會取得較高的效率,但是指標的維護非常容易出錯,因此不推薦使用。

問題一:list和vector的區別:
(1)vector為儲存的物件分配一塊連續的地址空間,隨機訪問效率很高。但是插入和刪除需要移動大量的資料,效率較低。尤其當vector中儲存
的物件較大,或者建構函式複雜,則在對現有的元素進行拷貝的時候會執行拷貝建構函式。
(2)list中的物件是離散的,隨機訪問需要遍歷整個連結串列,訪問效率比vector低。但是在list中插入元素,尤其在首尾插入,效率很高,只需要改變元素的指標。

(3)vector是單向的,而list是雙向的;

(4)向量中的iterator在使用後就釋放了,但是連結串列list不同,它的迭代器在使用後還可以繼續用;連結串列特有的;

  使用原則:
(1)如果需要高效的隨機存取,而不在乎插入和刪除的效率,使用vector;
(2)如果需要大量高效的刪除插入,而不在乎存取時間,則使用list;
(3)如果需要搞笑的隨機存取,還要大量的首尾的插入刪除則建議使用deque,它是list和vector的折中;
問題二:常量容器const
     const vector<int> vec(10);//這個容器裡capacity和size和值都是不能改變的,const修飾的是vector
     迭代器:const vector<int>::const_iterrator ite; //常量迭代器;
      注:const vector <int> vec(10) —— 與const int a[10]是一回事,意思是vec只有10個元素,不能增加了,裡面的元素也是不能變化的
vector<int> a(10);
const vector<int> b(10);
a[1]=10;//正確
b[1]=10;//錯誤
a.resize(20);//正確
b.resize(20);//錯誤
vector <const int> vec(10);  //目前沒有這種用法;這樣寫後也是當作vector <int>vec來用的;
關於vector<const int> ,在GCC下是沒有這種用法的,編譯不過
在VS2008是把它當作vector<int>這種型別來處理的;
問題三:capacity V.S size
    a、capacity是容器需要增長之前,能夠盛的元素總數;只有連續儲存的容器才有capacity的概念(例如vector,deque,string),list不需要capacity。
    b、size是容器當前儲存的元素的數目。
    c、vector預設的容量初始值,以及增長規則是依賴於編譯器的。
問題四:用vector儲存自定義類物件時,自定義類物件須滿足:
    a、有可供呼叫的無參建構函式(預設的或自定義的);

    b、有可用的拷貝賦值函式(預設的或自定義的)

問題五:迭代器iterator   

     a、vector與deque的迭代器支援算術運算,list的迭代器只能進行++/--操作,不支援普通的算術運算。

     b .向量中的iterator在使用後就釋放了,但是連結串列list不同,它的迭代器在使用後還可以繼續用;連結串列特有的;

     ite=find(vec.begin(),vec.end(),88);
     vec.insert(ite,2,77);  //迭代器標記的位置前,插入資料;
     cout<<*ite<<endl;  //會崩潰,因為迭代器在使用後就釋放了,*ite的時候就找不到它的地址了;


vector程式碼例項:

#include <iostream>
using namespace std;
#include <vector>   //向量的標頭檔案;
#include <algorithm> //演算法的標頭檔案;
int main()
{
	vector <int> vec(5,8);
	//--型別是vector<int>,該容器向量中含有5個int型別的數值8,變數名為vec。
	//vector是一個類别範本(class template),所以必須要宣告其型別,int,一個容器中所有的物件必須是同一種型別;
	// 定義一個容器物件;直接構造出一個陣列;用法和陣列一樣;
	// 	
	 	for(int i=0;i<vec.size();i++)   //size()是指容器裡當前有多少個使用的元素;
	 	{
	 		cout<<vec[i]<<"  ";
	 	}	
		cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  //得到容器裡用的多少個空間,和總共的大小;
	 vector<int>::iterator ite;  //定義了一個向量的迭代器;相當於定義了一個指標;
	for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束
	{
		cout<<*ite <<" ";  //迭代器返回的是引用:
	}
        cout<<endl;
	//在尾部插入;
	vec.push_back(9);  //VS6.0擴充的空間是兩倍;在VS2005擴充的空間是1.5倍;
	for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束
	{
		cout<<*ite <<" ";
	}
	cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;

	//尾部刪除;容量沒變【capacitty】,但是使用空間減少一個;容量一旦增加就不會減小;
	vec.pop_back();
	for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束
	{
		cout<<*ite <<" ";
	}
	cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;

	vec.push_back(88);  
	vec.push_back(99); //容量剛好夠;

	for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束
	{
		cout<<*ite <<" ";
	}
	cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;

	ite = find(vec.begin(),vec.end(),88);   //查詢這個元素;
	vec.erase(ite);  //利用迭代器指標刪除這個元素;
	for(int i=0;i<vec.size();i++)   //size()是指容器裡當前有多少個使用的元素;
	{
		cout<<vec[i]<<" ";
	}
	cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  //得到容器裡用的多少個空間,和總共的大小;

	vec.clear(); //只是清除了資料,沒有回收空間,空間的等到物件的生命週期結束時回收;
	//使用空間為0,但是容量的空間還在,只有在呼叫解構函式的時候空間才會回收;

	for(int i=0;i<vec.size();i++)   //size()是指容器裡當前有多少個使用的元素;
	{
		cout<<vec[i]<<"  ";
	}
	cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;

	ite=find(vec.begin(),vec.end(),88);
	vec.insert(ite,2,77);  //迭代器標記的位置前,插入資料;

	<span style="color:#ff0000;">//cout<<*ite<<endl;  //會崩潰,因為迭代器在使用後就釋放了,*ite的時候就找不到它的地址了;
	//和向量的用法一樣,但是連結串列list不同,它的迭代器在使用後還可以繼續用;連結串列特有的;</span>

	for(int i=0;i<vec.size();i++)   
	{
		cout<<vec[i]<<"  ";
	}
	cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;

	system("pause");
	return 0;
}
執行結果:

list程式碼示例:

#include<iostream>
#include <list>
#include <algorithm>
using namespace  std;
int main()
{
	list<char> lit; 
	//用法和向量一樣,
	//list是一個類别範本,template,char是連結串列裡物件的型別,lit是建立的一個物件;
	//連結串列可以再頭尾兩端插入,是雙向的;

	lit.push_back('a');
	lit.push_back('b');
	lit.push_front('d');
	lit.push_front('e');
	lit.push_front('f');
	lit.push_front('b');
	lit.push_front('b');

	list<char>::iterator it;  //定義一個list的迭代器,類似一個紙箱連結串列的指標,但是比一般的指標好用,裡面用到了好多過載操作;
	list<char>::iterator it1;  
	list<char>::iterator it2;  
	for(it=lit.begin();it!=lit.end();it++)
	{
		cout<<*it<<"  ";
	}
	cout<<endl;
	//-----------連結串列可以從兩端刪除------------------- 
	lit.pop_back();  
	lit.pop_front();
	for(it=lit.begin();it!=lit.end();it++)
	{
		cout<<*it<<"  ";
	}
	cout<<endl;
	//-------------刪除所有的a---------------------------------
	//lit.remove('a');  //刪除所有的a;

	for(it=lit.begin();it!=lit.end();it++)
	{
		cout<<*it<<"  ";
	}
	cout<<endl;
	//-------------移除連續且相同的a,只剩下一個;--------------------------------
	lit.unique();  //移除連續且相同的a,只剩下一個;

	for(it=lit.begin();it!=lit.end();it++)
	{
		cout<<*it<<"  ";
	}
	cout<<endl;
	list<char> lit1;
	lit1.push_back('g');
	lit1.push_back('h');
	lit1.push_back('i');
	lit1.push_back('k');
	for(it1=lit1.begin();it1!=lit1.end();it1++)
	{
		cout<<*it1<<"  ";
	}
	cout<<endl;
	//-------------將一個連結串列插入到另一個連結串列---------------------------------
	it1=find(lit.begin(),lit.end(),'f');  //先的找到要插入的位置,在該位置的前一個插入;
	////lit.splice(it1,lit1); //將第二個連結串列插入到第一個連結串列中;合併後的連結串列就沒了,因為傳的是&;
	for(it=lit.begin();it!=lit.end();it++)
	{
		cout<<*it<<"  ";
	}
	cout<<endl;
	//------在連結串列lit中的it前插入lit1中的一個元素it1;在f之前插入k-----
	//-----拿下來之後那個元素就沒有了-------------------
	it=find(lit.begin(),lit.end(),'f');
	it1=find(lit1.begin(),lit1.end(),'k');
	lit.splice(it,lit1,it1);
	//-------------把連結串列中的一段插入到另一個連結串列中---------------------------------
	//把連結串列lit1中的[it-----it1)段的字元插入到lit的it2指標前;
	it=find(lit1.begin(),lit1.end(),'h');
	it1=find(lit1.begin(),lit1.end(),'k');
	it2=find(lit.begin(),lit.end(),'f');
	lit.splice(it2,lit1,it,it1); 
	// ----void merge(list& x);	//將x合併到*this 身上。兩個lists 的內容都必須先經過遞增歸併排序。
	lit.sort();   //對兩個排序進行歸併排序;
	lit1.sort();
	lit.merge(lit1);
	//-----------將list裡的資料倒序排列---------------
	lit.reverse();
	for(it=lit.begin();it!=lit.end();it++)
	{
		cout<<*it<<"  ";
	}
	cout<<endl;
	for(it1=lit1.begin();it1!=lit1.end();it1++)
	{
		cout<<*it1<<"  ";
	}
	cout<<endl;
	system("pause");
	return 0;
}


執行結果:



相關文章