详解序列式容器之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函数时空间不够,如下:
- 详解序列式容器之vector
- STL-序列式容器-vector详解
- C++ 序列式容器之vector
- STL序列式容器之vector
- Chapter 4: 序列式容器之 vector
- 序列式容器----vector
- 序列式容器:vector
- 序列式容器 vector
- 序列式容器-vector
- STL六大组件之容器篇(序列式容器vector)
- STL 序列容器之vector
- STL序列容器之vector
- 【STL】序列式容器:vector
- 【STL】序列式容器--vector
- STL 源码剖析序列式容器之vector(四)
- SGI STL的序列式容器之vector浅析
- STL学习笔记--4、序列式容器之vector
- STL源码剖析之序列式容器vector
- linux man page
- Linux配置文件
- Swift
- 动态规划(DP)之入门学习-数字三角形
- Android wifi源码分析(二) Wifi关闭流程
- 详解序列式容器之vector
- P1352 没有上司的舞会
- 对象(object)赋给对象(object),地址(address)赋给地址(address)
- Java常用的数据加密算法介绍
- 大话PCIe:BAR空间和TLP
- tesseract-ocr使用以及训练方法
- Python文件读写
- ThinkPHP3.2中所有的单字母方法
- 博客专家申请规则