• STL—vector


    前面介绍了STL对象的构造与析构以及内存的配置与释放,那具体的容器是怎么应用STL的空间配置器的呢?这篇先介绍STL的容器vector。

    vector的数据成员

    vector只有4个数据成员:3个迭代器、1个内存配置器。

    STL会为每个容器都设置一个内存配置器的成员,这里的内存配置器就是前面介绍的STL空间配置器,使用了统一对外接口的类simple_alloc,即STL会为每个容器都定义一个simple_alloc类的类型成员,通过该类型成员来为容器分配内存。

    vector的迭代器就是原始指针,只不过用了typedef将迭代器的类型变为了iterator,其实它就是T* 。vector的3个迭代器分别指向当前内存的起始地址(start)、最后一个数据的尾后地址(finish)、整个内存的最后地址(end_of_storage)。源码如下:

    class vector
    {
    public:
            typedef T                       value_type;
            typedef value_type*             pointer;
            typedef const value_type*       const_pointer;
            typedef value_type*             iterator;//vector迭代器就是一个原生指针
            typedef const value_type*       const_iterator;
            typedef value_type&             reference;
            typedef const value_type&       const_reference;
            typedef size_t                  size_type;
            typedef ptrdiff_t               difference_type;
            typedef MiniSTL::reverse_iterator<iterator>             reverse_iterator;
            typedef MiniSTL::reverse_iterator<const_iterator>       const_reverse_iterator;
            typedef alloc allocator_type;
    
            allocator_type get_allocator() const { return allocator_type(); }
    
    
    private:
            typedef simple_alloc<T, allocator_type> data_allocator;
            iterator        start;
            iterator        finish;
            iterator        end_of_storage;

    vector对象的构造

    vector有多种构造函数,但做的事情都一样,即先调用内存配置器去分配一块内存,然后对这块内存初始化,最后设置3个迭代器成员,让它们指向正确的位置。

    以我们平常使用最多的vector构造方法,如:vector<int> vec(10); 为例,其对应的构造函数如下,下面还将该构造过程涉及到的函数一并列出:

    构造函数vector(size_type n)调用fill_initializer函数,该函数会调用allocate_and_fill函数先去分配一块内存,然后进行初始化,由于这种构造方式未提供初始值,则按T类型的默认初始化进行初始化,然后剩下工作就是设置好start、finish、end_of_storage迭代器,工作便完成。

            void fill_initializer(size_type n, const T& value)
            {
                    start = allocate_and_fill(n, value);
                    finish = start + n;
                    end_of_storage = finish;
            }
    
            iterator allocate_and_fill(size_type n, const T& value)
            {
                    iterator result = data_allocator::allocate(n);
                    uninitialized_fill_n(result, n, value);
                    return result;
            }
    
    public:
            vector() : start(0), finish(0), end_of_storage(0) {}
            explicit vector(size_type n) { fill_initializer(n, T()); }

    vector空间的动态增长

    当我们向vector进行push_back时,若原内存空间未满,那很好,直接在后面添加一个元素即可。若原内存空间已满,则不能直接在其后面添加了,因为谁也不知道原空间后面的内存到底是魔鬼还是天使。

    所以,若原空间内存已满,继续往vector添加元素时,会先调用内存配置数据成员,分配一块新的内存,为了减少内存分配的次数,所以既然要分配了,那干脆就多分点,所以这块新内存的大小为原空间内存的2倍。

    接着,将原内存上的数据拷贝到新内存中--->析构原内存空间中的对象--->释放原内存空间--->重新设置迭代器。

    vector添加元素时,会导致内存空间的重新分配,所以会导致之前的迭代器都失效。

    vector要是经常这样动态增长会导致程序效率下降,所以可以调用vector的reserve函数预先分配一大块指定大小的内存,以减少内存重分配次数。

    对照下面源码分析。当finish和end_of_storage相等,则知道已经没有剩余空间了,push_back会调用insert_aux函数完成剩下全部工作。insert_aux函数调用allocate函数分配原空间2倍大小的新内存空间,调用uninitialized_copy函数将原内存中数据拷贝到新内存,接着调用destroy析构原空间中对象,调用deallocate()释放原内存空间,并重新设置start、finish、end_of_storage迭代器。如下:

    push_back : 

            void push_back(const T& val)
            {
                    if (finish != end_of_storage)
                    {
                            construct(finish, val);
                            ++finish;
                    }
                    else
                            insert_aux(end(), val);
            }

    insert_aux : 

    template<typename T>
    void
    vector<T>::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;  //2倍原空间大小
                    iterator new_start = data_allocator::allocate(len);
                    iterator new_finish = new_start;
    
                    try {
                            new_finish = uninitialized_copy(start, position, new_start);
                            construct(new_finish, x);
                            ++new_finish;
                            new_finish = uninitialized_copy(position, finish, new_finish);
                    }
                    catch(...) {
                            destroy(new_start, new_finish);
                            data_allocator::deallocate(new_start, len);
                            throw;
                    }
    
                    destroy(begin(), end()); // 析构原内存空间的对象
                    deallocate(start, end_of_storage - start); // 释放原内存空间
                    start = new_start;
                    finish = new_finish;
                    end_of_storage = new_start + len;
            }
    }
    View Code

    有关vector空间的动态增长的详细介绍可参考我的另一篇文章:http://www.cnblogs.com/zxiner/p/7197327.html

     

    (全文完)

    附:
    一款简易版STL的实现,项目地址:https://github.com/zinx2016/MiniSTL/tree/master/MiniSTL
  • 相关阅读:
    CSS3实现投影效果
    @font-face使用在线字体
    JS全局对象的属性
    const命令声明变量应注意的几点
    IDEA设置不区分大小写提示
    分布式ID生成-雪花算法
    项目Git分支管理规范
    IDEA使用Mybatis插件 MyBatisCodeHelper-Pro
    解决码云出现git@gitee.com: Permission denied (publickey).
    安装RabbitMQ,一直提示Erlang版本过低
  • 原文地址:https://www.cnblogs.com/zxiner/p/7197332.html
Copyright © 2020-2023  润新知