天天看点

STL源码分析之vector容器

vector与array非常相似,两者唯一差别在于空间运用的灵活性

         array是静态空间,一旦配置了就不能改变;若要改变使用的空间大小,一切琐碎由客户端自己来处理:先配置新空间,然后将元素从旧址一一搬到新址去,在退还原来的空间

         vector则是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新的元素

以下为sgi vector源码:

//alloc即为之前讲过的空间配置器
template <class T, class Alloc = alloc>
class vector {
public:
  typedef T                     value_type;
  typedef value_type*           pointer;
  typedef const value_type*     const_pointer;
  typedef value_type*           iterator;         //可以看到,这里iterator直接就是指针
  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;
 
protected:
    //声明一个空间配置器对象
  typedef simple_alloc<value_type, Alloc> data_allocator;
  iterator start;                               //目前使用空间的开头
  iterator finish;                          //目前使用空间的尾部
  iterator end_of_storage;                      //目前可用空间的尾部
  void insert_aux(iterator position, const T& x);   //此函数将对象x插入到某个位置上,且此函数负责改变vector的大小
 
  void deallocate() {
    //直接将整个vector释放
    if (start) data_allocator::deallocate(start, end_of_storage- start);
  }
 
  void fill_initialize(size_type n, const T& value) {
    start = allocate_and_fill(n, value);
    finish = start + n;
    end_of_storage = finish;
  }
public:
  iterator begin() { return start; }
  iterator end() { return finish; }
  size_type size() const { return size_type(end() -begin()); }
  size_type capacity() const { returnsize_type(end_of_storage - begin()); }
  bool empty() const { return begin() == end(); }
  reference operator[](size_type n) { return *(begin() + n); }
 
//构造函数
  vector() : start(0), finish(0),end_of_storage(0) {}
  vector(size_type n, const T& value) {fill_initialize(n, value); }
  vector(int n, const T& value) {fill_initialize(n, value); }
  vector(long n, const T& value) {fill_initialize(n, value); }
  explicit vector(size_type n) { fill_initialize(n, T()); }
 
  vector(const vector<T,Alloc>& x) {
    start = allocate_and_copy(x.end() -x.begin(), x.begin(), x.end());
    finish = start + (x.end() - x.begin());
    end_of_storage = finish;
  }
 
  ~vector() {
        //destroy函数在空间配置器章节中讲过,通过使用value_type判断是否有对每个对象调用析构函数的必要性,没有就直接free, 有的话才调用每个的析构函数
destroy(start,finish);
    //这个函数就是上面看到的,调用了空间配置器对象的deallocate函数,主要看释放内存的大小,看调用的是第一级配置器还是第二级
deallocate();
  }
  vector<T, Alloc>& operator=(const vector<T,Alloc>& x);
  void reserve(size_type n) {
    if (capacity() < n) {
      const size_type old_size = size();
      iterator tmp = allocate_and_copy(n,start, finish);
      destroy(start, finish);
      deallocate();
     start = tmp;
      finish = tmp + old_size;
      end_of_storage = start + n;
    }
  }
 
//可见,front和back都是实实在在的指向第一个元素和最后一个元素
  reference front() { return *begin(); }
  reference back() { return *(end() - 1); }
 
//将元素插入至最尾端
  void push_back(const T& x) {
    if (finish != end_of_storage) {
      construct(finish, x);         //之前接触了,就是调用 placement new,在finish指向的未初始化内存构造一个对象
      ++finish;                     //finish指向下一个未初始化的内存
    }
    else
      insert_aux(end(), x);
  }
  void swap(vector<T, Alloc>& x) {              //交换两个vector的内容
    __STD::swap(start, x.start);
    __STD::swap(finish, x.finish);
    __STD::swap(end_of_storage,x.end_of_storage);
  }
//插入某个元素在某个位置
  iterator insert(iterator position, const T& x) { 
    size_type n = position - begin();
    if (finish != end_of_storage && position == end()){
      construct(finish, x);
      ++finish;
    }
    else
      insert_aux(position, x);
    return begin() + n;
  }
  iterator insert(iterator position) { return insert(position,T()); }
 
