STL源碼學習——Vector(向量)


STL源碼學習——Vector(向量)

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

 

insert
 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。

測試size()
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 喵。
  

  歡迎大家一起討論喵


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM