stl容器之vector
需要了解:实现细节,扩容机制,如何插入元素、消除元素,一些操作的时间复杂度等。
vector(mingw-8.1.0)的介绍
vector是序列式容器,动态扩容,比之array更加灵活。对于序列式容器而言,扩容有以下操作:配置新空间、移动数据、释放旧空间,所以扩容机制需要更合理的策略。
vector的迭代器
迭代器类型:Random Access,因为其维护的是连续线性空间,与raw pointer一样支持各种操作。注意,vector中的push_back、insert、erase等操作都可能使先前获得的迭代器失效。
vector的数据结构
数据结构:线性连续空间。其内部具有三个迭代器。在mingw中,其内部有名为_Vector_impl的底层实现:
其中start指向使用空间的头,finish指向使用空间的尾,end_of_storage指向可用空间的尾。begin操作如下,iterator目的是将非类的迭代器(例如指针)转换为类的迭代器,里面定义了很多类型,且重载了各种操作符来支持不同迭代器类型。
vector的扩容机制
以push_back为例,该函数为左值版
首先检查空间是否充足
- 若充足,则直接调用construct函数在备用空间中构造元素。
- 若不充足,则调用扩容插入函数realloc_insert。
realloc_insert流程
①计算需要扩容后的空间大小,扩容前的空间大小,记录原始空间的start和finish指针,分配新内存并获得新空间的start指针。
其中_M_check_len函数实现如下,其先判断当前空间是否已达到最大可分配大小,如果达到则抛出error。如果未达到,则扩容后的大小为2倍size(如果size为0,则大小为1,因为vector可能为空)
②接下来就是try语句块,内含扩容插入过程,先看代码
c++11支持move操作,所以预编译宏中将construct第三个实参更换为经完美转发的右值引用参数,而我们一开始使用的是左值版,实际不会用move。
- 首先使用一个construct来为新添加的元素在新空间中的__new_start+__elems_before位置进行构造。
- 然后将原始空间中的元素全部复制(拷贝/移动)到新空间中,将指针指向已存储元素之后。
- 最后又出现了一次操作,是因为该函数也会被insert调用,便于将插入点后的元素进行复制,若不是insert则不会造成影响。
③如果构造新空间过程发生了错误,将进行异常处理,将元素进行析构(有时为了优化而什么都不做),然后将新分配的空间进行销毁,然后继续抛出异常。
④若构造成功,将对旧空间元素进行析构然后销毁。并让内部的三个指针指向新内存空间。
vector的erase操作
传入一个迭代器,也就是说可以对某个位置上的元素进行消除,对于一个连续空间而言,这样的操作花销极大。_M_earse实现如下
首先判断该位置是否是最末尾的元素
- 如果不是,把[__position+1,end()]范围内的元素移动到从该位置开始的空间。
- 如果是,那该元素就在finish前一位,直接将该位置的元素析构后就能作为新的end了。
vector的insert操作
以在某个位置insert多个元素为例
实现代码太多,这里就以《STL源码剖析》里面的策略进行总结:
当前空间充足,判断插入元素数量n与插入点之后的元素数量__elems_after。
- Ⅰ. 若n<__elems_after,则先将finish前n个元素全部拷贝到finish之后,更新finish后将插入点后的剩余元素复制到原finish前,最后填充n个元素。
- Ⅱ. 若n>=__elems_after,则先在finish后填充n-__elems_after个元素,然后将插入点后的元素复制到填空末尾,最后再向插入点后填充剩余元素。
不足时就用到扩容插入函数了,步骤基本上就是
- 分配新空间
- 插入点前元素复制到新空间
- 填充n个元素
- 将插入点后元素复制到新空间,也就是上面realloc_insert提及的额外步骤