  void insert (iterator pos, size_type n, const T& x);
  void insert (iterator pos, int n, const T& x) {
    insert(pos, (size_type) n, x);
  }
  void insert (iterator pos, long n, const T& x) {
    insert(pos, (size_type) n, x);
  }
 
//将最尾端元素取出,要注意的是先将finish减一在释放,因为finish指向最后一个元素的下一个没有对象的内存位置
  void pop_back() {
    --finish;
    destroy(finish);
  }
 
//整体前移
  iterator erase(iterator position) {
    if (position + 1 != end())
      copy(position + 1, finish, position);
    --finish;
    destroy(finish);
    return position;
  }
  iterator erase(iterator first, iterator last){
    iterator i = copy(last, finish, first);
    destroy(i, finish);
    finish = finish - (last - first);
    return first;
  }
 
//调整vector大小
  void resize(size_type new_size, const T& x) {
    if (new_size < size())
      erase(begin() + new_size, end());         //这里的策略是只调整finish位置,而不改变整个vector的大小
    else
      insert(end(), new_size - size(), x);
  }
  void resize(size_type new_size) { resize(new_size, T()); }
  void clear() { erase(begin(), end()); }
 
protected:
//配置n个数据的空间并且用n个x填充
  iterator allocate_and_fill(size_type n, const T& x) {
    iterator result =data_allocator::allocate(n);
    __STL_TRY {
      uninitialized_fill_n(result, n, x);           //这里直接调用我们之前讲过的函数
      return result;
    }
   __STL_UNWIND(data_allocator::deallocate(result, n));
  }
           

Vector的迭代器

Vector维护的是一个连续线性空间,普通指针可以作为其迭代器且满足所有必要条件

因此 vector<int>:iterator it;

其实 it 的类型就是 int *

Veector的数据结构

Vector较为简单,就是简单的:

iterator start;                               //目前使用空间的开头
  iterator finish;                          //目前使用空间的尾部
  iterator end_of_storage;                      //目前可用空间的尾部
           

vector的实际配置的大小可能比客户端需求的量更大些,以备将来可能的扩充

STL源码分析之vector容器

下面我们再来看看push_back函数:

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

我们要关注的当然就是insert_aux函数了

void vector<T, Alloc>::insert_aux(iteratorposition, const T& x) {
//插入一个元素之前判断是否有足够的空间去插入
 if (finish != end_of_storage) {
    //在备用空间起始处构造一个元素,并以vector最后一个元素为其初值,因为将来是要将一些元素往后移一个的
construct(finish,*(finish - 1));
//调整finish
    ++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;
    __STL_TRY {
      new_finish = uninitialized_copy(start,position, new_start);
      construct(new_finish, x);
      ++new_finish;
      new_finish = uninitialized_copy(position,finish, new_finish);
    }
#ifdef  __STL_USE_EXCEPTIONS
    catch(...) {
      destroy(new_start, new_finish);
      data_allocator::deallocate(new_start,len);
      throw;
    }
#endif /*__STL_USE_EXCEPTIONS */
//最后释放之前的空间
    destroy(begin(), end());
deallocate();
//改头换面
    start = new_start;
    finish = new_finish;
    end_of_storage = new_start + len;
  }
}
           

因此,这里要注意的是,我们所谓的动态增长空间,并不是在原有基础上接连续的新空间,而是重新申请原有空间两倍大小的新空间

所以,所以,对于vector的操作,一旦引起空间的重新配置,指向原vector的所有迭代器就全都失效了!!!!!!!!!!!

关于Vector的元素操作

下面我们挑一个操作函数看看:

template <class T, class Alloc>
void vector<T,Alloc>::insert(iterator position, size_type n, const T& x) {
  if (n != 0) {                     //要插入的个数为0就不需要操作了
    if (size_type(end_of_storage - finish) >= n) {  //是否有空间插入
      T x_copy = x;
      const size_type elems_after = finish - position;  //有几个元素要放在新元素后面
      iterator old_finish = finish;
      if (elems_after > n) {
        //这里我们发现插入点之后的现有元素个数大于新增元素个数n,这就说明我们一部分现有元素会被搬到未初始化的空间上,而一部分则只是单纯的后移
        //于是我们先将n个末尾元素搬移到新的未初始化的空间上
        uninitialized_copy(finish - n, finish,finish);
        //立即调整finish
        finish += n;
        //把剩余的未搬元素整个的 移到 之前已经搬到未初始化空间上的元素原来占据的空间上
        copy_backward(position, old_finish - n,old_finish);
        //加入新元素
        fill(position, position + n, x_copy);
      }
      else {
        //否则,我们发现发现插入点之后的现有元素个数小于新增元素个数n, 这种情况就说明我们会把新插入的元素在我们拥有的未初始化空间上进行构造
        //所以,我们先在未初始化空间上构造n-elems_after个新元素
        uninitialized_fill_n(finish, n -elems_after, x_copy);
        finish += n - elems_after;
        //再在新元素之后把该移的旧元素都移过来
        uninitialized_copy(position,old_finish, finish);
        finish += elems_after;
        //最后在原来的已使用的空间上将所有旧元素改为新元素
        fill(position, old_finish, x_copy);
      }
    }
else {
//否则,我们没有足够的空间了!
//就要搬到新的申请的2倍空间上去(或者不是2倍,而是 old_size+n了)
      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;
      __STL_TRY {
    //分三段对新空间进行处理,复制+添加+复制
        new_finish = uninitialized_copy(start,position, new_start);
        new_finish =uninitialized_fill_n(new_finish, n, x);
        new_finish =uninitialized_copy(position, finish, new_finish);
      }
#         ifdef  __STL_USE_EXCEPTIONS
      catch(...) {
        destroy(new_start, new_finish);
        data_allocator::deallocate(new_start,len);
        throw;
      }
#         endif /*__STL_USE_EXCEPTIONS */
      destroy(start, finish);
      deallocate();
      start = new_start;
      finish = new_finish;
      end_of_storage = new_start + len;
    }
  }
}
           

为了理解方便,书上还配了图:

情况1、

STL源码分析之vector容器

情况2:

STL源码分析之vector容器

最后情况:

STL源码分析之vector容器

继续阅读