详解序列式容器之vector

来源:互联网 发布:驱动中国双十一数据 编辑:程序博客网 时间:2024/05/29 13:07


所谓的序列式容器,其中的元素都可序,但未必有序。C++语言本身提供了一个序列式容器array,STL另外再提供vector,list,deque,stack,queue,priority-queue等等序列式容器。其中stack和queue由于只是将deque改头换面而成,技术上被归类为一种配接器。

vector概述


vector与array的不同之处


vector的数据安排以及操作方式,与array非常相似。两者的唯一差别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变;就好比如,要换个大(或小)一点的房子,可以,一切琐细得由客户端自己来:首先配置一块新空间,然后将元素从旧地址一一搬往新地址,再把原来的空间释放给系统。
vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间已容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足,而一开始就要求一个大块头array了,我们可以安心地使用vector,吃多少用多少。
vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。一旦vector出现空间满载的情况,如果客户端每新增一个元素,vector内部只是扩充一个元素的空间,非常不明智。因为所谓的扩充空间,无论大小,是一项“配置新空间/数据移动/释放旧空间”的大工程,时间成本很高,应该加入某种未雨绸缪的考虑。这将稍后介绍。

vector定义摘要

以下是vector定义的源代码摘录。虽然STL规定,欲使用vector者必须先包含,但是SGL STL将vector 实现与更底层的

//alloc 是SGI STL 的空间配置器template<class T, class Alloc = alloc>class vector {public:    //vector的嵌套型别定义    typedef T           value_type;    typedef value_type* pointer;    typedef value_type* iterator;    typedef value_type& reference;    typedef size_t      size_type;    typedef ptrdiff_t   difference_type;protected:    //以下,simple_alloc 是SGI STL的空间配置器,    typedef simple_alloc<value_type, Alloc> data_allocator;    iterator start;         //表示目前使用空间的头    iterator finish;        //表示目前使用空间的尾    iterator end_of_storage;//表示目前可用空间的尾    void insert_aux(iterator position, const T& x);    void deallocate()    {        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    {        return size_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()    {        destroy(start, finish);//调用全局函数        deallocate();//调用vector的一个成员函数    }    reference front() { return *begin(); }    //取第一个元素;    reference back() { return *(end() - 1); }//取最后一个元素    void push_back(const T& x)    {       //将元素插入至最尾端        if (finish != end_of_storage)        {            constructor(finish, x); //调用全局函数            ++finish;        }        else            insert_aux(end(), x);   //调用的是vector的成员函数    }    void pop_back()             //将最尾端的元素取出    {        --finish;        destroy(finish);        //调用全局函数    }    iterator erase(iterator position)//清除某位置上的元素    {        if (position + 1 != end())        {            copy(position + 1, finish, position);//后续元素往前移动            --finish;            destroy(finish);     //调用全局函数            return position;        }    }    void resize(size_type new_size, const T& x)    {        if (new_size < size()            erase(begin() + new_size, end());        else            insert(end(), new_size - size(), x);    }    void resize(size_type new_size) { resize(new_size, T()); }    void clear() { erase(begin(), end()); }protected:    //配置空间,并填满内容    iterator allocate_and_fill(size_type n, const T& x)    {        iterator result = data_allocator::allocate(n);        uninitialized_fill_n(result, n, x);//调用全局函数        return result;    }};

vector的 迭代器


vector维护的是一个连续性空间,所以不论其元素型别为何,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器所需要的操作行为,如operator*,operator->,operator++,operator–,operator+,operator-,operator+=,operator-=,普通指针天生就具备。vector支持随机存取,而普通指针正有着这样的能力。所以,vector提供的是Random Access iterator。

