飞道的博客

C++(STL源码):17---序列式容器vector源码剖析

445人阅读  评论(0)

一、vector概述

  • 总的来说:vector是可变大小数组
  • 特点:
    • 支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
    • 元素保存在连续的内存空间中,因此通过下标取值非常快
    • 在容器中间位置添加或删除元素非常耗时
    • 一旦vector内存不足,重新申请内存之后,和原vector相关的指针,引用,迭代器都失效。内存重分配耗时很长
  • 通常,使用vector是最好的选择,如果没有什么特殊要求,最好使用vector
  • 与其他容器的比较:
vector 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque 双端队列。支持快速随机访问。在头尾插入/删除速度很快
list 双向链表。只支持双向顺序访问。在list中任何位置进行插入和删除的速度都很快
forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入和删除操作速度都很快
array 固定大小数组。支持快速随机访问。不能添加或删除元素
string 与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入或删除速度快

二、vector定义摘要

  • vector定于与<stl_vector.h>头文件中

  
  1. //alloc是SGI STL的空间配置器
  2. template < class T, class Alloc = alloc>
  3. class vector {
  4. public:
  5. // vector 的嵌套类型定义
  6. typedef T value_type;
  7. typedef value_type* pointer;
  8. typedef value_type* iterator;
  9. typedef value_type& reference;
  10. typedef size_t size_type;
  11. typedef ptrdiff_t difference_type;
  12. protected:
  13. // simple_alloc是SGI STL的空间配置器,见前面空间适配器文章的介绍
  14. typedef simple_alloc<value_type, Alloc> data_allocator;
  15. iterator start; // 表示目前使用空间的头
  16. iterator finish; // 表示目前使用空间的尾
  17. iterator end_of_storage; // 表示目前可用空间的尾
  18. void insert_aux(iterator position, const T& x);
  19. void deallocate() {
  20. if (start)
  21. data_allocator::deallocate(start, end_of_storage - start);
  22. }
  23. void fill_initialize(size_type n, const T& value) {
  24. start = allocate_and_fill(n, value);
  25. finish = start + n;
  26. end_of_storage = finish;
  27. }
  28. public:
  29. iterator begin() { return start; }
  30. iterator end() { return finish; }
  31. size_type size() const { return size_type(end() - begin()); }
  32. size_type capacity() const {
  33. return size_type(end_of_storage - begin());
  34. }
  35. bool empty() const { return begin() == end(); }
  36. reference operator[](size_type n) { return *(begin() + n); }
  37. vector() : start( 0), finish( 0), end_of_storage( 0) {}
  38. vector(size_type n, const T& value) { fill_initialize(n,value); }
  39. vector( int n, const T& value) { fill_initialize(n,value); }
  40. vector( long n, const T&value) { fill_initialize(n,value); }
  41. explicit vector(size_type n) { fill_initialize(n,T()); }
  42. ~ vector()
  43. destroy(start, finish); //全局函式,见前面文章destroy函数的介绍
  44. deallocate(); //这是 vector的㆒个 member function
  45. }
  46. reference front() { return *begin(); } // 第一个元素
  47. reference back() { return *(end() - 1); } // 最后一个元素
  48. void push_back(const T& x) { // 将元素安插至最尾端
  49. if (finish != end_of_storage) {
  50. construct(finish, x); //全局函式,见前面文章construct函数的介绍
  51. ++finish;
  52. }
  53. else
  54. insert_aux(end(), x); //这是 vector的一个member function
  55. }
  56. void pop_back() { // 将最尾端元素取出
  57. --finish;
  58. destroy(finish); // 全局函式,见前面文章destroy函数的介绍
  59. }
  60. iterator erase(iterator position) { // 清除某位置上的元素
  61. if (position + 1 != end())
  62. copy(position + 1, finish, position); // 后续元素往前搬移
  63. --finish;
  64. destroy(finish); // 全局函式,见前面文章destroy函数的介绍
  65. return position;
  66. }
  67. void resize(size_type new_size, const T& x) {
  68. if (new_size < size())
  69. erase(begin() + new_size, end());
  70. else
  71. insert(end(), new_size - size(), x);
  72. }
  73. void resize(size_type new_size) { resize(new_size, T()); }
  74. void clear() { erase(begin(), end()); }
  75. protected:
  76. // 配置空间并填满内容
  77. iterator allocate_and_fill(size_type n, const T& x) {
  78. iterator result = data_allocator::allocate(n);
  79. uninitialized_fill_n(result, n, x); // 全局函式,见前面uninitialized_fill_n函数的介绍
  80. return result;
  81. }

