STL容器 vector 详解


【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<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 和所有容器一样,也是使用 alloc 分配空间, 并以vector< class T> 中的T类型作为迭代器移动的一个单位(上例的T类型就是int)。

    下面具体分析 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 比较特殊,为了优化性能,其实现起来比前面几个复杂得多:

void vector<T, alloc>::insert (iterator position, size_type n, const T& x){ ... ...; }
insert 的源代码是按照两种情况来分的,如下图所示:

        insert 主要也是通过 copy() 函数来实现覆盖、用 fill() 函数填充满某个区间的。会分成上图中的几种情况是基于性能和安全问题的综合考虑,具体源代码和图解的详细过程可以参考侯捷编写的《STL 源码剖析》4.2.6节的P123~P128,在此不作赘述。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值