• SGI STL顺序容器 vector


    vector vs array

    在SGI STL中,vector和array都是数组容器,两种操作非常相似。区别在于:array是静态空间,一旦配置就不能改变;vector是动态空间,随着新元素加入,内部机制或自行扩充空间以容纳新元素。

    vector的迭代器

    vector维护的是一个连续线性空间,不论元素类型是什么,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector需要的操作:operator*,operator->,operatro++, operator--, operator+, operator-, operator+=, operator-=。而普通指针天生就具备这些操作。vector支持随机存取(operator[]),而普通指针也有这样的能力。因此,vector提供的迭代器是Random Access Iterators。

    // vector迭代器关联类型
    template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
    class vector : protected _Vector_base<_Tp, _Alloc>
    {
    ...
    public:
        // 定义vector的迭代器关联类型, 可用于iterator_traits萃取特性, 兼容STL架构
      typedef _Tp 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;
    ...
    };
    

    也就是说,如果客户端写出这样的代码:

    vector<int>::iterator ivite;
    vector<Shape>::iterator svite;
    

    ivite的类型其实是int,svite的类型是Shape

    vector的数据结构

    vector采用的数据结构就是一个线性连续空间。它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。

    vector通过基类_Vector_base的三个迭代器start、finish、end_of_storage,提供线性空间的首尾表示、大小、容量、空容器判断、随机访问运算符operator[]、最前端元素值、最后端元素值等功能。

    vector主要通过下面这部分,对外提供接口访问内部数据结构存储的元素:

    template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
    class vector : protected _Vector_base<_Tp, _Alloc>
    {
    ...
    public:
      iterator begin() { return _M_start; } // 线性空间起始位置对应迭代器
      const_iterator begin() const { return _M_start; } // 线性空间起始位置对应const迭代器
      iterator end() { return _M_finish; }  // 线性空间末尾位置对应迭代器
      const_iterator end() const { return _M_finish; }  // 线性空间末尾位置对应const迭代器
    
      reverse_iterator rbegin()
        { return reverse_iterator(end()); }
      const_reverse_iterator rbegin() const
        { return const_reverse_iterator(end()); }
      reverse_iterator rend()
        { return reverse_iterator(begin()); }
      const_reverse_iterator rend() const
        { return const_reverse_iterator(begin()); }
    
      size_type size() const     // 当前元素个数
        { return size_type(end() - begin()); }
      size_type max_size() const // 理论上最大能支持的元素个数
        { return size_type(-1) / sizeof(_Tp); }
      size_type capacity() const // 当前容量, 即线性空间最大能存放的元素个数
        { return size_type(_M_end_of_storage - begin()); }
      bool empty() const         // 判断是否包含元素
        { return begin() == end(); }
    
      reference operator[](size_type __n) { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素reference
      const_reference operator[](size_type __n) const { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素const reference
    
      reference front() { return *begin(); }    // 第一个元素reference
      const_reference front() const { return *begin(); }    // 第一个元素对应const reference
      reference back() { return *(end() - 1); } // 最后一个元素reference
      const_reference back() const { return *(end() - 1); } // 最后一个元素对应const reference 
    ...
    };
    

    vector对应存储结构示意图:

    size()是指当前实际存储元素的个数,capacity()是指底层用于存储的线性空间总大小,即容量。有size() <= capacity()成了。

    以vector iv(2,9)为例,经过下面操作:

    vector<int> iv(2,9);
    iv.push_back(1);
    iv.push_back(2);
    iv.push_back(3);
    iv.push_back(4);
    

    往vector 调用push_back()插入元素空间变化示意图:

    当容量不足时,如果继续插入元素,会导致vector扩容,扩容机制是这样的:
    1)当前容量为0,扩容后为1;
    2)当然容量>0,扩容为原来的2倍。

    擦除元素时,会导致finish指针移动,改变size()大小,但并不会导致end_of_storage指针变化,也就是不会导致容量变化。如果想要改变容量以节省空间,需要调用vector::shrink_to_fit(),减少容量以适应size()大小。不过shrink_to_fit()是C++11加入的内容,老版的SGI STL并没有该功能。
    简而言之,老版SGI STL无法缩减vector容量。

    vector的构造与内存管理

    vector的空间配置与释放

    vector的空间配置是通过基类_Vector_base完成的,所有细节都封装在基类内部。这样,vector就无需关心空间如何配置、释放的细节。

    vector基类_Vector_base:

    // vector基类, 负责维护vector底层数据结构的空间配置
    template <class _Tp, class _Alloc>
    class _Vector_base {
    public:
      typedef _Alloc allocator_type;
      allocator_type get_allocator() const { return allocator_type(); } // 构造一个临时allocator_type(空间配置器)对象
    
      _Vector_base(const _Alloc&)
        : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
      _Vector_base(size_t __n, const _Alloc&)
        : _M_start(0), _M_finish(0), _M_end_of_storage(0)
      {
        _M_start = _M_allocate(__n);              // 初始配置n byte空间
        _M_finish = _M_start;                     // 初始finish与start在同一位置
        _M_end_of_storage = _M_start + __n;       // 初始end_of_storage位置
      }
    
      ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); } // 释放整个线性空间
    
    protected:
      _Tp* _M_start;             // 线性内存起始位置
      _Tp* _M_finish;            // 线性内存使用的结束位置
      _Tp* _M_end_of_storage;    // 线性内存的终点位置
    
      typedef simple_alloc<_Tp, _Alloc> _M_data_allocator; // 一级空间配置/二级空间配置器的包装器
      _Tp* _M_allocate(size_t __n)                         // 为子类提供配置器的allocate()接口, 配置n byte空间
        { return _M_data_allocator::allocate(__n); }
      void _M_deallocate(_Tp* __p, size_t __n)             // 为子类提供配置器的deallocate()接口, 释放起始地址为p的n byte空间
        { _M_data_allocator::deallocate(__p, __n); }
    };
    

    关于simple_alloc有一点需要注意:在vector中,所有内存操作都是以元素个数为单位,但对于一级/二级空间配置器,内存都是以byte为单位,这其中,就是simple_alloc做了转换处理,将元素个数对应内存,转换成了byte为单位的内存。

    vector的构造

    站在客户端的角度,构造vector的方法:

    vector<int> ivec;           // vector内容为空
    vector<int> ivec2(10);      // size()为10, 内容0
    vector<int> ivec3(20, 5);   // 20个元素值为5
    vector<int> ivec4(ivec3);   // 20个元素值为5
    vector<int> ivec5({1,2,3}); // 3个元素, {1,2,3}. C++11内容, 初值列表初始化vector, 这里不列出
    vector<int> ivec6(ivec3.begin(), ivec3.end()); // 用迭代区间构造vector, 内容为20个元素值5
    

    vector的默认分配子是alloc(二级空间配置器),使用父类_Vector_base来屏蔽空间配置细节,方便以元素大小为配置单位。

        // vector 构造函数
        // 构造空vector, 对应客户端vector<int> ivec
      explicit vector(const allocator_type& __a = allocator_type())
        : _Base(__a) {}
    
        // 构造大小为n byte, 所有值为value的vector, 对应客户端vector<int> ivec3(20, 5)
      vector(size_type __n, const _Tp& __value,
             const allocator_type& __a = allocator_type())
        : _Base(__n, __a)
        { _M_finish = uninitialized_fill_n(_M_start, __n, __value); } // 用value填充未初始化内存区段(start, n)
    
        // 构造大小为n byte的vector, 对应客户端vector<int> ivec2(10)
      explicit vector(size_type __n)
        : _Base(__n, allocator_type())
        { _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); } // 这里Tp是int, 用int()(默认值0)填充未初始化内存区段(start, n)
    
        // 拷贝构造的vector, 对应客户端vector<int> ivec4(ivec3)
      vector(const vector<_Tp, _Alloc>& __x)
        : _Base(__x.size(), __x.get_allocator())
        { _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); } // 将x对应的源迭代区间所有元素拷贝到未初始化内存段
    
        // 用迭代区间[first, last)构造vector
      vector(const _Tp* __first, const _Tp* __last,
             const allocator_type& __a = allocator_type())
        : _Base(__last - __first, __a)
        { _M_finish = uninitialized_copy(__first, __last, _M_start); } // 将源迭代区间所有内容拷贝到start起始的目的区间
    

    vector的析构

    vector的析构函数很简单,调用全局destroy() 释放线性内存空间。

    // 释放迭代器区间[first, last), 如果迭代器指向基本类型, 什么也不做; 如果迭代器指向class 类型, 就先逐个析构
    ~vector() { destroy(_M_start, _M_finish); }
    
    // 注意vector析构之后, 会接着析构基类. 因此即使vector什么也没做, 基类会回收线性内存空间
    // 析构基类会先回收内存段(start, end_of_storage - start)
    // 空间配置那一章提到过, 如果是二级配置器, 内存>128byte, 会直接返还给OS; 如果内存<=128byte, 会加入到某个合适的free list, 留作备用
    ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }
    

    指定数组初始大小reserve(), resize()

    有没有办法让vector一开始就保留指定大小的线性空间,而不是慢慢动态增长到?
    答案是有的,可以用vector::reserve()。

     // 让vector容量 >= n 个元素
      void reserve(size_type __n) {
        if (capacity() < __n) { // 只有当前容量 < n时, 才需要重新配置空间
          const size_type __old_size = size(); // 已经装了元素个数
          iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish); // 配置n个元素新空间, 并将[start, finish)元素拷贝到新空间
          destroy(_M_start, _M_finish); // 调用全局destroy()销毁[start, finish)上的元素. 对于基本类型, 什么也不做; 对于class类型, 析构对象
          _M_deallocate(_M_start, _M_end_of_storage - _M_start); // 调用基类的deallocate() 释放线性空间
            // 重新配置start, finish, end_of_storage 管理线性空间
          _M_start = __tmp;
          _M_finish = __tmp + __old_size;
          _M_end_of_storage = _M_start + __n;
        }
      }
    
        // 配置n个元素新空间, 并将[start, finish)元素拷贝到新空间
        // 遵循 "commit or rollback"规则
      iterator _M_allocate_and_copy(size_type __n, const_iterator __first,
                                                   const_iterator __last)
      {
        iterator __result = _M_allocate(__n);
        __STL_TRY {
          uninitialized_copy(__first, __last, __result);
          return __result;
        }
        __STL_UNWIND(_M_deallocate(__result, __n)); // commit or rollback精髓: 发生异常时, 释放线性空间
      }
    

    还有一个跟reserve()类似的接口resize(),它是负责什么的呢?
    resize()用于指定vector的新size(),改变线性空间的finsih指针,但不改变start和end_of_storage指针。也就是说,resize()并不会影响capacity()大小。为满足最终size()为新指定值,如果当前元素超过指定的size,就擦除多余部分;如果当前元素占用size()不足,就会用指定值插入vector。

        // 如果size()超过new_size, 就擦除多余的; 如果不超过, 就在末尾插入指定元素x. 最终目标是让size()等于new_size
      void resize(size_type __new_size, const _Tp& __x) {
        if (__new_size < size()) // 新size < 现有size()时, 说明原来的size较大, 需要擦除一部分
          erase(begin() + __new_size, end()); // 擦除多余空间元素 [begin() + new_size, end())
        else
          insert(end(), __new_size - size(), __x);
      }
      void resize(size_type __new_size) { resize(__new_size, _Tp()); }
    
        // 擦除指定位置position的元素, 后面的(position~末尾)元素整体向前移动
      iterator erase(iterator __position) {
        if (__position + 1 != end()) // 要删除的元素不是末尾元素
          copy(__position + 1, _M_finish, __position); // 将擦除位置后的区间[position+1, finish)元素, 拷贝到position起始处
        --_M_finish;  // 因为只擦除一个元素, finish向前移动1
        destroy(_M_finish); // finish指向的就是要删除的那个元素, 析构之, 但不释放空间(尚未归还给配置器)
        return __position; // 返回销毁元素的位置
      }
    
        // 擦除迭代区间[first, last), 后面的元素整体向前移动
      iterator erase(iterator __first, iterator __last) {
        iterator __i = copy(__last, _M_finish, __first); // 将擦除区间后的区间[last, finish)元素, 拷贝到first起始处
        destroy(__i, _M_finish); // 析构[i, finish)对象, 但并没有释放空间
        _M_finish = _M_finish - (__last - __first); // 先前移动finish指针
        return __first; // 返回销毁后的起始位置
      }
    

    vector的查询

    尺寸size()与容量capacity()

    size()用于查询vector当前元素个数,capacity()用于查询当前vector线性空间最多容纳元素个数。

    size()和capacity()代码很简单,利用了线性空间的3个指针(start, finish, end_of_storage)

      size_type size() const     // 当前元素个数
        { return size_type(end() - begin()); }
    
      size_type capacity() const // 当前容量, 即线性空间最大能存放的元素个数
        { return size_type(_M_end_of_storage - begin()); }
    

    迭代器访问元素

    vector支持迭代器访问元素,提供iterator进行正向顺序访问,reverse_iterator进行反向访问,以及它们的const版本。

    所谓正向迭代器,是指从(地址空间)起始到末尾,从小下标到大下标的顺序访问;
    所谓反向迭代器,是指从(地址空间)末尾到开始,从大下标到小下标的顺序访问。

        // 正向迭代器
      iterator begin() { return _M_start; } // 线性空间起始位置对应迭代器
      const_iterator begin() const { return _M_start; } // 线性空间起始位置对应const迭代器
      iterator end() { return _M_finish; }  // 线性空间末尾位置对应迭代器
      const_iterator end() const { return _M_finish; }  // 线性空间末尾位置对应const迭代器
    
        // 反向迭代器
      reverse_iterator rbegin()
        { return reverse_iterator(end()); }
      const_reverse_iterator rbegin() const
        { return const_reverse_iterator(end()); }
      reverse_iterator rend()
        { return reverse_iterator(begin()); }
      const_reverse_iterator rend() const
        { return const_reverse_iterator(begin()); }
    

    引用访问元素

    通过两类元素访问方式:
    1)通过front(),back()直接访问第一个、最后一个元素,返回的是元素引用;
    2)通过operator[] 随机访问指定下标的元素,返回的是元素引用。

      reference operator[](size_type __n) { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素reference
      const_reference operator[](size_type __n) const { return *(begin() + __n); } // 随机访问第n-1个元素 (从0开始计), 返回元素const reference
    
      reference front() { return *begin(); }    // 第一个元素reference
      const_reference front() const { return *begin(); }    // 第一个元素对应const reference
      reference back() { return *(end() - 1); } // 最后一个元素reference
      const_reference back() const { return *(end() - 1); } // 最后一个元素对应const reference
    

    vector元素操作

    尾端插入元素push_back

    push_back是在线性空间当前已经使用段的末尾,添加一个新对象,同时右移finish指针。
    根据插入对象是否有参数,有两个版本push_back:1)以值x构造的Tp对象;2)无参构造Tp对象,即不需要初值x用于构造Tp对象。

      // 在尾端插入以x构造的对象(调用Tp(x)), 会导致size加1. 容量不够时, 需要扩容
      void push_back(const _Tp& __x) {
        if (_M_finish != _M_end_of_storage) { // 备用空间足够, 不需要扩容
          construct(_M_finish, __x); // 在尾端finish所指位置用x构造对象Tp()
          ++_M_finish;               // 尾端标记右移一格
        }
        else
          _M_insert_aux(end(), __x); // 在指定位置end() 插入以x构造的对象Tp()
      }
    
      // 在尾端插入空对象(调用Tp()), 会导致size加1. 容量不够时, 需要扩容
      void push_back() {
        if (_M_finish != _M_end_of_storage) { // 备用空间足够, 不需要扩容
          construct(_M_finish); // 在尾端finish所指位置构造对象Tp()
          ++_M_finish;          // 尾端标记右移一格
        }
        else
          _M_insert_aux(end()); // 在指定位置end() 插入新构造对象Tp()
      }
    

    指定位置插入元素insert

    insert也是用于插入元素,跟push_back的区别在于insert是在指定位置(迭代器)插入新元素。
    也就是说,push_back(x)相当于insert(end(), x)。

    //-----------------------------
    // insert单个对象
      // 在指定位置position插入单个对象Tp(x)
      iterator insert(iterator __position, const _Tp& __x) {
        size_type __n = __position - begin();
        if (_M_finish != _M_end_of_storage && __position == end()) {
          construct(_M_finish, __x);
          ++_M_finish;
        }
        else
          _M_insert_aux(__position, __x);
        return begin() + __n;
      }
    
      // 在指定位置position插入单个对象Tp()
      iterator insert(iterator __position) {
        size_type __n = __position - begin();
        if (_M_finish != _M_end_of_storage && __position == end()) {
          construct(_M_finish);
          ++_M_finish;
        }
        else
          _M_insert_aux(__position);
        return begin() + __n;
      }
    
    //-----------------------------
    // insertd多个对象
    
      // 在指定位置position插入源区间[first, last)所有对象
      void insert(iterator __position,
                  const_iterator __first, const_iterator __last);
    
      // 在指定区间{pos, n}插入构造的Tp(x)对象
      void insert (iterator __pos, size_type __n, const _Tp& __x)
        { _M_fill_insert(__pos, __n, __x); }
    

    insert不仅支持插入单个元素,还支持插入多个元素。通过指定 起始位置 + 源迭代器区间,或者指定 起始位置+元素个数+元素值,就能在指定位置(区间)上一次插入多个元素。

    insert批量插入代码较为复杂,我们直接图解:

    弹出最后一个元素pop_back

    pop_back()弹出vector的最后一个元素,会让size减-1,但不会影响capacity。

      // 将尾端元素拿掉, 并调整大小
      void pop_back() {
        --_M_finish; // 尾端标记前移一格, 表示将放弃尾端元素
        destroy(_M_finish); // 全局函数, 如果finish所指元素是trivial type, 什么也不做; 如果是class type, 析构finish所指对象
      }
    

    擦除元素earse

    擦除元素分为两类:1)擦除指定位置元素;2)擦除指定区间所有元素。

    擦除元素后,后面的所有元素都会往前移动,以补充空位。另外,移动后的空位置,如果不是trivial type,需要调用其析构函数析构对象;如果是trivial type,可以什么也不做。
    擦除元素会影响size,因此需要移动尾端标记finish,移动格数取决于擦除的元素个数。但不会影响capacity。

      // 擦除指定位置元素
      iterator erase(iterator __position) {
        if (__position + 1 != end()) // position所指并非最后一个元素, 说明后续还有元素, 需要往前移动补充擦除的空位
          copy(__position + 1, _M_finish, __position); // 将元素从[position+1, finish)拷贝到[position, ...), 也就是position+1之后元素往前移动1格
        --_M_finish; // 尾端标记前移一格
        destroy(_M_finish); // 析构尾端元素对象
        return __position;  // 返回擦除元素的位置对应迭代器
      }
    
      // 擦除指定区间[first, last)的所有元素
      iterator erase(iterator __first, iterator __last) {
        iterator __i = copy(__last, _M_finish, __first); // 将源区间[last, finish)所有元素前移, 拷贝到目标区间[first, ...), 返回目标区间结尾位置
        destroy(__i, _M_finish); // 销毁拷贝后残余的对象[i, finish)
        _M_finish = _M_finish - (__last - __first); // 尾端标记finish前移(last-first)格, 即已擦除元素个数
        return __first;
      }
    

    注意:vector没有像list那样的remove移除元素接口。

    清除所有元素clear

      // 擦除所有元素, 如果是non-trivial type, 会对每个元素调用析构函数
      void clear() { erase(begin(), end()); }
    

    vector比较操作

    SGI STL支持多种vector判断操作,不过并不是作为vector member function,而是作为global function。这样做,可以不用破坏容器的封装性。
    根据《Effective C++》Item19,operator>可以转换为由operator<实现,operator!=可以转换为由operator<实现。因此,只需要实现operator==和operator<即可。
    参考:https://www.cnblogs.com/fortunely/p/15715265.html

    operator==

    operator==用于比较两个vector是否相等。

    // 比较两个vector的所有元素是否相等, 相等性通过equal判断
    // 相等的两个vector, 要求长度相等, 并且所有元素都相等(通过元素的 "!=" 判断)
    template <class _Tp, class _Alloc>
    inline bool
    operator==(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
    {
      return __x.size() == __y.size() &&
             equal(__x.begin(), __x.end(), __y.begin());
    }
    

    operator<

    operator<用于判断第一个vector是否小于第二个。

    // 字典序比较两个vector的所有元素, 判断第一个vector是否小于第二个
    template <class _Tp, class _Alloc>
    inline bool
    operator<(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
    {
      return lexicographical_compare(__x.begin(), __x.end(),
                                     __y.begin(), __y.end());
    }
    

    vector特殊操作swap

    swap()用于交换2个vector,代价很小,只需要交换底层线性空间的3个指针。

      // 与x交换当前vector交换, 只需要交换维护底层线性空间3个指针即可, 无需将vector所有元素交换或拷贝
      void swap(vector<_Tp, _Alloc>& __x) {
        __STD::swap(_M_start, __x._M_start);
        __STD::swap(_M_finish, __x._M_finish);
        __STD::swap(_M_end_of_storage, __x._M_end_of_storage);
      }
    
  • 相关阅读:
    java enum类
    mvn filter autoconfig 产生自动配置
    Spring与Quartz的整合实现定时任务调度 以及crontab的用法
    网络广告术语CPC、CPM和CTR的含义和关系
    spring mvc3的注解@ResponseBody 自动返回jason
    Google Guava14.0 瓜娃学习笔记
    java中的各个数据结构区别
    org.apache.http.client.HttpClient; HttpClient 4.3超时设置
    maven test 运行 指定类或方法 打包 mvn clean assembly:assembly
    为什么要做url encode
  • 原文地址:https://www.cnblogs.com/fortunely/p/16245010.html
Copyright © 2020-2023  润新知