首先介绍下容器的概念,容器是将一些运用最广的数据结构实现出来,并且根据数据在容器中的排列特性,这些数据结构分为序列式容器以及关联式容器两种。其中序列式容器包括array(c++自建)、vector、heap、priority_queue(由heap演变)、list、slist、deque、stack和queue(由deque演变),关联式容器包括RB-tree、set和map和multiset和multimap(由RB-tree演变)、hashtable、hash_set和hash—map和hash-multiset和hash-multimap(由hashtable演变)。其中序列式容器中的元素都是可序的,但是未必有序,其中stack和queue只是将deque的接口修改了,技术上被归类为一种配接器(adapter)。
vector
vector各个方面和数组array都十分类似,惟一的区别是空间的运用的灵活性,array是一个固定了大小的静态空间,一旦配置之后就不能修改了,然后vector是一个动态的空间,随着新元素的加入,它的内部会自行扩充,vector的扩充也是一个配置新空间、数据移动、释放旧空间的大工程。
vector定义的源码如下所示:
template<class T,class Alloc=alloc>
class vector
{
public:
typedef T value_type;
typedef value_type* potinter;
typedef value_type* iterator;//普通指针
typedef value_type& 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);//在position位置前插入x。
void deallocate(){
if(start)
data_allocator::deallocate(start,end_of_storage-start);这是一个释放内存函数,deallocate,相对于allocator的申请空间
}
void fill_initialize(size_type n,const T& value)
{
start=allocate_and_fill(n,value);
finish=start+n;
end_of_storage=finish;
//这是一个填充函数,将我们申请的vector的长度为n的空间全部用value进行赋值。
}
public:
iteration begin(){return start;}
iteration 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);}//返回从begin开始后的第n个元素的引用,这确保了可以赋值成功。
vector():start(0),finish(0),end_of_storage(0){}
vector(size_type n,const T& vlaue){fill_initialize(n,value);}
vector(int n,const T& vlaue){fill_initialize(n,value);}
vector(long n,const T& vlaue){fill_initialize(n,value);}
explicit vector(size_type n){fill_initialize(n,T());}//声明为explicit的构造函数不能在隐式转换中使用,T()相当于一个调用默认初始化的构造函数,它的初始化的缺省值为0
~vector(){
destroy(start,finish);//这是个全局函数,是一个析构函数,负责调用类型的析构函数,销毁相应内存上的内容(但销毁后内存地址仍保留)
deallocate();//负责释放内存(此时相应内存中的值在此之前应调用destory销毁,将内存地址返回给系统,代表这部分地址使用引用-1)
}
reference front(){return *begin();}
reference back(){return *(end()-1);}
void push_back(const T& x)
{
if(finish!=end_of_storage)
{
construct(finish,x);//全局函数,用于将指定指针位置的内容置为x
++finish;
}
else
insert_aux(end(),x);//没有备用空间,扩充空间(重新配置,移动数据,释放原空间)
}
void pop_back()
{
--finish;
destroy(finish);//销毁内容
}
iterator erase(iterator position)
{
if(position+1!=end())
copy(position+1,finish,position);
--finish;//如果加一等于end,说明指向的最后一个元素,和pop_back是一样的,如果没有指向最后一个,那么就挨个向前复制。
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);//如果过短,那么在最后再插入缺少的个数个x;
}
protected:
iterator allocate_and_fill(size_type n,const T& x)
{
iterator result=data_allocator::allocate(n);//配置n个元素空间
uninitialized_fill_n(result,n,x);//全局函数,将范围内指向的所有的未初始化的内存空间赋值x。
return result;
}
}
因为vector是一个连续的线性空间,所以它的迭代器只要普通指针就可以满足了,并且普通指针也支持随机存取,所以vector提供的是一个random access iterators。它的数据机构很简单,以两个迭代器start以及finish分别指向配置得来的连续空间中目前被使用的范围,以end_of_storage代表整块连续空间的尾端。为了降低空间配置的速度成本,一般情况下实际配置大小会比需求更大一些,这就是capacity的概念,下面这张图可以很好的解释上面这段话:
配置空间的程序为:
template<calss T,class Alloc>
void vector<T,Alloc>::insert_aux(iterator position,const T& x)
{
if(finish!=end_of_storage)
//还有备用空间,在备用空间起始处构造元素,并将最后一个元素值设为其初值。
construct(finish,*(finish-1));
++finish;
//调整finish的位置。
T x_copy=x;
//copy_backward是从后往前赋值,具体见下附图,这样刚好能够不覆盖掉元素。
copy_backward(position,finsh-2,finish-1);
//将插入值赋给指针位置position。
*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=data_allocator::allocate(len);//配置空间。
iterator new_finish=new_start;//内部暂无元素
try{
new_finish=uninitialized_copy(start,position,new_start);//将start到position位置的元素全部复制到新地址
construct(new_finish,x);//将待插入的值放入最后可以备用空间
++new_finish;//调整位置
new_finish=uninitialized_copy(position,finish,new_finish);//将之前未复制完的后半段元素复制过来。
}
catch(.....){
destroy(new_start,new_finish);//上述操作失败,那么析构掉已经复制元素的内存空间
data_allocator::deallocate(new_start,len);//释放掉内存空间
throw;
}
destroy(begin(),end());//析构并释放原来的vector。
deallocate();
start=new_start;//调整start和finish和end_of_storage,使它们指向新的vector。
finish=new_finish;
end_of_storage=new_start+len;
}
}
注意一点,动态增加大小,从上述代码可以看出,并不是在原来的空间之后接上新的空间,而是要经过配置,复制,释放三个操作,因此,对vector的任何操作,一旦引起来空间重新配置,之前的指向原vector的迭代器就失效了,vector里面的函数包括void pop_back()、iterator erase(iterator first,iterator last)、iterator erase(iterator position)、void insert(iterator position ,size_type n,const T&x),挑选其中的几个进行剖析。
iterator erase(iterator first,iterator last)
{
iterator i=copy(last,finish,first);//全局函数,复制从last到finish的元素到从first开始的内存空间中,从前往后复制后,返回最后一个地址。
destroy(i,finish);
finish=finish-(last-first);
return first;
}
iterator erase(iterator position)
{
if(position+1!=end())//看是否是清除最后一个元素
copy(position+1,finish,position)
--finish;
destroy(finish);
return position;
}
void clear(){erase(begin(),end());}
void insert(iterator position, const T& value){
insert(position, 1, value);
}
//在position位置之后,插入n个值为value的元素
void insert(iterator position, size_type n, const T& value){
if (n == 0)return;
if ((end_of_storage - finish) >= n){//备用空间够插入n个新元素
T x_copy = value;
const size_type size_from_position_to_end = finish - position;//计算从最后一个位置到要插入位置的元素个数
iterator old_finish = finish;
if (size_from_position_to_end > n)//如果插入元素个数小于插入位置之后的元素个数{
uninitialized__copy(finish - n, finish, finish);//这是一个用于未初始化空间的复制函数,将倒数的n个元素往未初始化过的内存空间移动
finish += n;//改变finish的值
copy_backward(position, old_finish - n, old_finish);从后往前,将剩下的size_from_position_to_end-n个元素从之前的finish那个位置往前复制
fill(position, position + n, x_copy);//填入新的值
}
else{//插入点之后元素个数小于新增元素个数
uninitialized_fill_n(finish, n - size_from_position_to_end, x_copy);//先用x将finish之后的n-size_from_position_to_end个备用空间赋值。
finish += n - size_from_position_to_end;更改finish的值
uninitialized_copy(position, old_finish, finish);一个插入n个值,再将这剩下的size_from_position_to_end个之前的元素赋值到finish之后。
finish += size_from_position_to_end;修改finish
fill(position, old_finish, x_copy);用x将已经移动到最后的size_from_position_to_end个元素的空间赋值。
}
}
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;
//内存的分配要有原子性,即:要么全部成功,要么全部失败。
try{
new_finish = uninitialized_copy(begin(), position, new_start);//1.将原内容至position的所有元素(不包含position) 拷贝到新的vector
new_finish = uninitialized_fill_n(new_finish, n, value);//2.将position位置到后面的n个元素都填充为value
new_finish = uninitialized_copy(position, end(), new_finish);//3.拷贝从 position位置到end()位置的原vector的所有剩余元素
}
catch (...)//如果失败了
{
destroy(new_start, new_finish);
data_allocator::deallocate(new_start,len);(等同于free(new_start);)//删除申请到的内存
throw; //抛出异常
}
//析构并释放原vector
destroy(begin(), end());
//删除内存
deallocate();
//调整迭代器,指向新的vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
上述代码的操作可以用接下来的几幅图进行一个很直观的展示: