STL中vector的底层实现机制及常用操作

本文基于SGI STL vector源码分析其底层实现,只关注核心实现,不考虑空间配置器allocator。

一、vector概述

vector的数据安排以及操作方式,与array非常相似。两者的唯一区别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变,要换个大(或小)一点的房子,可以,一切琐细得由客户端自己来:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。而vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,因此不必因为担心空间不足而一开始就要一个大块头array了。

二、vector的迭代器

vector维护的是一个连续线性空间,所以不论其元素类型是什么,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器所需要的操作行为,如operator*,operator->,operator++,operator–,operator+,operator-,operator+=,operator-=,普通指针天生就具备。vector支持随机存取。

三、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最多可存储的元素的个数。
STL中vector的底层实现机制及常用操作_第1张图片
为了降低二次分配时的成本,vector实际配置的大小可能比客户需求的更大一些,以备将来扩充,这就是容量的概念。即capacity>=size,当等于时,容器此时已满,若再要加入新的元素时,就要重新进行内存分配,整个vector的数据都要移动到新内存。二次分配成本较高,在实际操作时,应尽量预留一定空间,避免二次分配。

四、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);     //从插入点开始填入新值
}//如下图所示

STL中vector的底层实现机制及常用操作_第2张图片

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);
}//如下图所示

STL中vector的底层实现机制及常用操作_第3张图片

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);
}//如下图所示

STL中vector的底层实现机制及常用操作_第4张图片
注意:二次分配内存需要设置异常处理,如有异常发生,实现“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;
}//如下图所示

STL中vector的底层实现机制及常用操作_第5张图片

//清除某个位置上的元素
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();                //传回最后一个数据,不检查这个数据是否存在

你可能感兴趣的:(STL)