《STL源码分析》Vector的实现

一、vector概述

        vector容器是一个动态数组,而arrary是一个静态数组。这里的静态和动态的区分,是从内存分配上面来看的。

int n = 10;
std::vector<int> vec(n);
std::array<int, n>	arr = { 0 };  //编译出错

        因为vector是动态分配,所以vector的大小其实是在运行时确定的所以可以时一个变量,而arrar则是一个静态数组,其大小是在编译时确定的,所以不能用变量去指定其大小。

        另外注意const修饰的变量,因为const修饰的变量初始化是在编译期间完成的,所以下面的例子可以通过。

const int n = 10;
std::array<int, n>	arr = { 0 };

二、vector的数据结构

        vector的底层数据结构是一个动态数组。其内存示意图如下:

通过 start,finish,end_of_storage 三个迭代器 可以很简单的提供大部分功能:大小、是否为空、[ ]、前端元素、后端元素。

class vector{
...
protected:
    iterator start; // 指向使用连续空间的第一个元素的位置
    iterator finish;// 指向使用连续空间的最后一个元素的下一个位置
    iterator end_of_storage;// 指向整个连续空间的尾端
...
};

三、vector的迭代器

因为 vector 维护的是一个连续线性空间,因此普通指针可以作为vector的迭代器,满足迭代器的必要条件。

四、vector 基本操作

1.push_back

void push_back(const T& x)
{
    if(finish != end_of_storage) // 说明还有备用空间
    {
        construct(finish, x); // constuct(T* p, const T& x); 构造函数
        ++finish;
    }
    else
        insert_aux(end(), x);// vector 成员函数
}
 
// 实现 空间资源的重新分配,得到更大的空间
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x)
{
    if(finish != end_of_storage) // 还有备用空间 - 上面明明判断过了,这边加上去的理由应该是 防止其他情况 处理吧
    {
        construct(finish, *(finish - 1));
        ++finish;
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    }
    else
    {
        const size_type old_size = size();
        const size_type len = old_size != 0 ? 2 * old_size : 1;// 这里实现了 新空间 大小翻倍
         
        iterator new_start = data_allocator::allocate(len); // 申请新 空间
        iterator new_finish = new_start;
        try
        {
            // 将原本的内容拷贝 到新的 vector
            new_finish  = uninitialized_copy(start, position, new_start); // 将 [start,position) 的内容 拷贝 到 new_start为起点的 vector中
            // 为新元素 设定 初值 x
            construct(new_finish, x);
            ++new_finish;
            // 然后 再把 后半段 也拷过来
            new_finish  = uninitialized_copy(position, finish, new_finish); // 将 [position,finish) 的内容 拷贝 到 new_finish为起点的 vector中
        }
        catch(...)
        {
            // 执行失败则滚回
            destroy(nwe_start, new_finish); // 析构
            data_allocator::deallocate(new_start, len); // 释放申请的空间
            throw;
        }
         
        // 析构并释放原来的vector
        destroy(begin(), end());
        dealloacte(); // 成员函数  
 
        // 将迭代器指向新的vector
        start = new_start;
        finish  = new_finish;
        end_of_storage = new_start + len;
    }
}

2.pop_back

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

        在开辟空间时之所以是2倍扩容,可能是考虑到如果开辟的过小,则拓展的容量还是不足以放下 新加入的元素还需要再次扩容,如果开辟的过大,则会造成空间的浪费,而且申请一大块的内存本身也比较困难。

3.erase

iterator erase(iterator first, iterator last)
{
    iterator i = copy(last,finish,first);// 将[last,finish)的元素拷贝到 first为起点的内存空间,返回 拷贝最后一个元素的位置
    destroy(i, finish); // 销毁 i 到 finish 的元素
    finish = finish - (last - first);// 更新finish
    return first;
}

iterator erase(iterator position)
{
    if(positio + 1 != end())
    {
        copy(position+1,finish,position);
    }
    --finish;
    destroy(finish);
    return position;
}

4.clear

void clear()
{
    erase(begin(), end());
}

5.insert

emplate <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
    if(n!=0)
    {
        if(size_type(end_of_storage - finish) >=n)  // 场景1)
        {
            T x_copy = x;
            const size_type elems_after = finish - position; //position后面的元素个数
            iterator old_finish = finish;
            if(elems_after > n) // 场景1) 中的 场景A.
            {
                uninitialized_copy(finish - n, finish, finish); // A.a
                finish += n;
                copy_backward(position, old_finish - n, old_finish); // A.c
                fill(position, position + n, x_copy); // A.d
            }
            else // 场景1) 中的 场景B.
            {
                uninitialized_fill_n(finish, n - elems_after, x_copy); // A.a
                finish += n - elems_after;
                uninitialized_copy(position, old_finish, finish); // A.c
                finish += elems_after;
                fill(position, old_finish, x_copy); // A.e
            }
        }
        else // 场景2)
        {
            const size_type old_size = size();
            const size_type len  = old_size + max(old_size, n); // 备用空间的翻倍
            iterator new_start = data_allocator::allocate(len); // 分配空间
            iterator new_finish = new_start;
            try // 异常情况
            {
                new_finish = uninitialized_copy(start, position, new_start); // 2).b
                new_finish = uninitialized_fill_n(new_finish, n, x); // 2).c
                new_finish = uninitialized_copy(position, finish, new_finish); // 2).d
            }
            catch(...)
            {
                destroy(new_start, new_finish);
                data_allocator::deallocate(new_start, len);
                throw;
            }
            // 2).e
            destroy(start, finish);
            deallocate();
 
            // 更新游标
            start = new_satrt;
            finish = new_finish;
            end_of_storage = new_start + len;
        }
    }
}

小结

在使用vector的时,要考虑其底层实现是一个动态数组,有3个意义:1)这是一块连续的内存,支持随机访问。2)在扩容时,大小扩容到原来的二倍,并且是在系统中(虚拟内存空间)另外分配二倍的内存,然后将原来的内容拷贝到新的内存,并释放旧内存。3)迭代器失效问题,当插入或者删除时会使当前及之后的迭代器失效,发生扩容时所有迭代器均失效。

五、sort算法分析

        因为vector的内存是一块连续的空间,支持随机访问,所以可以说使用sort进行快排。

本博客用于记录学习笔记,以整理本人学习原博客时的疑问。

原博客链接《STL 源码剖析》 vector 实现原理 - Linese - 博客园 (cnblogs.com)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值