上一篇文章中,關於stl_vector的故事只是個開始. 這篇文章中,接著去分析vector中的細節問題.
再次聲明,我沒有看過關於stl源碼分析方面的書籍,強調這一點是為了不會讓別人誤會我是從別的地方抄襲的. 另外,歡迎大家拍磚。
後面陸續的幾篇文章都會詳細分析vector中的函數實現. 我盡量做到篇幅不大,而且能夠盡量用自己的語言和自己分析的結果給大
家展現實現的過程. 這樣不會浪費大家的時間.寫的不好就請原諒了.
push_back函數的作用就是從_M_finish指向的位置開始插入資料。如果預先分配的儲存單元不夠,則會做擴充處理.會在後面分析的時候
說明這個問題.
void push_back(const _Tp& __x) { if (_M_finish != _M_end_of_storage) { construct(_M_finish, __x); ++_M_finish; } else _M_insert_aux(end(), __x); }// **// * 實現和上面相同,只是在需要初始化的地方都是用預設初始化值// ** void push_back() { if (_M_finish != _M_end_of_storage) { construct(_M_finish); ++_M_finish; } else _M_insert_aux(end()); }
1. _M_finish和_M_end_of_storage有什麼區別?_M_finish和_M_end_of_storage同樣都是vector從vector_base中繼承來的,
但是二者的作用是不同的.
[ _M_finish ] : 資料類型的指標,執行當前操作的資料. 在預設的初始化時候,_M_finish==_M_start.也就是指向底層儲存元素
數組的頭元素.例如不斷的往裡面push_back資料,那麼_M_finish就會不斷的像數組的尾端移動,
直到_M_end_of_storage.
[_M_end_of_storage ] : _M_end_of_storage在使用vector第一次初始化分配數組記憶體的時候,是指向數組的最後一個元素的後一個位置
因為初始化的時候 _M_finish = _M_start, _M_end_of_storage = _M_start + _n. (_n是元素個數).
隨著不斷的對vector進行操作,後面可能會導致_M_end_of_storage發生變化.
說的直白一點就是,_M_finish是_M_start(頭指標)和_M_end_of_storage(尾指標的後一個位置)之間遊動的遊標.
2. 執行push_back(n)的操作的時候,首先會檢查數組的容量有沒有達到上限,如果_M_finish == _M_end_of_storage.那麼就需要擴大
儲存單元,就會在_M_insert_aux(end(),_x)中進行容量的擴增.
3. 如果沒有達到上限的話,也就是說有空餘的空間,那麼就給當前遊標_M_finish指向的位置賦值,然後將遊標向下一個位置移動.
4. 如果沒有達到上限,就使用construct對_M_finish指向的記憶體地區進行賦值.
template <class _T1, class _T2>inline void _Construct(_T1* __p, const _T2& __value) { new ((void*) __p) _T1(__value);}
對於new的這種用法,不是很常見,但是在<<c++ primer>>中後面"特殊工具與技術"章節有講解.
new (place_address) typenew (place_address) type(parm-list)
5. 如果達到上限,就需要擴張儲存地區,按照原地區的2倍進行擴張.
const size_type __old_size = size(); // 擷取原儲存地區長度 const size_type __len = __old_size != 0 ? 2 * __old_size : 1; // 擴張為原來的2倍 iterator __new_start = _M_allocate(__len); // 按照新儲存長度重新申請記憶體 iterator __new_finish = __new_start; __STL_TRY { __new_finish = uninitialized_copy(_M_start, __position, __new_start); // 將原來數組中的_M_start -> _M_finish之間的資料複製到新的儲存地區 construct(__new_finish, __x); // insert new value. // 插入新資料,和前面的 操作一樣 ++__new_finish; // go next. // 移動_M_finish __new_finish = uninitialized_copy(__position, _M_finish, __new_finish); } __STL_UNWIND((destroy(__new_start,__new_finish), _M_deallocate(__new_start,__len))); destroy(begin(), end()); _M_deallocate(_M_start, _M_end_of_storage - _M_start); // 釋放原儲存記憶體 _M_start = __new_start; // 處理其他改動 _M_finish = __new_finish; _M_end_of_storage = __new_start + __len;
這裡可能有些人不明白為什麼要調用兩次uninitialized_copy函數,其實有點想多了,這要看上面的調用:
_M_insert_aux(end(), _x). 而end()的作用就是擷取_M_finish.而在uninitialized_copy函數中,首先會進行判斷.
template <class _InputIter, class _ForwardIter>_ForwardIter __uninitialized_copy_aux(_InputIter __first, _InputIter __last, _ForwardIter __result, __false_type){ _ForwardIter __cur = __result; __STL_TRY { for ( ; __first != __last; ++__first, ++__cur) _Construct(&*__cur, *__first); return __cur; } __STL_UNWIND(_Destroy(__result, __cur));}
雖然通過很多層的轉換,但是還是可以看到判斷_first != _last. 所以在上面的調用中這句是不會有執行的.而這看起來有點多此一舉是吧?
不是. 因為_M_insert_aux提供的是通用的使用方法,需要使用一種通用的實現.而上面的調用最終還是會得到 _new_finish = _new_finish.
所以不需要糾結.
最後,關於push_back的另一個一個參數的版本就不需要多解釋了,因為二者之間在實現上方法和原理都是一樣的,多說無益.