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;
}

 

posted @ 2020-05-08 10:02  知道了呀~  阅读(6454)  评论(0编辑  收藏  举报