vector的原理與底層實現


 

重點介紹一下resize()擴容和reserve()兩個函數

  resize()

                  resize()擴容的默認構造的方式是0, 之后插入按照1 2  4  8  16 二倍擴容。注(GCC是二倍擴容,VS13是1.5倍擴容。原因可以考慮內存碎片和伙伴系統,內存的浪費)。

     擴容后是一片新的內存,需要把舊內存空間中的所有元素都拷貝進新內存空間中去,之后再在新內存空間中的原數據的后面繼續進行插入構造新元素,

     並且同時釋放舊內存空間,並且,由於vector 空間的重新配置,導致舊vector的所有迭代器都失效了。

  reserve():

                     1、reserve只是保證vector的空間大小(_capacity)最少達到它的參數所指定的大小n。在區間[0, n)范圍內,預留了內存但是並未初始化

                   2、只有當所申請的容量大於vector的當前容量capacity時才會重新為vector分配存儲空間;小於當前容量則沒有影響

                   3、reserve方法對於vector元素大小沒有任何影響,不創建對象。

 

 

vector的初始的擴容方式代價太大,初始擴容效率低, 需要頻繁增長,不僅操作效率比較低,而且頻繁的向操作系統申請內存容易造成過多的內存碎片,

所以這個時候需要合理使用resize()和reserve()方法提高效率減少內存碎片的。

 

問題一:為什么非要以倍數的形式擴容,而不是以固定值的形式擴容。

倍數方式空間拷貝數據次數

假設vector初始的capacity=10,size=0,總共有100個元素,以2倍的形式增長。換算下來大概是需要進行5次擴容。這樣的話,相當於舊空間數據到原空間數據的拷貝有5次。

固定個數方式空間拷貝數據次數
假設vector初始的capacity=10,size=0,總共有100個元素,每次以10個元素個數的形式增長。(每次新增10個空間)。所以這次的擴容次數為 100/10 = 10次,也就是說,

插入100白個元素,需要擴容10次。

但是,如果n=1000的情況下, 以個數形式進行擴容就不能在為10了,否則拷貝空間次數將會太多

有的小伙伴要問:但是可以取100呀,想想,如果n=10的情況下,取100又不太合適,所以,以個數的形式來進行擴容顯然不符合所用n的取值。

 

問題二:為什么每次增長是1.5倍或者2倍形式,而不是3倍或者4倍形式增長。

如果以大於2倍的方式來進行擴容,下一次申請空間會大於之前申請所有空間的總和,這樣會導致之前的空間不能再被重復利用,這樣是很浪費空間的操作。

所以,如果擴容一般基於(1, 2] 之間進行擴容

舉個例子:

2倍擴容

1,2,4,8,16,32,...

1.5倍擴容

4,6,9,14,21,31.5,...

2倍擴容的時候,下一次擴容的內存總是比之前釋放過的內存和都大,即之前釋放過的內存在之后的擴容中不肯能被使用

1.5倍擴容的時候,以上面的例子,第6次擴容的時候,就可以重復利用之前釋放過的內存,31.5<4+6+9+14

所以為了提高內存利用率,減少擴容次數,每次擴容的倍數應該在[1.5,2]之間更合適

 

vector實現代碼

#ifndef _MY_VECTOR_HPP_
#define _MY_VECTOR_HPP_

template<typename T>
class MyVector
{

public:
    // 構造函數
    MyVector()
    {
        //這里默認數組大小為10
        //但是vector文件中默認構造的方式是0, 之后插入按照1 2  4  8  16 二倍擴容。注(GCC是二倍擴容,VS13是1.5倍擴容
        data = new T[10];
        _capacity = 10;
        _size = 0;
    }
    ~MyVector()
    {
        delete[] data;
    }
    //reserve只是保證vector的空間大小(_capacity)最少達到它的參數所指定的大小n。在區間[0, n)范圍內,預留了內存但是並未初始化
    void reserve(size_t n)
    {
        if(n>_capacity)
        {
            data = new T[n]; 
            _capacity = n;
        }
    }
    //向數組中插入元素
    void push_back(T e)
    {
        //如果 當前容量已經不夠了, 重新分配內存, 均攤復雜度O(1)
        if (_size == _capacity)
        {
            resize(2 * _capacity);
        }
        data[_size++] = e;
    }
    //刪除數組尾部的數據,同時動態調整數組大小,節約內存空間
    T pop_back()
    {
        T temp = data[_size];
        _size--;
        //如果 容量有多余的,釋放掉
        if (_size == _capacity / 4)
        {
            resize(_capacity / 2);
        }
        return temp;
    }
    //獲取當前數組中元素的個數
    size_t size()
    {
        return _size;
    }
    //判斷數組是否為空
    bool empty()
    {
        return _size==0?1:0;
    }
    //重載[]操作
    int &operator[](int i)
    {
        return data[i];
    }
    //獲取數組的容量大小
    size_t capacity()
    {
        return _capacity;
    }
    //清空數組,只會清空數組內的元素,不會改變數組的容量大小
    void clear()
    {
        _size=0;
    }
private:
    T *data;    //實際存儲數據的數組
    size_t _capacity; //容量
    size_t _size;  //實際元素個數
    //擴容
    void resize(int st)
    {
        //重新分配空間,在棧區新開辟內存,然后將以前數組的值賦給他,刪除以前的數組
        T *newData = new T[st];
        for (int i = 0; i < _size; i++)
        {
            newData[i] = data[i];
        }
        //實際使用時是清除數據,但不會釋放內存
        delete[] data;
        data = newData;
        _capacity = st;
    }
 
};

#endif //_MY_VECTOR_HPP_

 

測試代碼

 

#include <iostream>
#include<string>
#include "MyVector.hpp"
 
using namespace std;
int main()
{
    int size=11;
    MyVector<int> p;
    p.reserve(5);
    cout<<"size="<<p.size()<<"capacity="<<p.capacity()<<endl;
    for (int i = 0; i < size; i++)
    {
        p.push_back(i);
    }


    for(int i=0;i<size;i++)
    {
        cout<<p[i]<<' ';
    }
    cout<<endl;
    printf("size=%d     capcity=%d\n",p.size(),p.capacity());

    // MyVector<string>pp;

    // for(int i=0;i<5;i++)
    // {
    //     pp.push_back("str");
    // }
    // cout<<pp.size()<<"        "<<pp.capacity()<<endl;
    cout<<endl;
    return 0;
}

 


免責聲明!

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



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