STL 源码剖析(四)序列式容器--vector

1. 写在前面

之后的几篇主要记录一些关于序列式容器中重要的一些知识点以及疑难杂症,并不特别详细分析。

2. vector

2.1 与array的区别

  • array是静态空间,一旦配置就不能改变,要更换时需要自己来更改:首先配置一块新空间,将元素从旧址搬到该新空间,然后将旧址还给系统
  • vector是动态空间,随着元素的加入,内部机制会自行扩充空间以容纳新元素

2.2 vector中capacity(容量)

  • 1.容量:为了降低空间配置时的速度成本,vector实际配置的大小比客户端需求的量更大一些,以备将来的扩充
  • 2.在vector中的实现:
template <class T, class Alloc = alloc>
class vector {
protected:
	iterator start;         //表示目前使用空间的头
	iterator finish;       //表示目前使用空间的尾
	iterator end_of_storage;     //表示目前可用空间的尾
	...
public:
	size_type capacity() const {
		return size_type( end_of_storage - begin()); }  //实际配置大小
	...
};
  • 3.我们可以以一个例子来理解capacity与引入内存管理:
#include <iostream>
#include <vector> 
using namespace std;
int main(int argc, char** argv) {
	int i;
	vector<int> vec(2,9);
	cout << "size = " << vec.size() << endl;      //size = 2
	cout << "capacity = " << vec.capacity() << endl; //capacity = 2
	
	vec.push_back(5);
	cout << "size = " << vec.size() << endl;     //size = 3
	cout << "capacity = " << vec.capacity() << endl; //capacity = 4
	
	vec.push_back(6);
	cout << "size = " << vec.size() << endl;     //size = 4
	cout << "capacity = " << vec.capacity() << endl; //capacity = 4
	
	vec.push_back(7);
	cout << "size = " << vec.size() << endl;     //size = 5
	cout << "capacity = " << vec.capacity() << endl;  //capacity = 8
	
	for (i = 0; i < vec.size(); ++i)
		cout << vec[i] << ' ';           //9 9 5 6 7
	cout << endl;
	
	vec.pop_back();
	cout << "size = " << vec.size() << endl;  //size = 4
	cout << "capacity = " << vec.capacity() << endl; //capacity = 8
	
	vec.clear();
	cout << "size = " << vec.size() << endl;   //size = 0
	cout << "capacity = " << vec.capacity() << endl;  //capacity = 8
	return 0;
}

2.3 元素构造

  • vector提供了许多constructors,其中的一个允许我们指定空间大小及初值:
	//构造函数,允许指定大小 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){
		iteraator result = data_allocator:::allocate(n);
		uninitialized_fill_n(result, n, x);
		return result;
	} 

2.4 vector扩容

  • 当以push_back插入元素时,首先检查是否有备用空间,如果存在,则在备用空间直接构造元素;如果没有备用空间,则扩充空间(重新配置,移动数据,释放原空间):
void push_back(const T& x) {
	if (finish != end_of_storage) {   //还有备用空间
		construct(finish, x);   //直接在备用空间构造元素
		++finish;               //调整finish位置
	}
	else                        //无备用空间
		insert_aux(end(), x);
}
//无备用空间调用insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
  if (finish != end_of_storage)   //还有备用空间
  {
   	 construct(finish, *(finish - 1));  //在备用空间起始处构造元素,并以最后一个元素值为其初值
   	 ++finish;
   	 T x_copy = x;
  	  copy_backward(position, finish - 2, finish - 1);  //将position(即end())后的元素往后移
   	 *position = x_copy;
  }
  else {         //无备用空间
   	 const size_type old_size = size();
   	 //配置空间:如果原大小为0,则配置1;否则配置原大小的两倍
   	 const size_type len = old_size != 0 ? 2 * old_size : 1;  
   	 iterator new_start = data_allocator::allocate(len);   //实际配置
   	 iterator new_finish = new_start;
   	 try {
   	        //将原元素拷贝到新空间
      		new_finish = uninitialized_copy(start, position, new_start);
      		construct(new_finish, x);   //并为新元素设立初值x
    	    ++new_finish;
    	    //将position之后的元素也拷贝过来
    	    new_finish = uninitialized_copy(position, finish, new_finish);
 	   }
   	 catch(...) {    //若非全部成功,则‘析构’所有
         destroy(new_start, new_finish);
    	  data_allocator::deallocate(new_start, len);
     	 throw;
    }
    destroy(begin(), end());  //释放原空间
    deallocate();
    //调整迭代器,指向新空间
    start = new_start;
    finish = new_finish;
    end_of_storage = new_start + len;
  }
}
  • 从上述代码可以知道,所谓动态增加大小,并不是在原空间之后接续新空间,而是以原大小的的两倍另外配置空间,然后拷贝原vector的内容,在新空间构造元素,并释放原空间,这也是为什么在《C++ primer》中出现的插入可能使得迭代器失效的原因,正是因为插入元素可能使得空间重新配置,而导致原vector空间被释放,使得迭代器失效

3.vector元素操作:pop_back,erase,clear,insert

3.1 各操作及其功能

操作 功能
pop_back 将尾端元素拿掉
erase 清空迭代器范围中的所有元素
clear 清空vector中所有元素
insert 在某个插入点插入特定的元素
  • 对于上述几个操作,我们重点理解一下insert操作,因为insert相对其他几个操作来说更为复杂,不易理解:
    在这里插入图片描述

  • (忽略上述图画的不好看orz)在这里就不贴出代码了,重点理解一下即可:

在position位置插入n个x

1. 计算备用空间大于等于新增元素个数

## 1.1 插入点之后元素个数大于新增元素个数:
 ### 1.1.1    将finish之前的n个元素移到finish之后
 ### 1.1.2    finish变更为finish + n得到newfinish
 ### 1.1.3    将position之后到oldfinish - n之间的元素后移到oldfinish位置
 ### 1.1.4    从position开始填充n个x
## 1.2 插入点之后元素小于新增元素个数
 ### 1.2.1    将差值补到finish之后
 ### 1.2.2    finish变更为finish + n - elems_after得到newfinish
 ### 1.2.3    将position到oldfinish的元素拷贝到newfinish之后
 ### 1.2.4    在position到oldfinish之间插入x

2. 备用空间小于新增元素个数

## 2.1 申请空间,old_size + max(old_size, n)   //旧长度的两倍或旧长度+新增元素个数
## 2.2 两复制一填充:
### 2.2.1    将原vector插入点之前的元素拷贝到新空间
### 2.2.2    将新增元素填入新空间
### 2.2.3    将原vector插入点之后的元素拷贝到新空间

猜你喜欢

转载自blog.csdn.net/qq_38790716/article/details/84207162