STL原始碼剖析——序列式容器
阿新 • • 發佈:2018-11-01
序列式容器
所謂序列化容器,其中的元素都可序,但未必有序。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,則配置
;如果原大小不為0,則配置原大小的兩倍(
)。
vector的迭代器
- vector維護的是一個連續線性空間,普通指標即可滿足需求,提供隨機存取迭代器。
- 因vector滿載需進行“配置新空間/資料移動/釋還舊空間”操作,因而vector的迭代器會失效。
vector空間釋放
- clear()函式:清除所有元素,但容量不改變。即
。
- 下面演示程式可知:當push_back和pop_back時size的大小(元素個數)改變,而capacity的大小隻有在分配新空間時改變,且 ,只增不減。(vector容量只增不減。)
- 因vector容量只增不減,所以對於釋放vector佔有的記憶體有必要了解一下。
- 方法一:與臨時變數swap。
- 方法二:clear()+shink_to_fit()。先清除所有的元素,再將容量大小調整為元素大小即可。
- clear()函式:清除所有元素,但容量不改變。即
。
#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))。若插入位置之前的元素個數較少,則將之前的元素前移,反之將之後的元素後移。刪除操作類似插入操作。