上一篇文章中,关于stl_vector的故事只是个开始. 这篇文章中,接着去分析vector中的细节问题.
再次声明,我没有看过关于stl源码分析方面的书籍,强调这一点是为了不会让别人误会我是从别的地方抄袭的. 另外,欢迎大家拍砖。
后面陆续的几篇文章都会详细分析vector中的函数实现. 我尽量做到篇幅不大,而且能够尽量用自己的语言和自己分析的结果给大
家展现实现的过程. 这样不会浪费大家的时间.写的不好就请原谅了.
push_back函数的作用就是从_M_finish指向的位置开始插入数据。如果预先分配的存储单元不够,则会做扩展处理.会在后面分析的时候
说明这个问题.
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指向的内存区域进行赋值.
template <class _T1, class _T2> inline void _Construct(_T1* __p, const _T2& __value) { new ((void*) __p) _T1(__value); }
对于new的这种用法,不是很常见,但是在<<c++ primer>>中后面"特殊工具与技术"章节有讲解.
new (place_address) type new (place_address) type(parm-list)
5. 如果达到上限,就需要扩张存储区域,按照原区域的2倍进行扩张.
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函数中,首先会进行判断.
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的另一个一个参数的版本就不需要多解释了,因为二者之间在实现上方法和原理都是一样的,多说无益.