三、vector的迭代器

  • vector维护的是一个连续线性空间,所以不论其元素类别是什么,普通指针都可以作为vector的迭代器而满足所有必要条件
  • vector迭代器支持有操作有(普通指针也具备):
    • operator*、operator->、operator++、operator--、operator+、operator-、operator+=、operator-=
  • vector支持随机存取,而普通指针正有着这样的能力,所以,vector提供的是随机访问迭代器(Random Access iterators)
  • vector的迭代器定义如下:

  
  1. template < class T, class Alloc = alloc>
  2. class vector {
  3. public:
  4. typedef T value_type;
  5. typedef value_type* iterator; //vector的迭代器是原生指标
  6. ...
  7. };
  • 例如:

  
  1. vector< int>::iterator ivite; //等同于int* ivite;
  2. vector<Shape>::iterator svite; //等同于Shape* svite;

四、vector的数据结构

  • vector的数据结构非常简单:一个线性连续空间
  • 下面介绍vector的3个数据结构:
    • start:表示目前使用空间的头
    • finish:表示目前使用空间的尾
    • end_of_storage:表示目前可用空间的尾

  
  1. template < class T, class Alloc = alloc>
  2. class vector {
  3. ...
  4. protected:
  5. iterator start; //表示目前使用空间的头
  6. iterator finish; //表示目前使用空间的尾
  7. iterator end_of_storage; //表示目前可用空间的尾
  8. ...
  9. };
  • 说明:为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充。这便是容量的概念。也就是说,一个vector的容量永远大于或等于其大小。一旦容量等于大小,下次再新增元素时就需要新开辟一块空间。如下图所示

  • 运用start、finish、end_of_storage三个迭代器,vector提供了首尾标示、大小、容量、空容器判断、注标[]运算符、最前端元素值、最后端元素值....等机能,如下:

  
  1. template < class T, class Alloc = alloc>
  2. class vector {
  3. ...
  4. public:
  5. iterator begin() { return start; }
  6. iterator end() { return finish; }
  7. size_type size() const { return size_type(end() - begin()); }
  8. size_type capacity() const {
  9. return size_type(end_of_storage - begin());
  10. }
  11. bool empty() const { return begin() == end(); }
  12. reference operator[](size_type n) { return *(begin() + n); }
  13. reference front() { return *begin(); }
  14. reference back() { return *(end() - 1); }
  15. ...
  16. };

五、vector的构造与内存管理(constructor、push_back)

vector的内存管理

  • vector默认使用alloc做为空间配置器,并据此另外定义了一个data_allocator,为的是更方便以元素大小为配置单位:

   
  1. template < class T, class Alloc = alloc>
  2. class vector {
  3. protected:
  4. // simple_alloc<>见前面文章介绍
  5. typedef simple_alloc<value_type, Alloc> data_allocator;
  6. ...
  7. };
  • 于是,data_allocator::allocate(n) 表示配置n个元素空间

构造函数

  • vector提供许多构造函数,其中一个允许我们指定空间大小及初值:

   
  1. //构造函数,允许指定vector大小n和初值value
  2. vector(size_type n, const T& value) { fill_initialize(n, value); }
  3. // 充填并予初始化
  4. void fill_initialize(size_type n, const T& value) {
  5. start = allocate_and_fill(n, value);
  6. finish = start + n;
  7. end_of_storage = finish;
  8. }
  9. // 配置而后充填
  10. iterator allocate_and_fill(size_type n, const T& x) {
  11. iterator result = data_allocator::allocate(n); //配置n个元素空间
  12. uninitialized_fill_n(result, n, x); //全局函式,会根据第1个参数类型特性决定使用算法fill_n()或反复调用construct()来完成任务
  13. return result;
  14. }

