STL之std::vector源码剖析

vector容器概述

// highlighted block
1.	模板类vector类似于string类,也是一种动态数组。
2.	它可以在运行阶段的设置vector对象的长度,可在末尾附加新数据,还可以在中间插入新数据。
3.	它是使用new创建动态数组的替代品。
	实际上vector类确实使用newdelete来管理内存,但这种工作是自动完成的。

一、vector对比array容器

  1. 数组类是固定大小的序列容器,它们包含以严格线性序列排序的特定数量的元素。数组类具有固定大小,并且不通过分配器管理其元素的分配,它们是封装固定大小元素数组的聚合类型。

  2. vector和array均是数组的替代品

  3. vector类的功能比数组强大,使用的是自由存储空间,但付出的代价是效率稍低

  4. 如果需要的是长度固定的数组,使用数组是更加的选择,但代价是不那么方便和安全。有鉴于此,C++11新增了模板类array,它也是位于名称空间std中。

  5. Array与数组一样,array对象的**(容器大小)长度也是固定的**,也使用栈静态内存分配),而不是自由存储区,因此其效率与数组相同但更方便更安全

二、底层技术实现

1.空间的动态扩展

vector的数据安排以及操作方式,与array非常相似。
两者的唯一区别在于空间的运用的灵活性

array是静态空间,一旦配置了就不能改变;要换个大(或小)一点的房子,可以,一切琐细都得由客户端自己来:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。

vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块头的array了,我们可以安心使用array,吃多少用多少。

vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。一旦vector的旧有空间满载,如果客户端每新增一个元素,vector的内部只是扩充一个元素的空间,实为不智。因为所谓扩充空间(不论多大),一如稍早所说,是” 配置新空间/数据移动/释还旧空间 “的大工程,时间成本很高,应该加入某种未雨绸缪的考虑。

另外,由于 vector维护的是一个连续线性空间,所以vector支持随机存取 。

vector动态增加大小时,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此, 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 。这是易犯的一个错误,务需小心。

2.迭代器相关

Iterators迭代器的作用是遍历array数组类和vector中的元素。
可以通过begin/end()、rbegin/rend()、cbegin/cend()、crbegin/crend()等函数进行访问。

3.vector容器的内存自增长

与其他容器不同,其内存空间只会增长,不会减小。先来看看"C++ Primer"中怎么说:为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都紧挨着前一个元素存储。设想一下,当vector添加一个元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间,这样性能难以接受。因此STL实现者在对vector进行内存分配时,其实际分配的容量要比当前所需的空间多一些。就是说,vector容器预留了一些额外的存储区,用于存放新添加的元素,这样就不必为每个新元素重新分配整个容器的内存空间。

关于vector的内存空间,有两个函数需要注意:
size()成员指当前拥有的元素个数;
capacity()成员指当前(容器必须分配新存储空间之前)可以存储的元素个数。
reserve()成员可以用来控制容器的预留空间。

vector另外一个特性在于它的内存空间会自增长,每当vector容器不得不分配新的存储空间时,会以加倍当前容量的分配策略实现重新分配。例如,

当前capacity为50,当添加第51个元素时,预留空间不够用了,vector容器会重新分配大小为100的内存空间,作为新连续存储的位置。

4.vector内存释放

由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。

所有内存空间是在vector析构时候才能被系统回收。

empty()用来检测容器是否为空的,clear()可以清空所有元素。

但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。

如果需要空间动态缩小,可以考虑使用deque。

如果非vector不可,可以用swap()来帮助你释放内存。 具体方法如下:

vector<int>().swap(nums); //或者nums.swap(vector<int> ())

{ 
    std::vector<int> tmp = nums;  
    nums.swap(tmp); 
}

swap()是交换函数,使vector离开其自身的作用域,从而强制释放vector所占的内存空间.
总而言之,释放vector内存最简单的方法是vector.swap(nums)。当时如果nums是一个类的成员,不能把vector.swap(nums)写进类的析构函数中,否则会导致double free or corruption (fasttop)的错误,原因可能是重复释放内存。

5.利用vector释放指针

如果vector中存放的是指针,那么当vector销毁时,这些指针指向的对象不会被销毁,那么内存就不会被释放。如下面这种情况,vector中的元素时由new操作动态申请出来的对象指针:

