STL vector動態擴容


1. 基本操作

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    vector<int>v1;
    vector<int>v2(4);
    vector<int>v3(1,4);

    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);

    // 這里清除區間,左閉右開
    v1.erase(v1.begin()+2, v1.end()-1);

    v2 = v1;
    if(!v2.empty())
    {
        for(const auto i : v2)
        {
            cout << i << " ";
        }
        cout << endl;
        cout << "v1 size: " << v2.size() << endl;
        v2.~vector();
    }
    return 0;
}


1. push_back和pop_back操作
都只是對尾部進行操作,
push_back:從尾部插入數據時,當數組還有備用空間時就直接插入尾部就行,如果沒有就重新尋找更大的空間並將數據賦值過去
void push_back(const T& x)
{
      // 如果還沒有到填滿整個數組, 就在數據尾部插入
      if (finish != end_of_storage)
      {
            construct(finish, x);
            ++finish;
      }
    // 數組被填充滿, 調用insert_aux必須重新尋找新的更大的連續空間, 再進行插入
      else
          insert_aux(end(), x);
}

pop_back: 從尾部刪除,使用空間的尾自減並調用析構函數,但是並沒有釋放內存

void pop_back()
{
     --finish;
     destroy(finish);
}

finish始終都指向最后一個元素的后一個位置的地址.


2. 容器大小的調整
reverse: 修改容器的大小  空間大小
void reverse(size_type n)
{
    // 修改容器的大小要大於之前的容器大小
    
}

resize: size()  使用大小, 變小釋放,變大用尾部填充

new_size大於cap,小於2*cap,則用cap = 2*new_size
        大於2*cap,則cap = new_size

void resize(size_type new_size, const T& x)
{
    // 元素大小大於了要修改的大小, 則釋放掉超過的元素
      if (new_size < size())
        erase(begin() + new_size, end());
    // 元素不夠, 就從end開始到要求的大小為止都初始化x
      else
        insert(end(), new_size - size(), x);
}

2. reverse 和 resize

// vector::reserve
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int>::size_type sz;

  std::vector<int> foo;
  sz = foo.capacity();
  std::cout << "making foo grow:\n";
  for (int i=0; i<1000; ++i) {
    foo.push_back(i);
    //std::cout << "i: " << i << '\n';
    if (sz!=foo.capacity()) {
      sz = foo.capacity();
      std::cout << "capacity changed: " << sz << '\n';
    }
  }

  std::cout << "foo size: " << foo.size() << '\n';
  std::cout << "cap: " << foo.capacity() <<'\n';

  sz = foo.capacity();
  for(int i = 0;i < 1000;i++)
  {
      //std::cout << foo.pop_back() << '\n';
      std::cout << "i: " << i << " cap: " << foo.capacity() << " size: " << foo.size()<< '\n';
      foo.pop_back();
      if (sz!=foo.capacity()) {
        sz = foo.capacity();
        std::cout << "capacity changed: " << sz << '\n';
    }
  }

  return 0;
}

3. 測試

#include <iostream>
#include <vector>
using namespace std;

int main ()
{
    std::vector<int>::size_type sz;
    std::vector<int> foo;
    vector<int>v1;
    for(int i = 0;i < 12;i++)
    {
        v1.push_back(i);
        cout << v1.capacity() << " " << v1.size() << endl;
    }
    cout << "max_size: " << v1.max_size() << endl;
}

可見,VS中按1.5倍擴容,GCC以2倍擴容。

一種不調用析構函數將vector清空的方法:

vector<int>().swap(v1);

4. 擴容因子

實際上,C++標准並沒有push_back要用哪個增長因子,這是由標准庫的實現者決定的。

如何選取擴容因子呢?

從空間角度:擴容因子越大,預留的空間就越大,浪費的空間也越多

從時間角度:擴展到相同長度下,K越小,擴容的次數越多,時間開銷越大

假設擴容因子為k,擴容后的最終長度為n,這意味着需要擴容 $log_kn$ 次,

這元素復制和開辟內存的時間正比於: $t = 1 + k + k^2 + ... + k^{log_kn -1} = \frac{n-1}{k-1}$,可見k越小越好。

如何達到時間和空間的平衡呢?

我們來看一下K = 2時的情況。

每次擴容后capacity的情況如下:1,2,4,8,16,32 ……..

當我們釋放了4的空間,我們尋找8的新空間,再次擴容,釋放8,尋找16。。

仔細分析,第5次擴容時,需要尋找16的新空間,第4次釋放了8,第3次釋放了4,第2次釋放了2,第1次釋放了1,所以 1 + 2 + 4 + 8 = 15 < 16,也就意味着,之前釋放的空間,永遠無法被下一次的擴容利用,這對內存與cache是非常不友好的。

我們再來看一下K = 1.5的情況。

每次擴容之后capacity的情況為:1,2,3,4,6,9,13,19,28 ……

再按剛才的思路分析一遍,1 + 2 >= 3; 2 + 3 + 4 >= 6; 6 + 9 >= 13 …….

所以,當K為1.5時,顯然對內存和cache要友好很多,至少從容量上來說,是存在重復利用的可能性的。

因此,我們可以得出結論,當K = 2時,時間上要比 K = 1.5 占優,而空間上比 1.5 稍有劣勢。

理論最優擴容因子是多少呢?

繼續剛才的分析,我們希望的是,上幾次的空間,存在被下一次擴容時利用的可能性。

也就是 X(n-2) + X(n-1) >= X(n),顯然我們也希望時間上也要更好,即X(n-2) + X(n-1) = X(n)

即:1,2,3,5,8,13,21,34,55 。。。。

是不是很熟悉。。。是的,這就是我們的斐波那契數列。。。

那么當N趨於無限大時,取極限,最佳的擴容因子也就是那個最美的數,黃金分割率,1.618。

 

 

參考鏈接:

1. CSDN_denghe1122-STL vector (一)——擴容原理與內存分配與釋放

2. cplusplus-vector

3. CSDN_gettogetto-當面試官問我們vector擴容機制時,他想問什么?


免責聲明!

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



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