push_back()函数

  • 当我们以push_back() 将新元素安插入于vector尾端时,该函式首先检查是否还有备用空间,如果有就直接在备用空间上建构元素,并调整迭代器finish,使vector变大。如果没有备用空间了,就扩充空间(重新配置、搬移数据、释放原空间)
  • push_back()原型如下:

   
  1. void push_back(const T& x) {
  2. if (finish != end_of_storage) { //还有备用空间
  3. construct(finish, x); //全局函式
  4. ++finish; //调整水位高度
  5. }
  6. else //已无备用空间
  7. insert_aux(end(), x); // vector member function,见下
  8. }

   
  1. template < class T, class Alloc>
  2. void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
  3. if (finish != end_of_storage) { //还有备用空间
  4. // 在备用空间起始处建构一个元素,并以 vector 最后一个元素值为其初值。
  5. construct(finish, *(finish - 1));
  6. // 调整水位。
  7. ++finish;
  8. T x_copy = x;
  9. copy_backward(position, finish - 2, finish - 1);
  10. *position = x_copy;
  11. }
  12. else { // 已无备用空间
  13. const size_type old_size = size();
  14. const size_type len = old_size != 0 ? 2 * old_size : 1;
  15. // 以上配置原则:如果原大小为0,则配置 1(个元素大小)
  16. // 如果原大小不为 0,则配置原大小的两倍,
  17. // 前半段用来放置原数据,后半段准备用来放置新数据
  18. iterator new_start = data_allocator::allocate(len); // 实际配置
  19. iterator new_finish = new_start;
  20. try {
  21. // 将原 vector 的内容拷贝到新vector
  22. new_finish = uninitialized_copy(start, position, new_start);
  23. // 为新元素设定初值 x
  24. construct(new_finish, x);
  25. // 调整水位
  26. ++new_finish;
  27. // 将原vector的备用空间中的内容也忠实拷贝过来
  28. new_finish = uninitialized_copy(position, finish, new_finish);
  29. }
  30. catch(...) {
  31. // "commit or rollback" semantics.
  32. destroy(new_start, new_finish);
  33. data_allocator::deallocate(new_start, len);
  34. throw;
  35. }
  36. //析构并释放原vector
  37. destroy(begin(), end());
  38. deallocate();
  39. // 调整迭代器,指向新vector
  40. vector start = new_start;
  41. finish = new_finish;
  42. end_of_storage = new_start + len;
  43. }
  44. }

vector内存重分配及注意事项

  • 注意,所谓动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间
  • 注意(重点): 对vector 的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。这是程序员易犯的一个错误,务需小心
  • vector内存重分配即容量的增长是有规律的,可以通过下面的公式描述:

    • maxSize=maxSize+((maxSize>>1)>1?(maxSize>>1):1)
  • 图解:就是由1、2、3、4、6、9...依次增长

  • 下面用一个程序测试即可

   
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int main()
  5. {
  6. std:: vector< int> iv;
  7. iv.push_back( 1);
  8. cout << iv.capacity() << endl; //1
  9. iv.push_back( 1);
  10. cout << iv.capacity() << endl; //2
  11. iv.push_back( 1);
  12. cout << iv.capacity() << endl; //3
  13. iv.push_back( 1);
  14. cout << iv.capacity() << endl; //4
  15. iv.push_back( 1);
  16. cout << iv.capacity() << endl; //6
  17. iv.push_back( 1);
  18. iv.push_back( 1);
  19. cout << iv.capacity() << endl; //9
  20. return 0;
  21. }

六、vector的元素操作(pop_back、erase、clear、insert)

  • 所提供的元素操作动作很多,不就在此文章中一一说明

pop_back


   
  1. //将尾端元素拿掉,并调整大小
  2. void pop_back() {
  3. --finish; //将尾端标记往前移一格,表示将放弃尾端元素
  4. destroy(finish); // destroy是全局函式
  5. }

