C++ STL源码分析------vector

vector与array相比,有更好的灵活性。array是静态空间,配置之后就无法改变,若需要更大的空间,只能进行配置新空间 →数据移动→释放旧空间的操作;而vector不需要担心空间不足的问题,其内部机制会自动扩充空间从而接纳新的元素。

vector的数据结构是连续的线性空间。它的三个迭代器,start、finish、end_of_storage,分别表示目前使用空间的头、使用空间的尾和可用空间的尾:

...
class vector{
...
protected:
    iterator start;
    iterator finish;
    iterator end_of_storage;
...}

有了start、finish、end_of_storage这三个迭代器,我们可以完成一些简单操作:

  • 返回首尾指针:

iterator begin() { return start; }
iterator end() { return finish; }
  • 返回大小、容量、空容器判断:

size_type size() const { return size_type(end() - begin()); }
size_type capacity() const { return size_type(end_of_storage - begin()); }
bool empty() const { return begin() == end(); }
  • 返回注标运算子、首元素、尾元素:

reference front() { return *begin(); }
reference back() { return *(end() - 1); }//end指针指向尾元素之后
reference operator[](size_type n) { return *(begin() + n); }//退化成数组,存在越界访问的问题

vector的大小和容量

当不断向vector中加入和弹出数据后,通过程序观察大小和容量的变化。

#include<iostream>
#include<vector>

using namespace std;

int main()
{
	vector<int> iv;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	//resize()、reserve()、push_back、pop_back 
	iv.resize(1);//resize为size对应的方法;reverse为capacity对应的方法 
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.reserve(2);//resize为size对应的方法;reserve为capacity对应的方法 
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.push_back(2) ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.push_back(4) ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.push_back(5) ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.push_back(5) ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.pop_back() ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.pop_back() ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	return 0;
 } 

得到这样的结果:

                                      

因此可以得出结论:

1、capacity的值会大于等于size的值;

2、当使用resize改变size的大小时,capacity不会成倍地增大,而是与size大小相同或略大于size;

3、当通过push_back向vector中添加元素时,若vector的size和capacity相同,此时capacity会扩充至两倍;

4、当通过pop_back弹出数据时,capacity的大小不会变小。

当对元素进行插入、删除、清楚等操作时,观察capacity和size的大下变换:

#include<iostream>
#include<vector>

using namespace std;

int main()
{	
	//erase、clear、insert
	vector<int> iv(4,6);
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.push_back(6) ;
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.erase(iv.begin()); 
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;

	iv.insert(iv.begin(),1,1); 
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.insert(iv.begin(),6,1); 
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	
	iv.clear();
	cout<<"capacity= "<<iv.capacity()<<"  "<<"size= "<<iv.size()<<endl;
	return 0;
 } 

得到的结果如下:

                                         

当capacity足够大的时候,这些元素操作不会改变capacity大小,而插入元素后若超出vector的容量,则将capacity扩大至元素的个数。


vector的构造函数:

vector有很多的构造函数,其中一个允许我么指定空间大小和初始值:

vector(size_type n, const T& value) { fill_initialize(n, value); }
//填充和初始化
void fill_initialize(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& x) {
    iterator result = data_allocator::allocate(n);
    __STL_TRY {//异常控制?
      uninitialized_fill_n(result, n, x);
      return result;
    }
    __STL_UNWIND(data_allocator::deallocate(result, n));
  }

uninitialized_fill_n()是一个全局函数,为指定范围内的所有元素设置相同的初始值,接受三个参,分别是欲初始化空间的起始处、欲初始化空间的大小、初始值;data_allocator::allocate()是根据alloc定义的,方便以元素大小作为配置单位。


push_back()

vector的push_back()源码如下:

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