vector<void *> v;
for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++) 
    if (NULL != *it) 
    {
        delete *it; 
        *it = NULL;
    }
v.clear();

vector是线性容器,它的元素严格的按照线性序列排序,和动态数组很相似,和数组一样,它的元素存储在一块连续的存储空间中,这也意味着我们不仅可以使用迭代器(iterator)访问元素,还可以使用指针的偏移方式访问,和常规数组不一样的是,vector能够自动存储元素,可以自动增长或缩小存储空间。

三、源码摘要

1.默认构造出的vector不分配内存空间

2 向容器尾追加一个元素, 可能导致内存重新分配

//                          push_back(const T& x)
//                                   |
//                                   |---------------- 容量已满?
//                                   |
//               ----------------------------
//           No  |                          |  Yes
//               |                          |
//               ↓                          ↓
//      construct(finish, x);       insert_aux(end(), x);
//      ++finish;                           |
//                                          |------ 内存不足, 重新分配
//                                          |       大小为原来的2倍
//      new_finish = data_allocator::allocate(len);       <stl_alloc.h>
//      uninitialized_copy(start, position, new_start);   <stl_uninitialized.h>
//      construct(new_finish, x);                         <stl_construct.h>
//      ++new_finish;
//      uninitialized_copy(position, finish, new_finish); <stl_uninitialized.h>

3.内存满足条件则直接追加元素, 否则需要重新分配内存空间

4 . 在指定位置插入元素

//                   insert(iterator position, const T& x)
//                                   |
//                                   |------------ 容量是否足够 && 是否是end()?
//                                   |
//               -------------------------------------------
//            No |                                         | Yes
//               |                                         |
//               ↓                                         ↓
//    insert_aux(position, x);                  construct(finish, x);
//               |                              ++finish;
//               |-------- 容量是否够用?
//               |
//        --------------------------------------------------
//    Yes |                                                | No
//        |                                                |
//        ↓                                                |
// construct(finish, *(finish - 1));                       |
// ++finish;                                               |
// T x_copy = x;                                           |
// copy_backward(position, finish - 2, finish - 1);        |
// *position = x_copy;                                     |
//                                                         ↓
// data_allocator::allocate(len);                       <stl_alloc.h>
// uninitialized_copy(start, position, new_start);      <stl_uninitialized.h>
// construct(new_finish, x);                            <stl_construct.h>
// ++new_finish;
// uninitialized_copy(position, finish, new_finish);    <stl_uninitialized.h>
// destroy(begin(), end());                             <stl_construct.h>
// deallocate();

5.最重要的是resize()这个函数

简单来说就是,每个动态数组都分配有一定容量,当存储的数据达到容量的上限的时候,就重新分配内存。(重新开辟了一个数组,把之前的值放到复制到新开辟的数组中来。

// resize()
#ifndef MYVECTOR_H
#define MYVECTOR_H
class MyVector
{
private:
    int *data;    //实际存储数据的数组
    int capacity; //容量
    int _size;
    void resize(int st)
    {
        //重新分配空间,在堆区新开辟内存,然后将以前数组的值赋给他,删除以前的数组
        int *newData = new int[st];
        for (int i = 0; i < _size; i++)
        {
            newData[i] = data[i];
        }
        delete[] data;
        data = newData;
        capacity = st;
    }
 
public:
    // 构造函数
    MyVector()
    {
        data = new int[10];
        capacity = 10;
        _size = 0;
    }
    ~MyVector()
    {
        delete[] data;
    }
    MyVector(int st)
    {
        data = new int[st * 2]; //初始化的时候 容量分配为2倍size,实际vector不知是不是这样做的
        capacity = st * 2;
        _size = st;
    }
    void push_back(int e)
    {
        //如果 当前容量已经不够了, 重新分配内存, 均摊复杂度O(1)
        if (_size == capacity)
        {
            resize(2 * capacity);
        }
        data[_size++] = e;
    }
    int pop_back()
    {
        int temp = data[_size];
        _size--;
        //如果 容量有多余的,释放掉
        if (_size == capacity / 4)
        {
            resize(capacity / 2);
        }
        return temp;
    }
    int size()
    {
        return _size;
    }
    //重载[]操作
    int &operator[](int i)
    {
        return data[i];
    }
};
#endif
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值