1. 程式人生 > >C++標準模板庫(STL):vector、deque和list

C++標準模板庫(STL):vector、deque和list

之所以把這幾個容器寫在一起,是因為他們都是序列式容器。

序列式容器以線性序列的方式儲存元素(線性結構)。它沒有對元素進行排序,元素的順序和儲存它們的順序相同。以下有幾種標準的序列容器,每種容器都具有不同的特性:

  • vector<T>(向量容器)是一個長度可變的序列,用來存放 T 型別的物件。必要時,可以自動增加容量,但只能在序列的末尾高效地增加或刪除元素。
  • deque<T>(雙向佇列容器)是一個長度可變的、可以自動增長的序列,在序列的兩端都不能高效地增加或刪除元素。 
  • list<T>(連結串列容器)是一個長度可變的、由 T 型別物件組成的序列,它以雙向連結串列的形式組織元素,在這個序列的任何地方都可以高效地增加或刪除元素。訪問容器中任意元素的速度要比前三種容器慢,這是因為 list<T> 必須從第一個元素或最後一個元素開始訪問,需要沿著連結串列移動,直到到達想要的元素。

vector

       vector<T> 容器可以方便、靈活地代替陣列。在大多數時候,都可以用 vector<T> 代替陣列存放元素。vector<T> 在擴充套件容量,以及在序列內部刪除或新增元素時會產生一些開銷,效率低下;但大多數情況下,不會明顯變慢。 為了使用 vector<T> 容器模板,需要在程式碼中包含標頭檔案 vector。 vector<T> 容器的大小可以自動增長,從而可以包含任意數量的元素。只要元素個數超出 vector 當前容量,就會自動分配更多的空間。只能在容器尾部高效地刪除或新增元素(在中間新增和刪除並不高效)。

  • 建立vector物件

      vector<元素型別>物件名([容器大小],[元素初始值】);

   vector<int> v1;  //建立一個空的vector容器 ,物件名後面不能有括號
   vector<string> v2(10, "abc"); //建立一個大小為10的容器物件,並把這十個元素全部賦值abc
   vector<string> v3(v2); //用v2來初始化v3
   vector<int> v4{ 1,2,3,6 };
  • 獲取容器容量和大小

         分配大小(如果通過呼叫 reserve() 來增加記憶體,任何現有的迭代器,例如開始迭代器和結束迭代器,都會失效,所以需要重新生成它們。這是因為,為了增加容器的容量,vector<T> 容器的元素可能已經被複制或移到了新的記憶體地址。一般不用自己擴容,因為這是自動的。

v1.reserve(20);

         獲取容器容量和大小(一般容量比大小大

容器容量:int capacity=v1.capacity();

容器大小:int size=v.size();

  • 賦值函式

可以用成員函式assign()來給容器賦值。

//將5個3賦值給v1
v1.assign(5, 3);
//將[begin,end)區間中的元素賦值給容器,即[a,a+5)不包括a+5
int a[5] = { 1,2,3,5,6 };
v4.assign(a, a + 5);
  • 訪問元素
//兩種訪問方式 
cout<<v1[1];
cout << v1.at(1);
  • 對首元素和末元素的操作
v4.push_back(2); //在容器末尾新增元素2
v4.pop_back();   //刪除容器最後一個元素,但並不會返回這個元素值
v4.front()=2;      //獲取容器第一個元素的引用,可對第一個元素進行操作
v4.back()=2;       //獲取容器最後一個元素的引用,可對最後一個元素進行操作
v4.begin();        //獲得指向首元素的迭代器(可理解為指標)
v4.end();          //獲得指向末元素後一個元素的迭代器(其實這個元素並不存在)
  • 插入和刪除(中間元素)

        insert()和erase()函式的pos位置只能用迭代器begin()和end()來指示,不能用數字作為引數。

	v4.insert(pos, elem);   //在pos位置上插入元素elem,返回該資料的位置(指向該位置的迭代器)
	v4.insert(pos, n, elem);//在pos位置上插入n個elem元素,無返回值
	v4.insert(pos, begin, end);//在pos位置上插入[begin,end)區間的資料,無返回值
	v4.erase(pos);  //移除pos位置上的元素,返回下一個元素的迭代器
	v4.erase(begin,end)  //移除[begin,end)區間的資料,返回下一個元素的迭代器

deque(雙端佇列)

      deque<T>,一個定義在 deque 標頭檔案中的容器模板,可以生成包含 T 型別元素的容器,它以雙端佇列的形式組織元素。可以在容器的頭部和尾部高效地新增或刪除物件,這是它相對於 vector 容器的優勢。當需要這種功能時,可以選擇這種型別的容器。無論何時,當應用包含先入先出的事務處理時,都應該使用 deque 容器。處理資料庫事務或模擬一家超市的結賬佇列,像這兩種應用都可以充分利用 deque 容器。

deque的建立物件、賦值、訪問和對首末元素的操作和vector一模一樣(front()函式依舊是返回引用)。

對於插入刪除也和vector一樣,但是deque增加了以下函式。

	v1.push_front(1); //在雙向佇列最前面新增元素
	v1.pop_back();  //刪除最後一個元素
	v1.pop_front();   //刪除第一個元素

list(雙向連結串列)

     list<T> 容器模板定義在 list 標頭檔案中,是 T 型別物件的雙向連結串列。      list 容器具有一些 vector 和 deque 容器所不具備的優勢,它可以在常規時間內,在序列已知的任何位置插入或刪除元素。這是我們使用 list,而不使用 vector 或 deque 容器的主要原因。       list 的缺點是無法通過位置來直接訪問序列中的元素,也就是說,不能索引元素。為了訪問 list 內部的一個元素,必須一個一個地遍歷元素,通常從第一個元素或最後一個元素開始遍歷。

       list<T> 容器的每個 T 物件通常都被包裝在一個內部節點物件中,節點物件維護了兩個指標,一個指向前一個節點,另一個指向下一個節點。這些指標將節點連線成一個連結串列。通過指標可以從任何位置的任一方向來遍歷連結串列中的元素。第一個元素的前向指標總是為 null,因為它前面沒有元素,尾部元素的後向指標也總為 null。這使我們可以檢測到連結串列的尾部。list<T> 例項儲存了頭部和尾部的指標。這允許我們從兩端訪問連結串列,也允許從任一端開始按順序檢索列表中的元素。       可以用和其他序列容器相同的方式,來獲取 list 容器的迭代器。因為不能隨機訪問 list 中的元素,獲取到的迭代器都是雙向迭代器。以 list 為引數,呼叫 begin() 可以得到指向 list 中第一個元素的迭代器。通過呼叫 end(),可以得到一個指向最後一個元素下一個位置的迭代器,因此像其他序列容器一樣,可以用它們來指定整個範圍的元素。也可以像其他容器一樣,使用 rbegin()、rend()、crbegin()、crend()、cbegin()、cend() 來獲取反向迭代器和 const 迭代器。

  • 建立物件、賦值

         和vector、deque一樣。

  • 元素訪問和首尾元素的操作

一樣和deque有front()、back()、push_back()、push_front()、pop_back()、pop_front()。

        但是不能像前面兩個容器那樣直接用[]和at()訪問其中的元素。

  • 插入和刪除

       insert()和erase()和前面一樣,只不過多了一個remove()函式。

list.remove(elem);  //從容器中刪除所有與elem匹配的元素

list一些函式的應用(成員函式,不需要包含在algorithm標頭檔案中)

  • merge()合併函式

   list 的成員函式 merge() 以另一個具有相同型別元素的 list 容器作為引數。兩個容器中的元素都必須是升序。引數 list 容器中的元素會被合併到當前的 list 容器中。合併後另一個容器就為空了。

	list<int> lt1{1,2,3 };//必須為升序
	list<int> lt2{ 8,9,10};//必須為升序
	lt1.merge(lt2);
	for (auto lt = lt1.begin(); lt != lt1.end(); lt++)
	{
		cout << *lt<<"  ";
	}//最後輸出的也是升序的
  • sort()排序函式

   sort() 函式模板定義在標頭檔案 algorithm 中,要求使用隨機訪問迭代器。但 list 容器並不提供隨機訪問迭代器(沒有lt.begin()+2這樣的操作)只提供雙向迭代器,因此不能對 list 中的元素使用 sort() 演算法。但是,還是可以進行元素排序,因為 list 模板定義了自己的 sort() 函式。

   lt.sort();   //從小到大排序
  • splice()合併函式

  上面提到的merge()函式雖可以合併,但是合併後進行了排序。有的時候不希望改變原有元素的位置,只想首尾相接。

	list<int> lt1{1,5,6 };  //不一定要是升序
	list<int> lt2{ 8,3,10};
	lt1.splice(lt1.end(), lt2);   //將lt2插入到lt1後
	lt1.splice(lt1.begin(), lt2); //將lt2插入到lt1前