construct()是对象内容构造的基本函数,接受两个参数,分别是一个指针和一个初始值,作用是将初始值设定到指针所指的空间上。当vector的大小小于容量时,简单地在开辟的空间中加入一个值;否则,调用insert_aux()函数:

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;//若原大小为0,则配置1
    iterator new_start = data_allocator::allocate(len);
    iterator new_finish = new_start;
    __STL_TRY {
      new_finish = uninitialized_copy(start, position, new_start);//拷贝position前的旧数据
      construct(new_finish, x);//加入新数据
      ++new_finish;//调整new_finish
      new_finish = uninitialized_copy(position, finish, new_finish);//拷贝position后的旧数据
    }

#       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;
  }
}

其中,copy_backward( first, last ,result)表示的是从最后一个元素开始复制,接受三个参数,分别为被复制的元素的区间范围[first,last),复制到目标区间的具体位置[result-(last-first),result)。其实push_back()并不会执行insert_aux()的if判断,if判断后执行的操作是首先在vector的末尾插入一个元素,初始值为vector插入前的最后一个元素,然后将position至finish之间的元素向后移一位,最后将需要插入的值得副本插入至position位置。

当所需的vector的大小大于容量时,开辟一段新的空间,并将容量扩大为原来的两倍(若初始容量是0,则配置为1),前半段用来放置旧数据,后半段用来放置新数据。由于insert_aux()函数会被insert调用,因此函数的功能是更通用地将数据插入至position位置。position的值为end()时,也就是在末尾插入数据,就是push_back()的功能。

在完成数据的插入之后,需要析构并释放原vector,然后再调整迭代器的名称,将start、finish、end_of_storage指向新的vector。


vector其他主要元素操作:

除了push_back()之外,还有pop_back()、erase()、clear()、insert()、swap()等等,对其中部分操作的源码进行解读。

  • pop_back()

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

pop_back()将尾后指针finish向前移动,最后将finish指向的对象析构掉(destroy()为全局函数,用于析构)。

  • erase()

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;
  }

erase()函数有两个版本的,用于清除某个位置的元素或某两个位置之间的所有元素。

对于第一个版本,若删除元素不是末尾元素,则将position之后的元素向前移动,并将末尾元素析构;否则直接对末尾元素析构。

对于第二个版本,思想与第一个版本相似,将(last,finish)之间的元素拷贝至first处,然后将多余的元素析构掉。

  • insert()

template <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) {
      T x_copy = x;
      const size_type elems_after = finish - position;
      iterator old_finish = finish;
      if (elems_after > n) {
        uninitialized_copy(finish - n, finish, finish);
        finish += n;
        copy_backward(position, old_finish - n, old_finish);
        fill(position, position + n, x_copy);
      }
      else {
        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 {
      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;
    }
  }
}

insert(position,n,x)接受三个参数,分别是插入位置,插入元素数量,插入元素值。插入操作的情况比之前的元素操作都要复杂,因为它会遇到更多的情况:插入元素后是否会超过vector的容量(有就是备用空间容量是否大于插入元素个数)?插入元素的数量是否大于插入点后的元素数量?

备用空间容量大于等于插入元素个数n

若插入点后的元素数量m大于插入元素的数量n,先将vector的后n个元素uninitialized_copy至vector末尾,再将m-n个元素拷贝至old_finish(插入数据前的finish)之前,最后再对position位置填充需要插入的数据。

假设m=3,n=2,插入过程如下图所示:

若插入点后的元素数量m小于插入元素的数量n,假设m=2,n=3,插入过程如下图所示:

②备用空间容量小于插入元素个数n

此时需要开辟新的空间,首先将插入点之前的元素uninitialized_copy()至新的vector内,然后将需要拷贝的元素uninitialized_fill_n()至新vector的插入点,最后再将原vector剩下的元素uninitialized_copy()至新vector的最后。

开辟新的空间的大小由插入元素的数量决定:若插入元素数量大于vector的大小old_size,则新vector的大小为old_size+n;否则新vector的大小为2*old_size:

const size_type len = old_size + max(old_size, n);

Over......

猜你喜欢

转载自blog.csdn.net/cleveland_/article/details/85099848
今日推荐