STL源碼學習——Vector(向量)
今天繼續看STL源碼喵。雖然基本上說vector是最簡單的容器了,但其實相對來說我覺得同list比起來,還是list實現方便一些喵~讓電腦以人腦的方式工作總比讓人腦以電腦的方式工作簡單吧喵~
Vectors 包含着一系列連續存儲的元素,其行為和數組類似。訪問Vector中的任意元素或從末尾添加元素都可以在常量級時間復雜度內完成,而查找特定值的元素所處的位置或是在Vector中插入元素則是線性時間復雜度。(C/C++ 語言參考,見第8點)
1 :vector中[]沒有檢查數組越界,相對應的at()函數檢查了。所以at()相對來說較為安全喵~
2 :注意capacity()與max_size()的區別喵~總覺得max_size()這個函數怪怪的。

1 size_type max_size() const 2 { 3 return size_type(-1) / sizeof(_Tp); 4 } 5 size_type capacity() const 6 { 7 return size_type(_M_end_of_storage._M_data - _M_start); 8 }
3 :vector的swap()成員函數效率為O(1),只是交換了各自的指針。

1 iterator insert(iterator __position, const _Tp &__x) 2 { 3 size_type __n = __position - begin(); 4 if (_M_finish != _M_end_of_storage._M_data) 5 { 6 if (__position == end()) 7 { 8 _Construct(_M_finish, __x); 9 ++_M_finish; 10 } 11 else 12 { 13 _Construct(_M_finish, *(_M_finish - 1)); 14 ++_M_finish; 15 _Tp __x_copy = __x; 16 copy_backward(__position, _M_finish - 2, _M_finish - 1); 17 *__position = __x_copy; 18 } 19 } 20 else 21 _M_insert_overflow(__position, __x); 22 return begin() + __n; 23 }
4 :這個insert()函數開始我看那里求出了相對偏移量 __n 然后我再看,這個函數只有最后那行return語句用到了這個 __n。
然后我再想,這里為什么要這樣寫呢,原來這里有迭代器失效的問題喵~
5 :第15行出現了一個__x_copy,我開始一直在想,這里復制一次有什喵用呢?這里並沒對__x或__x_copy做任何改變啊。那復制一份有什喵用呢?
原來關鍵的問題就在__x_copy使用的那兩行之間的區間復制函數。雖然__x是一個const引用,不會在這個函數里被改變,但在其它地方是可以被改變的哦,而且當它正好是[__position, _M_finish - 2)之間的數時,就可能會被改變。所以這里提前做了一次備份喵。
6 :接着看下一個insert()函數,STL中效率考慮得很細嘛,開始看vector中的
iterator insert(iterator __position)
我以為會像list中一樣后一個函數直接調用前一個函數,可這里並沒有,而用重寫了一次。
然后我仔細看了一下,重寫的那個函數沒有第15行的__x_copy這個變量,
重寫可以避免元素的構造而產生的浪費。(不過個人還是覺得僅僅為了這點而重寫這個函數有點得不償失喵~)
7 :vector.size()居然能返回-1。

1 int main() 2 { 3 vector<int> v(1, 1); 4 v.pop_back(); 5 v.pop_back(); 6 printf("%d v.size\n", v.size()); 7 printf("%d v.capacity\n", v.capacity()); 8 return 0; 9 }
原因是pop_back()函數,這個函數居然沒有判斷當前容器中的元素個數!這樣真的沒問題喵~
7 :大家請看如下函數,注釋為期望輸出。
如果你覺得注釋期望輸出與你想的一致,那么你可以試着運行一下這段代碼,將會有一個驚喜等着你哦~
1 int main() 2 { 3 vector<int> v; 4 int n = 3; 5 for (int i=0; i<n; i++) 6 v.push_back(i); 7 for (vector<int>::iterator it=v.begin(); it!=v.end(); ++it) 8 printf("%d ", *it); 9 puts(""); ///0 1 2 10 v.insert(v.begin(), v.end()-1, v.end()); 11 for (vector<int>::iterator it=v.begin(); it!=v.end(); ++it) 12 printf("%d ", *it); 13 puts(""); ///2 0 1 2 14 return 0; 15 }
第二行的真實輸出為“1 0 1 2”。為什喵會這樣呢?
因為在這次insert過程中,STL並沒有判斷被移動區間[0,3)與被復制區間[2,3)有重疊的情況。
這里的實際操作順序為先移動[0,3)再復制[2,3)(而此時[2,3)的值而被改變),這與我們的思維是不一致的喵~
喵喵喵~
不過,如果你的好奇心比較強的話,你可能會多做一些實驗,這樣的話你就會發現有的時候復制是正確的,比如將上式中的n=3改為n=2然后你就會發現結果是與我們預期的結果是一致的。(有一定隨機性)
這又是為什喵呢?為什喵呢?為什喵呢?
原來是,當n=2的時候,在執行insert的過程中,已經將作為參數的那片內存區域丟棄了,也就是說在insert執行到一半的時候作為參數傳入的迭代器就已經失效了哦。所以在后面復制[end-1,end),實際是去讀的那塊已經丟棄,但還沒有被改寫的內存區域里的數據。(當然也可能被改寫的)
所以在insert傳入區間參數時,最好不要是自身vector的迭代器。如果實在是要在某點插入自身的部分區間,那最好還是先手動復制一份,再使用。避免這種意想不到的bug喵~
8 :這一點也是之前就有耳聞的喵~就是vector的push_back的平均效率為線性的。
因為當vector需要自己增加存儲空間的時候一般是增加為原來兩倍。
所以 1 + 2 + 4 + 8 + ... + n = 2n 喵。
歡迎大家一起討論喵