vector

对于容器vector,他的存储原理就是一个数组,对于数组空间申请的话,那么就是基于https://welsey.blog.csdn.net/article/details/112189216中的空间分配器去进行空间申请;

vector核心成员数据:

	pointer _M_start;             // 开始存储元素的空间地址
    pointer _M_finish;            // 最后一个存储元素的之后的一个位置的空间地址
	pointer _M_end_of_storage;    // 整个存储空间的后一个位置的空间地址

这里有必要提一下这三个指针,另外还有就是整个STL的区间习惯,整个STL一般上涉及到区间的[first, last)都是左闭右开的形式;另外就是STL的右开就是只是获取对应的地址,而不获取对应地址的数据,这是c++允许的;

另外还有vector的迭代器,容器的迭代器的话,一般而言,迭代器的移动与取值都与容器本身的数据结构是息息相关的,因此对于STL的每个容器,都定义了一个属于自己的迭代器,vector的迭代器就是normal_iterator<_Tp*>,这里的normal_iterator迭代器操作上来看还是个指针,只不过添加了一些STL迭代器的一些需要的信息;关于迭代器提取器的话iterator_traits,他会提取迭代器中需要的属性,而将指针转换成normal_iterator<_Tp*>形式的意义也在于此;

      typedef typename __traits_type::iterator_category iterator_category;
      typedef typename __traits_type::value_type  	value_type;
      typedef typename __traits_type::difference_type 	difference_type;
      typedef typename __traits_type::reference 	reference;
      typedef typename __traits_type::pointer   	pointer;

那么接下来根据增、删、修改访问这三个角度对其进行解析:

增:

容器增加一个元素,STL一般的接口就是insert接口,该接口的话代码如下

template<typename _Tp, typename _Alloc>
    typename vector<_Tp, _Alloc>::iterator
    vector<_Tp, _Alloc>::
    insert(iterator __position, const value_type& __x)
    {
      const size_type __n = __position - begin();
      if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage && __position == end())
	{
	  _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
	  ++this->_M_impl._M_finish;
	}
      else
	{
#if __cplusplus >= 201103L
	  if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
	    {
	      _Tp __x_copy = __x;
	      _M_insert_aux(__position, std::move(__x_copy));
	    }
	  else
#endif
	    _M_insert_aux(__position, __x);
	}
      return iterator(this->_M_impl._M_start + __n);
    }

首先呢,vector对于数据的存储时一直保持从开头到end()连续的状态的,因此插入元素位置会影响到具体操作:

  1. 对于留有存储空间并且插入位置在end()位置的话,直接在end()位置进行元素添加的话:这种情况直接在end()位置进行输入插入并移动原本容器的end()向后一个单元即可;
  2. 对于留有存储空间但是插入元素不在end()位置的情况,需要将插入位置[insertPos, last)整体后移一位,然后将元素插入;
  3. 对于没有存储空间的情况下,就需要另外申请一段更大的空间,然后将原本的数据复制到新的空间中,当然,这里有个技巧就是复制的时候就将待插入位置留出来,然后进行两个分区间复制即可;当然,还要最后的destory原存储空间工作;

删:

删除元素的话一般是调用erase接口,删除一个元素,由于容器存储空间足够,因此不必考虑空间扩展,当然,此处我认为也可以加入适当检测,当元素少于容器容量1/4就进行容器缩减,但是STL中并没有这样的操作,具体代码如下:

  template<typename _Tp, typename _Alloc>
    typename vector<_Tp, _Alloc>::iterator
    vector<_Tp, _Alloc>::erase(iterator __position)
    {
          /* 如果删除的元素位置不在vector尾部,就需要进行元素移动 */
        if (__position + 1 != end())    _GLIBCXX_MOVE3(__position + 1, end(), __position);
        --this->_M_impl._M_finish;
        /* 只是进行空间析构,并没有delete空间 */
        _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
        return __position;
    }

很简单,就是直接移动区间,然后进行一次对象析构即可;

修改:

对于容器元素的操作一般通过迭代器去操作,当然,也可以重载后的operator[]去操作,operator[]的话,代码如下:

      reference
      operator[](size_type __n)
      { return *(this->_M_impl._M_start + __n); }

原理就是直接根据指针移动到需要的index索引位置,返回对应数据的引用,这样就可以对容器对应位置的数据进行读写;

当然,通过迭代器也可以实现这样的目的,说道迭代器,vector的迭代器本质上就是个指针套了一个normal_iterator迭代器模板类的壳子,以下就是该迭代器的源码:

template<typename _Iterator, typename _Container>
    class __normal_iterator
    {
    protected:
      _Iterator _M_current;

      /**   迭代器属性,在该类型中定义了,需要再进行一次typedef扩展出来,才能在本结构中任意使用这些typedef
            iterator_traits对指针进行了偏特化,定义了对应的迭代器属性,而一般的迭代器属性直接基于模板参数_Iterator中的属性进行提取。*/
      typedef iterator_traits<_Iterator>		__traits_type;

    public:
      typedef _Iterator					iterator_type;
      typedef typename __traits_type::iterator_category iterator_category;
      typedef typename __traits_type::value_type  	value_type;
      typedef typename __traits_type::difference_type 	difference_type;
      typedef typename __traits_type::reference 	reference;
      typedef typename __traits_type::pointer   	pointer;

      _GLIBCXX_CONSTEXPR __normal_iterator() : _M_current(_Iterator()) { }

      explicit
      __normal_iterator(const _Iterator& __i) : _M_current(__i) { }

      // Allow iterator to const_iterator conversion
      template<typename _Iter>
        __normal_iterator(const __normal_iterator<_Iter,
			  typename __enable_if<
      	       (std::__are_same<_Iter, typename _Container::pointer>::__value),
		      _Container>::__type>& __i)
        : _M_current(__i.base()) { }

      // Forward iterator requirements
      /** normal_iterator的operator*取值返回的左值引用,而move_iterator的operator*取值返回的右值引用,两者的区别在于对重载函数的调用,右值接口一般就是用于数据移动的,会对传入的右值进行数据的直接移动,而不是复制操作. */
      reference operator*() const
      {
          return *_M_current;
      }

      pointer
      operator->() const
      { return _M_current; }

      __normal_iterator&
      operator++()
      {
	++_M_current;
	return *this;
      }

      __normal_iterator
      operator++(int)
      { return __normal_iterator(_M_current++); }

      // Bidirectional iterator requirements
      __normal_iterator&
      operator--()
      {
	--_M_current;
	return *this;
      }

      __normal_iterator
      operator--(int)
      { return __normal_iterator(_M_current--); }

      // Random access iterator requirements
      reference
      operator[](const difference_type& __n) const
      { return _M_current[__n]; }

      __normal_iterator&
      operator+=(const difference_type& __n)
      { _M_current += __n; return *this; }

      __normal_iterator
      operator+(const difference_type& __n) const
      { return __normal_iterator(_M_current + __n); }

      __normal_iterator&
      operator-=(const difference_type& __n)
      { _M_current -= __n; return *this; }

      __normal_iterator
      operator-(const difference_type& __n) const
      { return __normal_iterator(_M_current - __n); }

      const _Iterator&
      base() const
      {
          return _M_current;
      }
    };

从以上源码可以解析,该迭代器如果传入模板类型是个指针的话,他做的任意接口操作都可以等同于一个指针,因此对于通过迭代器进行数据存取这种方式,本身就可以将其看作为一个指针操作。

posted @ 2021-01-04 22:36  呵哈呵  阅读(24)  评论(0)    收藏  举报