STL中的Vector是最爲經常使用的順序容器之一,與C/C++中的數組Array很類似,最重要的區別在於對連續內存空間運用的靈活性。git
咱們在使用數組Array時要指定數組的大小,然後由系統來分配連續的內存空間,Array是靜態空間必須指定大小且指定以後自動沒法擴縮容,所以爲了不元素裝不下,通常都把數組設置的比預期要大,可是每每致使空間的浪費。github
另外每每不少時候需裝載元素的數量沒法預期,這樣設置怎樣的大小就取決於使用者了。若是最初數組設計比較小,那麼就面臨着開闢新的更大空間、將舊空間元素複製到新空間、釋放舊空間三個大步驟,這一切都是須要使用者本身來維護實現的。數組
使用數組太大浪費空間、過小浪費效率,真的很但願有個助手幫咱們作了這些事情,專業的人作專業的事情,讓咱們只關心業務邏輯便可,那該多好!bash
就這樣盼望着 盼望着 東風來了 春天的腳步近了,不不, vector的腳步近了….函數
vector底層與Array同樣都是連續的內存空間,區別在於vector的空間是動態的,隨着更多元素的加入能夠自動實現空間擴展,而且vector針對這種擴展作了優化,並非one by one的擴展,那樣實在是低效,而是按照某種倍率來擴展,這樣就有效了減小由於擴容帶來的複製效率下降問題。優化
簡單來講就是當須要放置1個元素時,vector空間已滿,此時vector並不會只向系統申請1個元素的空間,而是按照目前已佔用的空間的倍率來申請。ui
假如原來佔用A字節,那麼再次申請時多是2A字節,因爲此時向尾部地址擴展不必定有連續未分配的內存,大多時候仍是會涉及開闢新的更大空間、將舊空間元素複製到新空間、釋放舊空間
三個大步驟。spa
因此和數組相比底層的操做都是同樣的,不要把Vector神話,都是普通的結構只不過被封裝了一層而已。設計
從本質上看,vector就是在普通Array和使用者中間加了一層,從而把使用者從對數組的直接管理權接手過來,讓使用者有個管家同樣,在毫無影響使用的前提下更加省心和高效。指針
看中對vector的定義(節選部分紅員變量)
//alloc是SGI STL的空間配置器
template <class T,class Alloc=alloc> class vector {
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
//simple_alloc是SGI STL的空間配置器
typedef simple_alloc<value_type,Alloc> data_allocator;
//表示目前使用空間的頭
iterator start;
//表示目前使用空間的尾
iterator finish;
//表示目前可用空間的尾
iterator end_of_storage;
}複製代碼
從定義看vector 是一種前閉後開
的容器,在vector裏頭有一些變量記錄着一些信息,分別是三個迭代器:
vector的空間示意圖:
vector中有三個和容量空間相關的成員函數:size、capacity、empty。
其中size表明已存儲的元素數量,capacity表示能夠容納的最大元素數量,empty判斷是否無元素存儲,這些判斷也都是依據迭代器來實現的,定義以下:
size_type size() const {return size_type(end()-begin());}
size_type capacity() const{return size_type(end_of_storage-begin());}
bool empty() const{return begin()==end();}複製代碼
vector因爲在底層和數組沒有區別,都是地址連續的內存空間,所以普通指針具有vector迭代器的重要功能,諸如:*、->、++、--、+、-、+=、-=
這些迭代器操做,均可以由指針來實現。
//迭代器舉例
iterator begin()
{
return start;
}
iterator end()
{
return finish;
}
reference front()
{
return *begin();
}
reference back()
{
return *(end() - 1);
}複製代碼
在使用push_back()將元素插入vector尾端時的基本流程:
代碼定義以下:
void push_back(const T& x)//添加元素 {
//是否超出最大可容納空間
if(finish !=end_of_storage){
/*全局函數,construct()接收一個指針p和一個初值value, 該函數的用途就是將 初值value設定到指針鎖指的空間上。 */
construct(finish,x);
++finish;
}
else{
insert_aux(end(),x); //vector的成員函數
}
}
template <class T, class Alloc = alloc> void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
if (finish != end_of_storage) {
//在備用空間起始處構造一個元素,並以vector最後一個元素值爲其初始值
construct(finish, *(finish - 1));
//調整水位
++finish;
T x_copy = x;
copy_backward(position, finish - 2, finish -1);
*position = x_copy;
}
else { //已無備用空間
const size_type old_size = size();
const size_type len = old_size != 0 ? 2 * old_size : 1;
//以上配置原則:若是原大小爲0,則配置1(個元素大小);
//若是原大小不爲0, 則配置原大小的兩倍,
//前半段用來放置原數據,後半段準備用來放置新數據
iterator new_start = data_allocator::allocate(len); //實際配置
iterator new_finish = new_start;
try {
//將原vector的內容拷貝到新vector
new_finish = uninitialized_copy(start, position, new_start);
//將新元素設定初值x
construct(new_finish, x);
//調整水位
++new_finish;
//將安插點的原內容也拷貝進來(提示:本函數也可能被insert(p,x)調用)
new_finish = uninitialized_copy(position, finish, new_finish);
}
catch(...) {
//"commit or rollback" semantics
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
//析構並釋放原vector
destroy(begin(), end());
deallocate();
//調整迭代器,指向新vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}複製代碼
insert成員函數也是實現元素添加的主要函數,所以也涉及了與push_back相似的擴容過程,可是比push_back更復雜一些,在插入元素時須要根據插入點、備用空間大小、待插入元素數量等角度進行分類對待,詳細過程看下代碼定義:
//從position開始,插入n個元素,元素初值爲x
template <class T, class Alloc> void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
if ( n != 0){ //當n!=0才進行如下全部操做
if(size_type(end_of_storage - finish) >= n) {
//備用空間大於等於「新增元素個數」
T x_copy = x;
//如下計算插入點以後的現有元素個數
const size_type elems_after = finish - position;
iterator old_finish = finish;
if (elems_after > n) {
//「插入點以後的現有元素個數」大於「新增元素個數」
uninitialized_copy(finish - n, finish, finish);
finish += n; //將vector尾端標記後移
copy_backward(position, old_finish - n, old_finish); //逆向拷貝
fill(position, position + n, x_copy);
}
else {
//「插入點以後的現有元素個數」小於等於「新增元素個數」
uninitialized_fill_n(finish, n - elems_after, x_copy);
finish += n-elems_after;
uninitialized_copy(position, old_finish, finish);
finish += elems_after;
fill(position, old_finish, x_copy);
}
}
else {
//備用空間小於「新增元素個數」(那就必須配置額外內存)
//首先決定新長度:舊長度的兩倍,或舊長度+新增元素個數
const size_type old_size = size();
const size_type len = old_size + max(old_size, n);
//如下配置新的vector空間
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY {
//如下首先將舊vector的插入點以前的元素複製到新空間
new_finish = uninitialized_copy(start, position, new_start);
//如下再將新增元素(初值皆爲n)填入新空間
new_finish = uninitialized_fill_n(new_finish, n, x);
//如下再將舊vector的插入點以後的元素複製到新空間
new_finish = uninitialized_copy(position, finish, new_finish);
}
#ifdef __STL_USE_EXCEPTIONS
catch(...) {
//若有異常發生,實現」commit or rollback"semantics
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
#endif /* __STL_USE_EXCEPTIONS */
//如下清楚並釋放舊的vector
destroy(start, finish);
deallocate();
//如下調整水位標記
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}複製代碼
插入點、備用空間大小、待插入元素數量等角度進行分類:
1-1 插入點以後元素個數大於新增元素個數:
A. 將finish以前的n個元素移到finish以後
B. finish變動爲finish + n獲得newfinish
C. 將position以後到oldfinish - n之間的元素後移到oldfinish位置
D. 從position開始填充n個x
1-2 插入點以後元素小於新增元素個數
A. 將差值補到finish以後
B. finish變動爲finish + n - elems_after獲得newfinish
C. 將position到oldfinish的元素拷貝到newfinish以後
D. 在position到oldfinish之間插入x
2. 備用空間小於新增元素個數
A. 申請空間,old_size + max(old_size, n)
B. 複製填充:
c. 將原vector插入點以前的元素拷貝到新空間
D. 將新增元素填入新空間
E. 將原vector插入點以後的元素拷貝到新空間
這個過程仍是比較有意思的,其中涉及到了幾個泛型拷貝:
uninitialized_copy和copy_backward等,能夠再本身細緻地畫一下圖理解這個過程,在此再也不贅述。