说明:本文转自 http://www.cnblogs.com/respawn/archive/2012/07/30/2615446.html
上一篇文章中,关于stl_vector的故事只是个开始. 这篇文章中,接着去分析vector中的细节问题.
再次声明,我没有看过关于stl源码分析方面的书籍,强调这一点是为了不会让别人误会我是从别的地方抄袭的. 另外,欢迎大家拍砖。
后面陆续的几篇文章都会详细分析vector中的函数实现. 我尽量做到篇幅不大,而且能够尽量用自己的语言和自己分析的结果给大
家展现实现的过程. 这样不会浪费大家的时间.写的不好就请原谅了.
push_back函数的作用就是从_M_finish指向的位置开始插入数据。如果预先分配的存储单元不够,则会做扩展处理.会在后面分析的时候
说明这个问题.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
void
push_back(
const
_Tp& __x) {
if
(_M_finish != _M_end_of_storage) {
construct(_M_finish, __x);
++_M_finish;
}
else
_M_insert_aux(end(), __x);
}
// **
// * 实现和上面相同,只是在需要初始化的地方都是用默认初始化值
// **
void
push_back() {
if
(_M_finish != _M_end_of_storage) {
construct(_M_finish);
++_M_finish;
}
else
_M_insert_aux(end());
}
|
1. _M_finish和_M_end_of_storage有什么区别?_M_finish和_M_end_of_storage同样都是vector从vector_base中继承来的,
但是二者的作用是不同的.
[ _M_finish ] : 数据类型的指针,执行当前操作的数据. 在默认的初始化时候,_M_finish==_M_start.也就是指向底层保存元素
数组的头元素.例如不断的往里面push_back数据,那么_M_finish就会不断的像数组的尾端移动,
直到_M_end_of_storage.
[_M_end_of_storage ] : _M_end_of_storage在使用vector第一次初始化分配数组内存的时候,是指向数组的最后一个元素的后一个位置
因为初始化的时候 _M_finish = _M_start, _M_end_of_storage = _M_start + _n. (_n是元素个数).
随着不断的对vector进行操作,后面可能会导致_M_end_of_storage发生变化.
说的直白一点就是,_M_finish是_M_start(头指针)和_M_end_of_storage(尾指针的后一个位置)之间游动的游标.
2. 执行push_back(n)的操作的时候,首先会检查数组的容量有没有达到上限,如果_M_finish == _M_end_of_storage.那么就需要扩大
存储单元,就会在_M_insert_aux(end(),_x)中进行容量的扩增.
3. 如果没有达到上限的话,也就是说有空余的空间,那么就给当前游标_M_finish指向的位置赋值,然后将游标向下一个位置移动.
4. 如果没有达到上限,就使用construct对_M_finish指向的内存区域进行赋值.
1
2
3
4
|
template
<
class
_T1,
class
_T2>
inline
void
_Construct(_T1* __p,
const
_T2& __value) {
new
((
void
*) __p) _T1(__value);
}
|
对于new的这种用法,不是很常见,但是在<<c++ primer>>中后面"特殊工具与技术"章节有讲解.
1
2
|
new
(place_address) type
new
(place_address) type(parm-list)
|
5. 如果达到上限,就需要扩张存储区域,按照原区域的2倍进行扩张.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const
size_type __old_size = size();
// 获取原存储区域长度
const
size_type __len = __old_size != 0 ? 2 * __old_size : 1;
// 扩张为原来的2倍
iterator __new_start = _M_allocate(__len);
// 按照新存储长度重新申请内存
iterator __new_finish = __new_start;
__STL_TRY {
__new_finish = uninitialized_copy(_M_start, __position, __new_start);
// 将原来数组中的_M_start -> _M_finish之间的数据复制到新的存储区域
construct(__new_finish, __x);
// insert new value. // 插入新数据,和前面的 操作一样
++__new_finish;
// go next. // 移动_M_finish
__new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
}
__STL_UNWIND((destroy(__new_start,__new_finish),
_M_deallocate(__new_start,__len)));
destroy(begin(), end());
_M_deallocate(_M_start, _M_end_of_storage - _M_start);
// 释放原存储内存
_M_start = __new_start;
// 处理其他改动
_M_finish = __new_finish;
_M_end_of_storage = __new_start + __len;
|
这里可能有些人不明白为什么要调用两次uninitialized_copy函数,其实有点想多了,这要看上面的调用:
_M_insert_aux(end(), _x). 而end()的作用就是获取_M_finish.而在uninitialized_copy函数中,首先会进行判断.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
template
<
class
_InputIter,
class
_ForwardIter>
_ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
_ForwardIter __result,
__false_type)
{
_ForwardIter __cur = __result;
__STL_TRY {
for
( ; __first != __last; ++__first, ++__cur)
_Construct(&*__cur, *__first);
return
__cur;
}
__STL_UNWIND(_Destroy(__result, __cur));
}
|
虽然通过很多层的转换,但是还是可以看到判断_first != _last. 所以在上面的调用中这句是不会有执行的.而这看起来有点多此一举是吧?
不是. 因为_M_insert_aux提供的是通用的使用方法,需要使用一种通用的实现.而上面的调用最终还是会得到 _new_finish = _new_finish.
所以不需要纠结.
最后,关于push_back的另一个一个参数的版本就不需要多解释了,因为二者之间在实现上方法和原理都是一样的,多说无益.