erase


   
  1. // 清除[first,last)中的所有元素
  2. iterator erase(iterator first, iterator last) {
  3. iterator i = copy(last, finish, first); //copy是全局函式
  4. destroy(i, finish); //destroy是全局函式
  5. finish = finish - (last - first);
  6. return first;
  7. }
  • 下图是上面这个erase函数的版本

 


   
  1. // 清除某个位置上的元素
  2. iterator erase(iterator position) {
  3. if (position + 1 != end())
  4. copy(position + 1, finish, position); //copy是全局函式
  5. --finish;
  6. destroy(finish); //destroy是全局函式
  7. return position;
  8. }

clear


   
  1. //清除容器内所有元素
  2. void clear() { erase(begin(), end()); }

insert


   
  1. //从position开始,插入n个元素,元素初值为x
  2. template< class T,class Alloc>
  3. void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
  4. {
  5. if (n != 0) { //当n!= 0才进行以下所有动作
  6. if (size_type(end_of_storage - finish) >= n){
  7. //备用空间大于等于“新增元素个数”
  8. T x_copy = x;
  9. // 以下计算插入点之后的现有元素个数
  10. const size_type elems_after = finish - position;
  11. iterator old_finish = finish;
  12. if (elems_after > n){
  13. //“插入点之后的现有元素个数”大于“新增元素个数”
  14. uninitialized_copy(finish - n, finish, finish);
  15. finish += n; // 将vector尾端标记后移
  16. copy_backward(position, old_finish - n, old_finish);
  17. fill(position, position + n, x_copy); //从插入点开始填入新值
  18. }
  19. }
  20. else {
  21. //“插入点之后的现有元素个数”小于等于“新增元素个数”
  22. uninitialized_fill_n(finish, n - elems_after, x_copy);
  23. finish += n - elems_after;
  24. uninitialized_copy(position, old_finish, finish);
  25. finish += elems_after;
  26. fill(position, old_finish, x_copy);
  27. }
  28. }
  29. else {
  30. // 备用空间小于“新增元素个数”(那就必须配置额外的内存)
  31. // 首先决定新长度:旧长度的两倍,或旧长度+新增元素个数
  32. const size_type old_size = size();
  33. const size_type len = old_size + max(old_size, n);
  34. // 以下配置新的vector空间
  35. iterator new_start = data_allocator::allocate(len);
  36. iterator new_finish = new_start;
  37. STL_TRY {
  38. // 以下首先将旧vector的安插点之前的元素复制到新空间
  39. new_finish = uninitialized_copy(start, position, new_start);
  40. // 以下再将新增元素(初值皆为n)填入新空间
  41. new_finish = uninitialized_fill_n(new_finish, n, x);
  42. // 以下再将旧 vector 的插入点之后的元素复制到新空间
  43. new_finish = uninitialized_copy(position, finish, new_finish);
  44. }
  45. # ifdef STL_USE_EXCEPTIONS
  46. catch(...) {
  47. // 如有异常发生,实现"commit or rollback" semantics.
  48. destroy(new_start, new_finish);
  49. data_allocator::deallocate(new_start, len);
  50. throw;
  51. }
  52. # endif /* STL_USE_EXCEPTIONS */
  53. // 以下清除并释放旧的vector
  54. destroy(start, finish);
  55. deallocate();
  56. // 以下调整水位标记
  57. start = new_start; finish =
  58. new_finish; end_of_storage =
  59. new_start + len;
  60. }
  61. }
  • 注意,插入完成后,新节点将位于哨兵迭代器(即上缪按的position,标示出插入点) 所指之节点的前方——这是STL对于“插入操作”的标准规范。下面的图片展示了insert(position,n,x)的操作

备用空间>=新增元素个数的情况:

  • ①备用空间2>=新增元素个数2
  • ②插入点之后的现有元素个数3>新增元素个数2

  • ③插入点之后的现有元素个数2<=新增元素个数3

备用空间>=新增元素个数的情况:

  • 例如下面备用空间2<新增元素个数n==3


转载:https://blog.csdn.net/qq_41453285/article/details/103565158
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场