vector容器概述
vector的數據安排以及操作方式,與array非常相似。兩者的唯一區別在於空間的運用的靈活性。array是靜態空間,一旦配置了就不能改變;要換個大(或小)一點的房子,可以,一切瑣細都得由客戶端自己來:首先配置一塊新空間,然后將元素從舊址一一搬往新址,再把原來的空間釋還給系統。vector是動態空間,隨着元素的加入,它的內部機制會自行擴充空間以容納新元素。因此,vector的運用對於內存的合理利用與運用的靈活性有很大的幫助,我們再也不必因為害怕空間不足而一開始要求一個大塊頭的array了,我們可以安心使用array,吃多少用多少。
vector的實現技術,關鍵在於其對大小的控制以及重新配置時的數據移動效率。一旦vector的舊有空間滿載,如果客戶端每新增一個元素,vector的內部只是擴充一個元素的空間,實為不智。因為所謂擴充空間(不論多大),一如稍早所說,是”配置新空間/數據移動/釋還舊空間“的大工程,時間成本很高,應該加入某種未雨綢繆的考慮。稍后我們便可看到SGI vector的空間配置策略了。
另外,由於vector維護的是一個連續線性空間,所以vector支持隨機存取。
注意:vector動態增加大小時,並不是在原空間之后持續新空間(因為無法保證原空間之后尚有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然后將原內容拷貝過來,然后才開始在原內容之后構造新元素,並釋放原空間。因此,對vector的任何操作,一旦引起空間重新配置,指向原vector的所有迭代器就都失效了。這是程序員易犯的一個錯誤,務需小心。
以下是vector定義的源代碼摘錄:
#include<iostream> using namespace std; #include<memory.h> // alloc是SGI STL的空間配置器 template <class T, class Alloc = alloc> class vector { public: // vector的嵌套類型定義,typedefs用於提供iterator_traits<I>支持 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: // 這個提供STL標准的allocator接口 typedef simple_alloc <value_type, Alloc> data_allocator; iterator start; // 表示目前使用空間的頭 iterator finish; // 表示目前使用空間的尾 iterator end_of_storage; // 表示實際分配內存空間的尾 void insert_aux(iterator position, const T& x); // 釋放分配的內存空間 void deallocate() { // 由於使用的是data_allocator進行內存空間的分配, // 所以需要同樣使用data_allocator::deallocate()進行釋放 // 如果直接釋放, 對於data_allocator內部使用內存池的版本 // 就會發生錯誤 if (start) data_allocator::deallocate(start, end_of_storage - start); } void fill_initialize(size_type n, const T& value) { start = allocate_and_fill(n, value); finish = start + n; // 設置當前使用內存空間的結束點 // 構造階段, 此實作不多分配內存, // 所以要設置內存空間結束點和, 已經使用的內存空間結束點相同 end_of_storage = finish; } public: // 獲取幾種迭代器 iterator begin() { return start; } iterator end() { return finish; } // 返回當前對象個數 size_type size() const { return size_type(end() - begin()); } size_type max_size() const { return size_type(-1) / sizeof(T); } // 返回重新分配內存前最多能存儲的對象個數 size_type capacity() const { return size_type(end_of_storage - begin()); } bool empty() const { return begin() == end(); } reference operator[](size_type n) { return *(begin() + n); } // 本實作中默認構造出的vector不分配內存空間 vector() : start(0), finish(0), end_of_storage(0) {} vector(size_type n, const T& value) { fill_initialize(n, value); } vector(int n, const T& value) { fill_initialize(n, value); } vector(long n, const T& value) { fill_initialize(n, value); } // 需要對象提供默認構造函數 explicit vector(size_type n) { fill_initialize(n, T()); } vector(const vector<T, Alloc>& x) { start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end()); finish = start + (x.end() - x.begin()); end_of_storage = finish; } ~vector() { // 析構對象 destroy(start, finish); // 釋放內存 deallocate(); } vector<T, Alloc>& operator=(const vector<T, Alloc>& x); // 提供訪問函數 reference front() { return *begin(); } reference back() { return *(end() - 1); } //////////////////////////////////////////////////////////////////////////////// // 向容器尾追加一個元素, 可能導致內存重新分配 //////////////////////////////////////////////////////////////////////////////// // push_back(const T& x) // | // |---------------- 容量已滿? // | // ---------------------------- // No | | Yes // | | // ↓ ↓ // construct(finish, x); insert_aux(end(), x); // ++finish; | // |------ 內存不足, 重新分配 // | 大小為原來的2倍 // new_finish = data_allocator::allocate(len); <stl_alloc.h> // uninitialized_copy(start, position, new_start); <stl_uninitialized.h> // construct(new_finish, x); <stl_construct.h> // ++new_finish; // uninitialized_copy(position, finish, new_finish); <stl_uninitialized.h> //////////////////////////////////////////////////////////////////////////////// void push_back(const T& x) { // 內存滿足條件則直接追加元素, 否則需要重新分配內存空間 if (finish != end_of_storage) { construct(finish, x); ++finish; } else insert_aux(end(), x); } //////////////////////////////////////////////////////////////////////////////// // 在指定位置插入元素 //////////////////////////////////////////////////////////////////////////////// // insert(iterator position, const T& x) // | // |------------ 容量是否足夠 && 是否是end()? // | // ------------------------------------------- // No | | Yes // | | // ↓ ↓ // insert_aux(position, x); construct(finish, x); // | ++finish; // |-------- 容量是否夠用? // | // -------------------------------------------------- // Yes | | No // | | // ↓ | // construct(finish, *(finish - 1)); | // ++finish; | // T x_copy = x; | // copy_backward(position, finish - 2, finish - 1); | // *position = x_copy; | // ↓ // data_allocator::allocate(len); <stl_alloc.h> // uninitialized_copy(start, position, new_start); <stl_uninitialized.h> // construct(new_finish, x); <stl_construct.h> // ++new_finish; // uninitialized_copy(position, finish, new_finish); <stl_uninitialized.h> // destroy(begin(), end()); <stl_construct.h> // deallocate(); //////////////////////////////////////////////////////////////////////////////// iterator insert(iterator position, const T& x) { size_type n = position - begin(); if (finish != end_of_storage && position == end()) { construct(finish, x); ++finish; } else insert_aux(position, x); return begin() + n; } iterator insert(iterator position) { return insert(position, T()); } void pop_back() { --finish; destroy(finish); } iterator erase(iterator position) { if (position + 1 != end()) copy(position + 1, finish, position); --finish; destroy(finish); return position; } iterator erase(iterator first, iterator last) { iterator i = copy(last, finish, first); // 析構掉需要析構的元素 destroy(i, finish); finish = finish - (last - first); return first; } // 調整size, 但是並不會重新分配內存空間 void resize(size_type new_size, const T& x) { if (new_size < size()) erase(begin() + new_size, end()); else insert(end(), new_size - size(), x); } void resize(size_type new_size) { resize(new_size, T()); } void clear() { erase(begin(), end()); } protected: // 分配空間, 並且復制對象到分配的空間處 iterator allocate_and_fill(size_type n, const T& x) { iterator result = data_allocator::allocate(n); uninitialized_fill_n(result, n, x); return result; } // 提供插入操作 //////////////////////////////////////////////////////////////////////////////// // insert_aux(iterator position, const T& x) // | // |---------------- 容量是否足夠? // ↓ // ----------------------------------------- // Yes | | No // | | // ↓ | // 從opsition開始, 整體向后移動一個位置 | // construct(finish, *(finish - 1)); | // ++finish; | // T x_copy = x; | // copy_backward(position, finish - 2, finish - 1); | // *position = x_copy; | // ↓ // data_allocator::allocate(len); // uninitialized_copy(start, position, new_start); // construct(new_finish, x); // ++new_finish; // uninitialized_copy(position, finish, new_finish); // destroy(begin(), end()); // deallocate(); //////////////////////////////////////////////////////////////////////////////// template <class T, class Alloc> void 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; // 將安插點以后的原內容也拷貝過來 new_finish = uninitialized_copy(position, finish, new_finish); } catch(...) { // 回滾操作 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; } } //////////////////////////////////////////////////////////////////////////////// // 在指定位置插入n個元素 //////////////////////////////////////////////////////////////////////////////// // insert(iterator position, size_type n, const T& x) // | // |---------------- 插入元素個數是否為0? // ↓ // ----------------------------------------- // No | | Yes // | | // | ↓ // | return; // |----------- 內存是否足夠? // | // ------------------------------------------------- // Yes | | No // | | // |------ (finish - position) > n? | // | 分別調整指針 | // ↓ | // ---------------------------- | // No | | Yes | // | | | // ↓ ↓ | // 插入操作, 調整指針 插入操作, 調整指針 | // ↓ // data_allocator::allocate(len); // new_finish = uninitialized_copy(start, position, new_start); // new_finish = uninitialized_fill_n(new_finish, n, x); // new_finish = uninitialized_copy(position, finish, new_finish); // destroy(start, finish); // deallocate(); //////////////////////////////////////////////////////////////////////////////// template <class T, class Alloc> void insert(iterator position, size_type n, const T& x) { // 如果n為0則不進行任何操作 if (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(...) { destroy(new_start, new_finish); data_allocator::deallocate(new_start, len); throw; } # endif /* __STL_USE_EXCEPTIONS */ destroy(start, finish); deallocate(); start = new_start; finish = new_finish; end_of_storage = new_start + len; } } } };
1 vector本質
vector數據結構如下,通過三個迭代器start, finish, end_of_storage的系列public接口,可很好地完成數據存儲、溢出判斷(iter >= iv.end())、大小、容量(容量與大小不等,以免不斷申請空間耗費資源)、重載操作符[]、判空、最前元素、最后元素等等。
class vector{ … protected: iterator start ; iterator finish; iterator end_of_storage; public: iterator begin () { return start ; } iterator end() { return finish; } 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(); } reference operator[] (size_type n) { return *(begin() + n); } reference front () { return *begin(); } … }
由於vector是用連續空間存儲數據,不斷擴容將導致大量的新空間申請、元素拷貝和釋放原有空間,十分耗時,vector申請空間時,都將多申請部分空間備用,如下圖的[finish, end_of_storage)所示。只有當finish == end_of_storage時,再申請新的空間(2*capacity()), 圖2就是在圖1的基礎上,再插入元素引起的空間變化時的數據存儲情景。
圖1 vector數據存儲-1
在圖1和圖2中的start不再指向相同的地址,擴大空間是新請新的更大的空間,因而不僅是start迭代器,其它指向空間變化前vector的迭代器都將失效。此處極易引起bug。
圖2 vector數據存儲-2
2 vector常用方法與技巧
1、空間申請
構造函數、reserve()、resize()。
1: vector<int> iv(3, -1); 2: iv.reserve(10); 3: iv.resize(10, -1);
構造函數不述;
reserve(n),申請空間,等同於擴大[finish, end_of_storage),當n <= capacity()時,無效,可以理解為主要改變end_of_storage(或capacity)——reserve()匹配capacity();
resize(10),申請空間並賦值,等同於改變[start, finish),可以理解為主要改變finish(或size())——resize()匹配size()。
2、空間釋放
erase()、resize()、clear()均僅改變finish(或size()),不改變end_of_storage。
swap()釋放空間——清空,改變end_of_storage:
vector<int> iv(10, -1); iv.reserve(500); vector<int>().swap(iv);//交換空間 cout<<iv.capacity()<<endl;//輸出0
swap()釋放空間——釋放多余空間,改變end_of_storage:
vector<int> iv(10, -1); iv.reserve(500); vector<int>(iv).swap(iv);//交換空間 cout<<iv.capacity()<<endl;//輸出10
3、resize()與operator []
operator []使得vector與array處理完全類似,但operator []極易引起異常與錯誤,如下:
vector<int> iv(); iv.reserve(500); iv[300] = -1; cout<<iv.capacity()<<endl;//輸出500 cout<<iv.size()<<endl;//輸出0
這里就引起了迭代器不符合預期,也不知道什么才是預期。建議用法為resize()與operator []一起使用,從而使得操作完全等同於array,且安全:
vector<int> iv(); iv.resize(500, 0); iv[300] = -1; cout<<iv.capacity()<<endl;//輸出500 cout<<iv.size()<<endl;//輸出500
4、vector迭代器
vector的迭代器十分簡單,等同於指針,++、--、+n、-n、>、<、!=等操作都可應用——random access iterator,基本上全部stl algorithms均可以在此上應用。
PS:強烈不推薦使用>,<,≤,≥之類比較迭代器,遍歷時直接使用!=即可,以免混於其它非random access iterator的容器。
參考資料:
1、侯捷. STL源碼剖析;
2、侯捷. STL源碼剖析注釋;
對於同仁們的布道授業,一並感謝。
----