1. 程式人生 > >STL原始碼剖析——序列式容器

STL原始碼剖析——序列式容器

序列式容器

  • 所謂序列化容器,其中的元素都可序,但未必有序。C++語言本省提供了一個序列式容器array,STL另外提供了vector,list,deque,stack,queue,priority_queue等序列式容器。其中stack和queue由於只是將deque作為底層實現,技術上被歸類為一種配接器

  • 文章只是將一些細節知識點總結,底層原始碼不涉及。

vector

  • vector的資料結構

    • 陣列。
  • vector和array的區別

    • array是靜態空間,一旦配置了就不能改變;要存入大於空間個數的元素,需要客戶端自己實現。
    • vector是動態空間,隨著元素的加入,它的內部機制會自行擴充以容納新元素。
    • 兩者均為線性連續空間。
#include<iostream>
#include<vector>

int main(int argc, char* argv[]) {
    //*************array************
    int a[5] = { 0,1,2,3,4 };
    //換大空間
    int b[10] = { 0 };
    for (int i = 0; i < 5;++i) {
        b[i] = a[i];
    }
    b[5
] = 10; //***************vector**************** vector<int> vec{1,2,3}; cout << vec.size() << endl; //3,size表示元素個數 cout << vec.capacity() << endl; //3,capacity表示佔用的空間,capacity>=size vec.push_back(4); vec.push_back(5); cout << vec.size() << endl; //5
cout << vec.capacity() << endl; //6,空間自行擴充 getchar(); return 0; }
  • vector空間滿載情況

    • 為了降低空間配置時的速度成本,vector實際配置的大小可能比客戶端需求量更大一些,以備將來可能的擴充。這便是容器的概念。也就是說,一個vector的容量永遠大於等於其大小,一旦等於,便需擴充
    • vector空間滿載,將會進行“配置新空間/資料移動/釋還舊空間”,時間成本很高。因此,vector在配置新空間時,一般以舊空間的兩倍來擴充。注意,這裡擴充指的是容量capacity,size表示加入元素的個數。
    • 空間配置原則:如果原大小為0,則配置 c a p a c i t y = 1 ;如果原大小不為0,則配置原大小的兩倍( c a p a c i t y = 2 c a p a c i t y )。
      這裡寫圖片描述
  • vector的迭代器

    • vector維護的是一個連續線性空間,普通指標即可滿足需求,提供隨機存取迭代器。
    • 因vector滿載需進行“配置新空間/資料移動/釋還舊空間”操作,因而vector的迭代器會失效
      這裡寫圖片描述
  • vector空間釋放

    • clear()函式:清除所有元素,但容量不改變。即 s i z e = 0 , c a p a c i t y ! = 0
      這裡寫圖片描述
    • 下面演示程式可知:當push_back和pop_back時size的大小(元素個數)改變,而capacity的大小隻有在分配新空間時改變,且 c a p a c i t y = 2 c a p a c i t y ,只增不減。(vector容量只增不減。
    • 因vector容量只增不減,所以對於釋放vector佔有的記憶體有必要了解一下。
      • 方法一:與臨時變數swap。
      • 方法二:clear()+shink_to_fit()。先清除所有的元素,再將容量大小調整為元素大小即可。
#include<iostream>
#include<vector>

using namespace std;

int main(int argc, char* argv[]) {
    vector<int> vec{1,2,3,4};
    cout << vec.size() << endl; //4,size表示元素個數
    cout << vec.capacity() << endl; //4,capacity表示佔用的空間,capacity>=size
    vec.push_back(5);
    cout << vec.size() << endl; //5,size表示元素個數
    cout << vec.capacity() << endl; //8,空間自行擴充
    vec.push_back(6);
    vec.push_back(7);
    cout << vec.size() << endl; //7
    cout << vec.capacity() << endl;  //8
    vec.push_back(8);
    vec.push_back(9);
    cout << vec.size() << endl; //9
    cout << vec.capacity() << endl;  //16,空間自行擴充
    //************************方法一************************
    /*
    vector<int>().swap(vec);
    cout << vec.size() << endl; //0
    cout << vec.capacity() << endl;  //0
    */
    //************************方法二************************
    vec.clear();
    vec.shrink_to_fit();
    cout << vec.size() << endl; //0
    cout << vec.capacity() << endl;  //0
    getchar();
    return 0;
}
  • vector優缺點

    • 優點:隨機存取。時間複雜度O(1)。
    • 缺點:插入和刪除的時間複雜度O(N)。需要對元素進行移動。

list

  • list的資料結構

    • list是一個環狀雙向連結串列。所以它只需要一個指標,就可以完整的表現整個連結串列。
    • 只要刻意在環狀連結串列的尾端加上一個空白節點,便可滿足“前閉後開”區間。
      這裡寫圖片描述
  • list的空間分配

    • list不像vector是連續儲存空間,需要預留空間來節省“配置新空間/資料移動/釋還舊空間”操作的成本。因而容量大小等於元素個數。
    • 每次配置一個節點大小,當元素刪除時,相應空間一併刪除
  • list的迭代器

    • list不能像vector一樣以普通指標作為迭代器,因為其節點不保證在儲存空間中連續存在。
    • list是一個環狀雙向連結串列,迭代器必須具備前移、後移的能力,所以list提供的是雙向迭代器。
    • 插入操作和接合操作都不會造成原有的list迭代器失效,只有在刪除元素時,“指向被刪除元素”的那個迭代器失效,其他迭代器不受影響
  • 注意

    • 插入操作:分配一個節點,“插入在…之前”。
      這裡寫圖片描述
    • 移除操作:刪除元素,並釋放空間。
      這裡寫圖片描述
    • 遷移操作
      • 將某連續範圍的元素遷移到特定位置之前。只進行節點間的指標移動即可。
      • list中的splice(接合操作)、merge(合併操作)、reverse(反轉操作)、sort(排序操作)均是以遷移操作為基礎。
      • 圖解
        這裡寫圖片描述
  • list優缺點

    • 優點:無預留空間。插入和刪除在常數時間內完成。
    • 缺點:不能進行隨機存取。時間複雜度為O(N)。

deque

  • vector和deque的區別

    • vector是單向開口的連續線性空間,deque是雙向開口的連續線性空間。
    • deque允許常數時間內對起頭端進行元素的插入或移除操作。vector需要將起頭端之後的元素進行整體的前移或後移操作,時間複雜度為O(N),效率極差。
    • deque沒有所謂的容量概念,它是動態地以分段連續空間組合而成,隨時可以增加一段新的空間並連結起來(隨後會介紹)。
  • deque的中控器

    • deque採用一塊所謂的map(不是map容器)作為主控。這裡所謂的map是一小塊連續空間,其中每個元素都是指標,指向另一段(較大的)連續線性空間,稱為緩衝區。緩衝區才是deque的儲存空間主體。
    • deque的連續空間其實是假象,是由一段一段的定量連續空間構成。
      這裡寫圖片描述
  • deque的迭代器

    • deque的迭代器包含四個指標

      • cur:指向緩衝區中的現行元素
      • first:指向緩衝區的頭
      • last:指向緩衝區的尾
      • node:指向管控中心
    • 當map所指的緩衝區容量滿載時,需要對map進行擴充,擴充過程:配置更大的,拷貝原來的,釋放原來的。因此deque的迭代器會失效

    • deque的中控器、緩衝區、迭代器的相互關係
      這裡寫圖片描述
  • deque在尾端新增元素,引發新緩衝區的配置(條件:map中控器未滿載,不需要考慮map)。
    這裡寫圖片描述

  • deque在最前端新增元素,引發新緩衝區的配置(條件:map中控器未滿載,不需要考慮map)。
    這裡寫圖片描述

  • map滿載時,擴充map(配置更大的,拷貝原來的,釋放原來的)。

  • deque優缺點

    • 可隨機存取。時間複雜度為O(1)。可在頭部和尾部進行插入操作,相對於vector的頭插操作效率較高。
    • 插入和刪除操作時間複雜度為O(N)(頭插和尾插時間複雜度為O(1),頭刪和尾刪時間複雜度為O(1))。若插入位置之前的元素個數較少,則將之前的元素前移,反之將之後的元素後移。刪除操作類似插入操作。