【1. 数据结构】
vector 采用的数据结构和数组类似,也是线性连续空间。并且包含以下三个迭代器:
protected:
iterator start; // 指向第一个元素
iterator finish; // 指向最后一个元素的下一个位置
iterator end_of_storage;// 指向目前可用空间的最后一个位置
vector 将空间分为“容量(capacity)”和“大小(size)”两个部分,这是为了方便在堆上预先分配好空间。在增加新的元素时,如果大小即将超过当前的“容量”,那么“容量”就会扩充至原来的两倍,直到足够为止。然而需要注意的是,vector 并不是简单在当前末尾的地址后面继续申请新的空间,而是要经过 重新分配容量 -> 全部元素复制到新分配的容量空间 -> 释放原空间 这三个步骤的,并且由于所有元素的堆地址可能都发生了改变,vector 原有的迭代器都会失效,这点格外需要注意!可以看出,vector 自身的迭代器是声明为 protected 类型的,想要访问以上三个迭代器还需要用到 public 里的方法,如下:
public:
iterator begin(){return start;}
iterator end(){return finish;}
reference front(){return *begin();} // 返回迭代器所指元素的引用
reference back(){return *(end() - 1);} // 注意要-1才是最后一个元素
bool empty() const {return begin() == end();}
代码很精简易懂,不做过多说明。需要插入一句的是,size_t 这个类型在C/C++的很多库函数中都经常使用,可能有些人并不知道其中的原因。size_t 代表的就是 unsigned int / long / long long , 使用size_t 来作为分配空间时的函数形参类型可以保证非负的安全性, size_t 这个类型的声明大大增加了程序的可移植性和高效性。
【2. 构造与内存管理】
因为 vector 相对于普通的数组类型多了“容量”这个概念,所以很容易想出vector 的内存分配肯定和普通数组有什么不同之处。非常正确,下面我们声明一个 vector 变量,并用数字代表 vector 的值,用 * 代表 vector 的容量中还未被使用的空间:
再次提醒,vector 的容量扩充不是简单的在尾部申请新的空间,而是要进行“全体移动”的三个步骤。vector 和所有容器一样,也是使用 alloc 分配空间, 并以vector< class T> 中的T类型作为迭代器移动的一个单位(上例的T类型就是int)。
vector<int> iv(2, 9); [ 9, 9] // 创建size为2的vector, 并都初始化为9 iv.push_back(1); [ 9, 9, 1, * ] // 插入1后, 容量capacity变为原来的两倍, size为3 iv.push_back(2); [ 9, 9, 1, 2 ] // capacity == size iv.pop_back(); [ 9, 9, 1 , * ] // 弹出最后一个元素 vector<int>::iterator ivIte = find(iv.begin(), iv.end(), 1); // 声明一个迭代器,从左到右寻找后,指向vector中第一个为1的元素 iv.erase(ivIte); [ 9, 9, * , * ] // 删除vector指向的元素, capacity仍为4,ivIte还在vector中 iv.push_back(2); [ 9, 9, 2, * ] // 尾部添加2 ivIte = find(iv.begin(), iv.end(), 2); // 让ivIte指向从左往右第一个为2的元素 if (ivIte != iv.end()) iv.insert(ivIte, 3, 7); [ 9, 9, 7 , 7, 7, 2, * , *] // 在ivIte前插入3个7,容量capacity扩充为4 ******** 代码 ******** ******** 操作结果 ******** 下面具体分析 vector 的构造函数,源代码如下:
vector (size_type n, const T& value) {full_initial(n, value);}
// 参数 n 是size_t类型的变量,value 则是类型为 class T 的引用
/*
* fill_initialize函数,也包含在class vector{}中
* 作用是修改三个 protected 迭代器,使其指向分配的堆空间
*/
void fill_initialize(size_type n, const T& value) {
start = allocate_and_fill(n, value); // 使start指向分配首地址
finish = start + n; // finish指向最后一个元素的下一位
end_of_storage = finish; // vector的容量的最后一个位置
}
/*
* allocate_and_fill函数,返回由全局函数uninitialized_full_n分配的空间首地址
* 作用是分配 n 个 class T 类型的空间,并且初始化为 x
*/
iterator allocate_and_fill(size_type n, const T& x) {
iterator result = data_allocator :: allocate(n);// 配置n个元素空间
uninitialized_full_n(result, n, x); // 全局函数,使迭代器result指向预初始化空间起点,n代表空间大小size,x是初值
return result; // uninitialized_full_n()会根据参数特性选择使用fill_n()或者多次使用construct()分配空间
}
【3. 元素操作】
vector 的元素操作有很多,下面就pop_back、erase、clear 和 比较复杂的 insert做详细说明。
pop_back :
void pop_back() {
--finish; // 尾部迭代器向前移动一个单位
destroy(finish); // 全局函数,清除finish所指内容
erase :}
iterator erase(iterator first, iterator last){
iterator i = copy(last, finish, first); // 注意:这里用的是复制,直接用last---finish部分覆盖first之后的部分,此时结尾为i
destroy(i, finish); // 清除i---finish部分
finish = finish - (last - first); // 使finish重新指向结尾
return first; // 返回删除部分的首地址迭代器
clear :}
void clear() {erase(begin(), end());} // 使用 erase() 清空 vector
insert 比较特殊,为了优化性能,其实现起来比前面几个复杂得多:
insert 的源代码是按照两种情况来分的,如下图所示:void vector<T, alloc>::insert (iterator position, size_type n, const T& x){ ... ...; }
insert 主要也是通过 copy() 函数来实现覆盖、用 fill() 函数填充满某个区间的。会分成上图中的几种情况是基于性能和安全问题的综合考虑,具体源代码和图解的详细过程可以参考侯捷编写的《STL 源码剖析》4.2.6节的P123~P128,在此不作赘述。