本文基于SGI STL vector源码分析其底层实现,只关注核心实现,不考虑空间配置器allocator。
vector的数据安排以及操作方式,与array非常相似。两者的唯一区别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变,要换个大(或小)一点的房子,可以,一切琐细得由客户端自己来:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。而vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,因此不必因为担心空间不足而一开始就要一个大块头array了。
vector维护的是一个连续线性空间,所以不论其元素类型是什么,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器所需要的操作行为,如operator*,operator->,operator++,operator–,operator+,operator-,operator+=,operator-=,普通指针天生就具备。vector支持随机存取。
vector所采用的数据结构非常简单:线性连续空间。它以两个迭代器start和finish分别指向配置得到的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间的尾端。
template <class T, class Alloc = alloc>
class vector{
public:
...
protected:
iterator start; //表示目前使用空间的头部
iterator finish; //表示目前使用空间的尾部
iterator end_of_storage; //表示目前可用空间的尾部
private:
...
};
两个关键大小:
大小:size() = finish - start;
容量:capacity() = end_of_storage - start;
如下图所示,size()表示vector中已有元素的个数,capacity()表示vector最多可存储的元素的个数。
为了降低二次分配时的成本,vector实际配置的大小可能比客户需求的更大一些,以备将来扩充,这就是容量的概念。即capacity>=size,当等于时,容器此时已满,若再要加入新的元素时,就要重新进行内存分配,整个vector的数据都要移动到新内存。二次分配成本较高,在实际操作时,应尽量预留一定空间,避免二次分配。
1、构造
vector的构造函数主要有以下几种:
template <class T, class Alloc = alloc>
vector():start(0), finish(0), end_of_storage(0){}
vector(size_type n, const T& value){
fill_initializa(n, value);
}
vector(int n, const T& value){
fill_initializa(n, value);
}
vector(long n, const T& value){
fill_initializa(n, value);
}
explicit vector(size_type n){
fill_initializa(n, T());
}
由上述构造函数知,基本上所有构造函数都是基于fill_initialize()实现的。
//填充并予以初始化
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个元素空间
uninitialized_fill_n(result, n, x); //全局函数,会根据第一个参数的型别特性,决定使用算法fill_n()或反复调用construct()来完成任务。
return result;
}
2、析构
vector的析构函数很简单,就是先销毁所有已存在的元素,然后释放所有内存。
~vector(){
destroy(start, finish); //全局函数
deallocate(); //vector的一个成员函数
}
void deallocate(){
if(start)
data_allocator::deallocate(start, end_of_storage - start); //表示释放从start开始的end_of_storage个元素空间。
}
vector的插入和删除元素是通过vector的push_back()和pop_back()这两个成员函数来实现的。内部实现如下:
1、push_back()
void push_back(const T& x){
if(finish != end_of_storage) { //还有备用空间
construct(finish, x);
++finish; //调整水位高度
}
else //已无备用空间
insert_aux(end(), x); //触发内存的二次分配
}
二次分配,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原空间大小的2倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始原内容之后构造新元素,并释放原空间。
因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就失效了。
2、pop_back()
void pop_back(){
--finish; //将尾端标记往前移一格,表示将放弃尾端元素
destroy(finish);
}
pop_back()只是将vector中的最后一个元素销毁,并不把该空间内存释放。
1、insert()
//从position开始,插入n个元素,元素初值为x
template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x);
主要分以下几种情况:
1、n == 0,不需要插入,直接返回
2、end_of_storage - finish >= n,备用空间大于等于新增元素个数,不需要重新分配内存。
//计算插入点之后的现有元素个数
const size_type elems_after = finish - position;
iterator old_finish = finish;
if(elems_after > n){ //插入点之后的现有元素个数大于新增元素个数
uninitialized_copy(finish-n, finish, finish);
finish += n; //将vector尾端标记往后移
copy_backward(position, old_finish - n, old_finish);
fill(position, position + n, x); //从插入点开始填入新值
}//如下图所示
else{ //插入点之后的现有元素个数小于等于新增元素个数
uninitialized_fill_n(finish, n - elems_after, x);
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish);
finish += elems_after;
fill(position, old_finish, x);
}//如下图所示
3、end_of_storage - finish < n,备用空间小于新增元素个数,需要重新分配内存。
else{
//备用空间小于新增运算个数(那就必须配置额外的内存)
//首先决定新长度:旧长度的两倍,或旧长度+新增元素个数
const size_type old_size = size();
const size_type len = old_size + max(old_size, n);
//以下配置新的vector空间
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
//以下首先将旧vector的插入点之前的元素复制到新空间
new_finish = uninitialized_copy(start, position, new_start);
//以下再将新增元素填入新空间
new_finish = uninitilized_fill_n(new_finish, n, x);
//以下再将vector的插入点之后的元素复制到新空间
new_finish = uninitilized_copy(position, finish, new_finish);
}//如下图所示
注意:二次分配内存需要设置异常处理,如有异常发生,实现“commit or rollback” semantics。
插入完成之后,新节点将位于哨兵迭代器(position)所指之节点的前方----这是STL 对于“插入操作”的标准规范。
2、erase()
//清除[first, last)中的所有元素
iterator erase(iterator first, iterator last){
iterator i = copy(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;
}
3、clear()
利用erase()实现
void clear(){
erase(begin(), end());
}
4、resize()
resize(new_size) 函数主要是用于改变size()的,也就是改变vector的大小,最终改变的是(finish - start)的值,当size() < new_size时,就插入元素,当size() > new_size时,就擦除元素。
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);
}
5、assign()
assign()操作最终都会调用到下面的函数,主要操作是首先擦除容器中已有的全部元素,在从头开始插入n个val元素。
void _Assign_n(size_type n, const T& val){
T tmp = val;
erase(begin(), end());
insert(begin(), n, tmp);
}
6、reverse()
reserve(n) 函数主要是预留n大小的空间,对应的是容器的容量,目的是保证(end_of_storage - start)>= n。只有当空间不足时,才会操作,即重新分配一块内存,将原有元素拷贝到新内存,并销毁原有内存。
void reserve(size_type n){
// determine new minimum length of allocated storage
if (capacity() < n){
// not enough room, reallocate
pointer _Ptr = this->_Alval.allocate(n);
_Umove(begin(), end(), _Ptr);
size_type _Size = size();
if (start != 0){
// destroy and deallocate old array
_Destroy(start, finish);
this->_Alval.deallocate(start, end_of_storage - start);
}
end_of_storage = _Ptr + n;
finish = _Ptr + _Size;
start = _Ptr;
}
}
● 构造、析构
vector<Type> v; //创建一个空的vector
vector<Type> v2(v1); //用vector v1初始化vector v2
vector<Type> v(n); //创建一个vector,含有n个元素,数据均已缺省构造产生
vector<Type> v(n, elem); //创建一个含有n个elem拷贝到的vector
vector<Type> v(beg, end);//创建一个以[beg;end)区间的vector
v.~vector<Type>(); //销毁所有元素,释放内存
● 插入、删除、赋值
v.push_back(elem); //在尾部插入一个元素
v.pop_back(); //删除最后一个元素
v.insert(pos,elem); //在pos位置插入一个elem拷贝,传回新元素位置
v.insert(pos,n,elem); //在pos位置插入n个elem元素。无返回值
v.insert(pos,beg,end); //在pos位置插入在[beg, end)区间的元素。无返回值
v.erase(pos); //删除pos位置的元素,传回下一个元素的位置
v.erase(beg,end); //删除[beg, end)区间的元素,传回下一个元素的位置
v.clear(); //移除容器中的所有元素
v.assign(beg,end); //将[beg, end)区间中的元素赋值给v
v.assign(n,elem); //将n个elem的拷贝赋值给v
● 大小相关
v.capacity(); //返回容器的容量大小。
v.max_size(); //返回Vector所能容纳元素的最大数量(上限),当容器扩展到这个最大值时就不能再自动增大了
v.resize(elem); //重新设定容器的大小
v.reserve(); //保留适当的容量
v.size(); //返回容器中实际元素的个数
● 获取迭代器
v.begin(); //vector头部元素的位置
v.end(); //vector最后一个元素的下一个位置
v.rbegin(); //传回一个逆向队列的第一个元素
v.rend(); //传回一个逆向队列的最后一个元素的下一个位置
● 获取数据
operator[]; //返回容器中指定位置的一个引用
v.at(idx); //传回索引idx所指的数据,如果idx越界,抛出out_of_range
v.front(); //传回第一个数据
v.back(); //传回最后一个数据,不检查这个数据是否存在