template<class T,class Alloc=alloc>class vector{public:typedef T      value_type;typedef value_type* iterator;  //vector的迭代器是普通指针.. .};根据上面的定义,如果客户端写出这样的代码:vector<int>::iterator ivite;vector<Shape>::iterator svite;ivite的型别其实就是int*,svite的型别其实就是Shape*

vector的数据结构


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

template<class T,class Alloc=alloc>class vector{.. .    iterator start;         //表示目前使用空间的头    iterator finish;        //表示目前使用空间的尾    iterator end_of_storage;//表示目前可用空间的尾.. .};


为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充。这便是容量(capacity)的概念。换句话说,一个vector的容量永远大于或等于其大小。一旦容量等于其大小,便是满载,下次再有新增元素,整个vector就得另觅居所了。
运用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 operator[](size_type n) { return *(begin() + n); }    reference front() { return *begin(); }    //取第一个元素;    reference back() { return *(end() - 1); }//取最后一个元素    void push_back(const T& x)    {       //将元素插入至最尾端        if (finish != end_of_storage)        {            constructor(finish, x); //调用全局函数            ++finish;        }        else            insert_aux(end(), x);   //调用的是vector的成员函数    }    void pop_back()             //将最尾端的元素取出    {        --finish;        destroy(finish);        //调用全局函数    }.. .

这里写图片描述

vector的构造与内存管理:constructor,push_back


vector缺省使用alloc作为空间配置器,并据此另外定义了一个data_allocator,为的是更方便以元素的大小为配置单位:

template<class T,class Alloc=alloc>class vector{protected:typedef simple_alloc<value_type,Alloc>data_allocator;...};

于是data_allocator::allocate(n) 表示配置n个元素空间

vector提供许多constructors,其中一个允许我们指定空间大小及初值:

//构造函数,允许指定vector大小n,和初值valuevector(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);//配置n个元素的空间    unitialized_fill_n(result,n, x);//全局函数    return result;}


当我们以push_back()将新元素插入于vector尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大。如果没有备用空间了,就需要扩充空间(重新配置,移动数据,释放原空间):

  void push_back(const T& x) {    if (finish != end_of_storage) {      construct(finish, x);  //调用全局函数,(placement new)       ++finish;               //调整水位高度    }     else                     //已无备用空间     insert_aux(end(),x);  //重新构造整个vector并插入元素

vector 的成员函数

template<class T,class Alloc>vector<T, Alloc>::insert_aux(iterator position, const T& x){  if (finish != end_of_storage)  //有备用空间  {  //在备用空间起始处构造一个元素,并以vector最后一个元素为其初始值    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,则配置一个元素大小    //如果原大小不为零,则配置原来大小的两倍   //前半段用来存放原始数据,后半段用来存放新数据   iterator new_start = allocate(len);    iterator new_finish = new_start;  try  {    //原vector内容拷贝到新vector      new_finish = uninitialized_copy(start, position, new_start);      construct(new_finish, x);  //为新元素设定初始x      ++new_finish;          //调整水位    //安插点的原内容也一起拷贝      new_finish = uninitialized_copy(position, finish,new_finish);    }   catch(...)   {        destroy(new_start,new_finish),         deallocate(new_start,len);        throw;   }  //销毁,释放原vector    destroy(begin(), end());    deallocate(start, end_of_storage - start);  //调整迭代器,指向新的vector    start =new_start;    finish =new_finish;    end_of_storage =new_start +len;  }}


注意,所谓的动态增加大小,并不是在原空间之后接续新空间,(因为无法保证在元空间之后还有可用的空闲空间),而是以原大小的两倍另外配置一块较大的空间,然后将远呢呢绒拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此对vector的任何操作,一旦引起空间重置,指向原vector的所有迭代器就都失效了。这一点很重要,也很容易犯错。

vector的元素操作: pop_back,erase, clear,insert


vector 容器的元素操作函数有很多,我就选取四个讲解一下,首先是pop_back()函数,pop() 函数的作用是将尾端元素拿掉并调整大小,并不涉及到容量的改变:

 void pop_back()  {    --finish;  //finish是指向现有元素的最后一个元素的下一个cell地址,只需要减一,然后调用destory()即可    destroy(finish);  }


ease() 函数作用是清除某一个元素,或者清除两个迭代器之间的所有元素,如下:

//清除某一个元素  iterator erase(iterator position)   {    if (position + 1 != end())      copy(position + 1,finish, position);    --finish;    destroy(finish);    return position;  }  //清除迭代器first 和 last之间的所有元素  iterator erase(iterator first, iterator last)  {    iterator i = copy(last, finish, first);  //将last到finish(最后一个元素的下一个cell)所有元素copy到从first开始的地方 //返回finish对应的cell,在copy之后所在位置的迭代器,赋值给i//然后销毁从i到finish的所有元素,并移动finish到删除元素之后新元素序列的尾端的下一个cell  destroy(i, finish); finish = finish - (last - first); return first; }

 第二个 erase 示意图如下:
 这里写图片描述
 
  insert函数是把元素插入到对应位置,该函数效率很低,特别是front插入,要移动所有元素退后一个位置,很花销时间,企业级数据尽量少用 vector 的 insert,以下是其源代码:
  

template <class T, class Alloc>void vector<T, Alloc>::fill_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 = allocate(len);            iterator new_finish = new_start;            __STL_TRY             {    //移动元素                //首先移动要插入点位置之前的所有元素到新的vector                new_finish = uninitialized_copy(start, position,new_start);                //其次开始构造要插入的元素               new_finish = uninitialized_fill_n(new_finish, n, x);                //最后移动要插入点位置之后的所有元素到新的vector                new_finish                = uninitialized_copy(position, finish, new_finish);            }            __STL_UNWIND((destroy(new_start,new_finish);            //以下清除并且释放旧的vector            deallocate(new_start,len)));            destroy(start, finish);            deallocate(start, end_of_storage - start);            //移动起始点和水位            start = new_start;            finish = new_finish;            end_of_storage = new_start +len;        }    }}

这里写图片描述

如果if不成立,则如下图:
这里写图片描述
 如果调用insert函数时空间不够,如下:
 这里写图片描述

原创粉丝点击