STL 原始碼剖析(四)序列式容器--vector
阿新 • • 發佈:2018-12-22
1. 寫在前面
之後的幾篇主要記錄一些關於序列式容器中重要的一些知識點以及疑難雜症,並不特別詳細分析。
2. vector
2.1 與array的區別
- array是靜態空間,一旦配置就不能改變,要更換時需要自己來更改:首先配置一塊新空間,將元素從舊址搬到該新空間,然後將舊址還給系統
- vector是動態空間,隨著元素的加入,內部機制會自行擴充空間以容納新元素
2.2 vector中capacity(容量)
- 1.容量:為了降低空間配置時的速度成本,vector實際配置的大小比客戶端需求的量更大一些,以備將來的擴充
- 2.在vector中的實現:
template <class T, class Alloc = alloc>
class vector {
protected:
iterator start; //表示目前使用空間的頭
iterator finish; //表示目前使用空間的尾
iterator end_of_storage; //表示目前可用空間的尾
...
public:
size_type capacity() const {
return size_type( end_of_storage - begin()); } //實際配置大小
...
};
- 3.我們可以以一個例子來理解capacity與引入記憶體管理:
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char** argv) {
int i;
vector<int> vec(2,9);
cout << "size = " << vec.size() << endl; //size = 2
cout << "capacity = " << vec.capacity() << endl; //capacity = 2
vec. push_back(5);
cout << "size = " << vec.size() << endl; //size = 3
cout << "capacity = " << vec.capacity() << endl; //capacity = 4
vec.push_back(6);
cout << "size = " << vec.size() << endl; //size = 4
cout << "capacity = " << vec.capacity() << endl; //capacity = 4
vec.push_back(7);
cout << "size = " << vec.size() << endl; //size = 5
cout << "capacity = " << vec.capacity() << endl; //capacity = 8
for (i = 0; i < vec.size(); ++i)
cout << vec[i] << ' '; //9 9 5 6 7
cout << endl;
vec.pop_back();
cout << "size = " << vec.size() << endl; //size = 4
cout << "capacity = " << vec.capacity() << endl; //capacity = 8
vec.clear();
cout << "size = " << vec.size() << endl; //size = 0
cout << "capacity = " << vec.capacity() << endl; //capacity = 8
return 0;
}
2.3 元素構造
- vector提供了許多constructors,其中的一個允許我們指定空間大小及初值:
//建構函式,允許指定大小 n和初值value
vector(size_type n, const T& value) {
fill_initialize(n,value);
}
// 填充並予以初始化
void fill_initialize(size_type n, const T& value){
start = allocate_and_fill(n, value);
finish = start + n;
end_of_storage = finish;
}
//配置而後填充
iterator allocate_and_fill(size_type n, const T& x){
iteraator result = data_allocator:::allocate(n);
uninitialized_fill_n(result, n, x);
return result;
}
2.4 vector擴容
- 當以push_back插入元素時,首先檢查是否有備用空間,如果存在,則在備用空間直接構造元素;如果沒有備用空間,則擴充空間(重新配置,移動資料,釋放原空間):
void push_back(const T& x) {
if (finish != end_of_storage) { //還有備用空間
construct(finish, x); //直接在備用空間構造元素
++finish; //調整finish位置
}
else //無備用空間
insert_aux(end(), x);
}
//無備用空間呼叫insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
if (finish != end_of_storage) //還有備用空間
{
construct(finish, *(finish - 1)); //在備用空間起始處構造元素,並以最後一個元素值為其初值
++finish;
T x_copy = x;
copy_backward(position, finish - 2, finish - 1); //將position(即end())後的元素往後移
*position = x_copy;
}
else { //無備用空間
const size_type old_size = size();
//配置空間:如果原大小為0,則配置1;否則配置原大小的兩倍
const size_type len = old_size != 0 ? 2 * old_size : 1;
iterator new_start = data_allocator::allocate(len); //實際配置
iterator new_finish = new_start;
try {
//將原元素拷貝到新空間
new_finish = uninitialized_copy(start, position, new_start);
construct(new_finish, x); //併為新元素設立初值x
++new_finish;
//將position之後的元素也拷貝過來
new_finish = uninitialized_copy(position, finish, new_finish);
}
catch(...) { //若非全部成功,則‘析構’所有
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
destroy(begin(), end()); //釋放原空間
deallocate();
//調整迭代器,指向新空間
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
- 從上述程式碼可以知道,所謂動態增加大小,並不是在原空間之後接續新空間,而是以原大小的的兩倍另外配置空間,然後拷貝原vector的內容,在新空間構造元素,並釋放原空間,這也是為什麼在《C++ primer》中出現的插入可能使得迭代器失效的原因,正是因為插入元素可能使得空間重新配置,而導致原vector空間被釋放,使得迭代器失效
3.vector元素操作:pop_back,erase,clear,insert
3.1 各操作及其功能
操作 | 功能 |
---|---|
pop_back | 將尾端元素拿掉 |
erase | 清空迭代器範圍中的所有元素 |
clear | 清空vector中所有元素 |
insert | 在某個插入點插入特定的元素 |
-
對於上述幾個操作,我們重點理解一下insert操作,因為insert相對其他幾個操作來說更為複雜,不易理解:
-
(忽略上述圖畫的不好看orz)在這裡就不貼出程式碼了,重點理解一下即可:
在position位置插入n個x
1. 計算備用空間大於等於新增元素個數
## 1.1 插入點之後元素個數大於新增元素個數: ### 1.1.1 將finish之前的n個元素移到finish之後 ### 1.1.2 finish變更為finish + n得到newfinish ### 1.1.3 將position之後到oldfinish - n之間的元素後移到oldfinish位置 ### 1.1.4 從position開始填充n個x ## 1.2 插入點之後元素小於新增元素個數 ### 1.2.1 將差值補到finish之後 ### 1.2.2 finish變更為finish + n - elems_after得到newfinish ### 1.2.3 將position到oldfinish的元素拷貝到newfinish之後 ### 1.2.4 在position到oldfinish之間插入x
2. 備用空間小於新增元素個數
## 2.1 申請空間,old_size + max(old_size, n) //舊長度的兩倍或舊長度+新增元素個數 ## 2.2 兩複製一填充: ### 2.2.1 將原vector插入點之前的元素拷貝到新空間 ### 2.2.2 將新增元素填入新空間 ### 2.2.3 將原vector插入點之後的元素拷貝到新空間