所谓的序列式容器,其中的元素都可序,但未必有序。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,和初值value
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);//配置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函数时空间不够,如下:
149

被折叠的 条评论
为什么被折叠?



