• C++——vector容器的基本使用和模拟实现


    1、vector的介绍

    1. vector是表示可变大小数组的序列容器。
    2.  就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。
    3.  本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
    4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
    5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。
    6. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和 forward_lists统一的迭代器和引用更好。

    注意:string与vector类似,但也有不同之处。string是存放字符的动态数组,vector是存放一个T类型的动态数组,本质都是一个顺序表。并且我们使用vector时需要加头文件#include

     2、vector的常用接口

    构造函数

    1. void test_vector1()
    2. {
    3. vector<int> v1;
    4. vector<int> v2(10, 8);//开十个空间都赋值8
    5. vector<int> v3(++v2.begin(), --v2.end());//用自身的迭代器区间构造
    6. vector<int> v4(v3);//拷贝构造
    7. string s("hello world");
    8. vector<char> v5(s.begin(), s.end());//用string的迭代器区间构造
    9. }

    尾插和尾删

    1. vector<int> v;
    2. v.push_back(1);
    3. v.push_back(2);
    4. v.push_back(3);
    5. v.push_back(4);
    6. v.pop_back();
    7. v.pop_back();
    8. v.pop_back();
    9. v.pop_back();

    由于头删和头插效率太低了,vector并没有提供相应的接口。

    遍历和[]访问

    vector可支持[]下标访问,并且可以改变vector中的值,至于遍历,我们可以用下标遍历,也可以使用范围for来遍历。

    1. //遍历
    2. for (size_t i = 0; i < v.size(); i++)
    3. {
    4. v[i] += 1;//可修改
    5. cout << v[i] << " ";
    6. }
    7. cout << endl;
    8. //
    9. vector<int>::iterator it = v.begin();
    10. //迭代器 it指向vector的第一个数据
    11. while (it != v.end())
    12. {
    13. *it -= 1;
    14. cout << *it << " ";
    15. it++;
    16. }
    17. cout << endl;
    18. //范围for 本质也是迭代器
    19. for (auto e : v)
    20. //把v中的每个数据遍历的时候给给e
    21. {
    22. cout << e << " ";
    23. }
    24. cout << endl;
    25. //int a[] = { 1,2,3 };
    26. 原生指针就是天然的迭代器,数组支持范围for,会被替换指针
    27. //for (int* p = a; p < a + sizeof(a) / sizeof(int); p++)
    28. //{}

    reserve和resize的使用

    1. v.reserve(100); //扩容并只改变capacity空间
    2. v.resize(100); //扩容不仅改变capacity空间并且改变size
    3. v.resize(100, 5); //扩容+初始化 或 删除数据(类比string)
    4. v.resize(2); //截取数据但不改变capacity

    find和assign的使用

    1. vector<int>::iterator ret = find(v.begin(), v.end(), 3);
    2. //auto ret = find(v.begin(), v.end(), 3);
    3. //如果没找到find返回第二个参数
    4. //如果找到了返回该数下标
    5. vector<int> v;
    6. v.push_back(1);
    7. v.push_back(2);
    8. v.push_back(3);
    9. v.push_back(4);
    10. v.assign(10, 5);//给v的前十个值上全部赋值为5,并且会覆盖原来的值

    clear、capacity、size、max_size的使用

    1. cout <<"max_size:"<< v.max_size() << endl;//数据所能存放的最大容量
    2. cout <<"capacity:"<< v.capacity() << endl;//当前容量
    3. cout <<"size:"<< v.size() << endl;//当前数据个数
    4. v.clear();//只清理size的值不改变capacity
    5. cout << "-------------------";
    6. cout << "max_size:" << v.max_size() << endl;
    7. cout << "capacity:" << v.capacity() << endl;
    8. cout << "size:" << v.size() << endl;

    insert、erase的使用

    1. vector<int> v;
    2. v.push_back(1);
    3. v.push_back(2);
    4. v.push_back(3);
    5. v.push_back(4);
    6. //v.assign(10, 5);
    7. //覆盖之前已有的值
    8. vector<int>::iterator ret = find(v.begin(), v.end(), 3);
    9. //auto ret = find(v.begin(), v.end(), 3);
    10. //如果没找到find返回第二个参数
    11. if (ret != v.end())
    12. {
    13. cout << "找到了" << endl;
    14. v.insert(ret, 30);
    15. //v.erase(ret);
    16. //不能在这删,因为ret失效了
    17. //这个迭代器失效问题,我们后面会细讲
    18. }
    19. v.insert(v.begin(), 30);//在v.begin()的前面插入30也就是头插
    20. vector<int>::iterator pos = find(v.begin(), v.end(), 300);
    21. if (pos != v.end())
    22. //找不到的时候就会返回v.end
    23. //因此找不到的时候不会进入
    24. {
    25. v.erase(pos);//删除pos下标的值
    26. }

    增容方式

    1. int main()
    2. {
    3. size_t sz;
    4. std::vector<int> foo;
    5. sz = foo.capacity();
    6. std::cout << "making foo trow;\n";
    7. for (int i = 0; i < 100; i++)
    8. {
    9. foo.push_back(i);
    10. if (sz != foo.capacity())
    11. {
    12. sz = foo.capacity();
    13. std::cout << "capacity changed:" << sz << '\n';
    14. }
    15. }
    16. }

     由上图可知vs下vector是以大约1.5增容的。而Linux下g++是以2倍增容的。

    vector相关OJ题

    1、只出现一次的数

    力扣

    1. class Solution {
    2. public:
    3. int singleNumber(vector<int>& nums)
    4. {
    5. int value=0;
    6. for(auto e : nums)
    7. {
    8. value^=e;//将nums中的值全部互相异或,这样的话相同的数据刚好抵消
    9. }
    10. return value;//返回剩下的那个与其他值不相等的数据
    11. }
    12. };

    2、杨辉三角形

    力扣

    1. 杨辉三角形
    2. class Solution {
    3. public:
    4. vectorint>> generate(int numRows)
    5. {
    6. vectorint>> vv;
    7. vv.resize(numRows);//先给外层vector开内存
    8. for (size_t i = 0; i < numRows; i++)
    9. {
    10. vv[i].resize(i + 1);//给每一个小vector开内存
    11. vv[i][0] = vv[i][vv[i].size() - 1] = 1;//小vector中的头尾设为1
    12. }
    13. for (size_t i = 0; i < vv.size(); i++)
    14. {
    15. for (size_t j = 0; j < vv[i].size(); j++)
    16. {
    17. if (vv[i][j] == 0)
    18. {
    19. vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
    20. }
    21. }
    22. }
    23. return vv;
    24. }
    25. };

    3、电话号码的字母组合(重点题,代码中有详细注释)

    1. //回溯力扣题:《电话号码的字母组合》
    2. class Solution {
    3. string arr[10] = { "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };
    4. //将0-9(其中0和1不给值)中的每个数字映射一个字符串
    5. public:
    6. //最关键的一个函数体,会反复使用
    7. void _letterCombinations(const string& digits, size_t i,
    8. string combinStr, vector& strV)
    9. //传入digits的引用并且加const为了防止改变digits并且减少拷贝,digits就是我们传入的字符串
    10. //传入i为了遍历digits中的每个字符串,因为digits字符串的每个字符还代表这一个字符串:例如2->"abc"
    11. //combinStr是为了去存放每次递归后的字符串组合
    12. //strV为了存放所有情况遍历完全之后的字符串总和
    13. {
    14. if (i == digits.size())
    15. //由于i是从0开始的,倘若此时i已经与digits的长度相等那么说明i已经越界了也代表digits的字符串已经遍历完全了
    16. {
    17. strV.push_back(combinStr);
    18. //将我们组合好的combinStr尾插到strV中,strV也就是最终我们需要返回的那个字符串
    19. return;//立即返回,只要执行该语句,该函数将不再执行接下来的语句,因为此时就需要返回
    20. }
    21. string str = arr[digits[i] - '0'];
    22. //定义一个新的字符串str并且让str中存入我们需要遍历的字符串
    23. //digits[i]是访问到我们输入字符串中的某个字符
    24. //digits-'0'为了得到arr的下标 使其得到digits字符串中每个字符中的字符串
    25. //最后让它赋值给str我们就可以遍历str将其进行组合
    26. for (size_t j = 0; j < str.size(); j++)//遍历str进行组合,并且继续递归调用_letterCombination函数
    27. {
    28. _letterCombinations(digits, i + 1, combinStr + str[j], strV);
    29. //传入digits 为了继续访问它的字符中的字符串
    30. //传入i+1 为了访问digits中的下一个字符中的字符串 并且这样的话i不会发生改变 以便下次可以直接继续使用i+1
    31. //传入 combinStr+str[j] 为了将我们得到的str中的字符尾插到combinStr中存放起来,等遍历结束可以尾插到strV中
    32. //传入 strV 如果遍历结束可直接把combinStr中的值直接传入strV中
    33. }
    34. }
    35. vector letterCombinations(string digits)
    36. //digits是我们所选择的字符串
    37. {
    38. string combinStr;//定义一个combinStr字符串
    39. vector strV;//定义一个vector容器里面的值是string值
    40. if (digits.empty())//为了考虑我们的digits为空的情况下
    41. return strV;//若为空 直接返回
    42. //开始递归调用此函数
    43. _letterCombinations(digits, 0, combinStr, strV);
    44. return strV;//递归调用完成后返回
    45. }
    46. };

    图解:

    3、vector的模拟实现

    vector的成员变量

    1. private:
    2. iterator _start; // 指向数据块的开始
    3. iterator _finish; // 指向有效数据的尾
    4. iterator _endofStorage; // 指向存储容量的尾

    构造函数

    默认构造

    1. vector()
    2. :_start(nullptr)
    3. , _finish(nullptr)
    4. , _endofstorage(nullptr)
    5. {}

    迭代器构造

    1. //一个类模板的成员函数,又可以是一个函数模板
    2. template<class InputIterator>
    3. vector(InputIterator first, InputIterator last)
    4. :_start(nullptr)
    5. ,_finish(nullptr)
    6. ,_endofstorage(nullptr)
    7. {
    8. while (first != last)
    9. {
    10. push_back(*first);
    11. first++;
    12. }
    13. }

    拷贝构造

    1. //v2(v1)
    2. //现代写法
    3. vector(const vector& v)
    4. {
    5. vector tmp(v.begin(),v.end());
    6. //没有模拟swap之前可用下列写法
    7. /*swap(_start, tmp._start);
    8. swap(_finish, tmp._finish);
    9. swap(_endofstorage, tmp._endofstorage);*/
    10. //this->swap(tmp);
    11. swap(tmp);
    12. }
    13. v2(v1)传统写法
    14. //vector(const vector& v)
    15. // //拷贝构造(深拷贝)
    16. //{
    17. // _start = new T[v.capacity()];
    18. // _finish = _start + v.size();
    19. // _endofstorage = _start + v.capacity();
    20. // memcpy是浅拷贝
    21. // memcpy(_start, v._start, v.size() * sizeof(T));
    22. //}

    这里传统写法不是很推荐,因为会用到memcpy,因为memcpy会用到浅拷贝。

    析构函数

    1. ~vector()
    2. {
    3. if (_start)
    4. {
    5. delete[] _start;
    6. _start = _finish = _endofstorage = nullptr;
    7. }
    8. }

    赋值运算符重载函数

    1. //v1=v2
    2. //现代写法
    3. vector& operator=(vector v)
    4. {
    5. //未模拟swap时用下列方法
    6. /*swap(_start, v._start);
    7. swap(_finish, v._finish);
    8. swap(_endofstorage, v._endofstorage);*/
    9. swap(v);
    10. return *this;
    11. }

    迭代器

    1. public:
    2. typedef T* iterator;
    3. typedef const T* const_iterator;

    begin()和end()

    1. const_iterator begin() const
    2. {
    3. return _start;
    4. }
    5. const_iterator end() const
    6. {
    7. return _finish;
    8. }
    9. iterator begin()
    10. {
    11. return _start;
    12. }
    13. iterator end()
    14. {
    15. return _finish;
    16. }

    容量函数

    size()和capacity()

    1. size_t size() const
    2. {
    3. return _finish - _start;
    4. }
    5. size_t capacity() const
    6. {
    7. return _endofstorage - _start;
    8. }

    reserve

    1. void reserve(size_t n)
    2. {
    3. if (n > capacity())
    4. {
    5. //扩容
    6. size_t sz = size();
    7. T* tmp = new T[n];
    8. if (_start)
    9. {
    10. //memcpy是浅拷贝
    11. //memcpy(tmp, _start, sizeof(T) * size());
    12. for (size_t i = 0; i < sz; i++)
    13. {
    14. //T 是int,一个一个拷贝没问题
    15. //T 是string,一个一个拷贝调用是T的深拷贝复制,也没问题
    16. tmp[i] = _start[i];
    17. }
    18. delete[] _start;
    19. }
    20. _start = tmp;
    21. _finish = _start + sz;
    22. /*_finish = tmp + size();
    23. _start = tmp;*/
    24. _endofstorage = _start + n;
    25. }
    26. }

    resize

    1. void resize(size_t n, const T& val = T())
    2. {
    3. if (n < size())
    4. {
    5. _finish = _start + n;
    6. }
    7. else
    8. {
    9. if (n > capacity())
    10. {
    11. reserve(n);
    12. }
    13. while (_finish != _start + n)
    14. {
    15. *_finish = val;
    16. _finish++;
    17. }
    18. }
    19. }

    empty

    1. bool empty()const
    2. {
    3. return _start == _finish;
    4. }

    增删查改

    push_back

    1. void push_back(const T& x)
    2. {
    3. if (_finish == _endofstorage)
    4. {
    5. 扩容
    6. //size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
    7. size_t sz = size();
    8. //T* tmp = new T[newCapacity];
    9. //if (_start)
    10. //{
    11. // memcpy(tmp, _start, sizeof(T) * size());
    12. // delete[] _start;
    13. //}
    14. _start = tmp;
    15. _finish = _start + sz;
    16. //_finish = tmp + size();
    17. //_start = tmp;
    18. //_endofstorage = _start + newCapacity;
    19. reserve(capacity() == 0 ? 4 : capacity() * 2);
    20. }
    21. *_finish = x;
    22. _finish++;
    23. }

    pop_back

    1. void pop_back()
    2. {
    3. assert(_finish > _start);//检查是否为空
    4. _finish--;
    5. }

    insert

    1. iterator insert(iterator pos, const T& x)
    2. {
    3. assert(pos >= _start);
    4. assert(pos <= _finish);
    5. //满了就扩容
    6. if (_finish == _endofstorage)
    7. {
    8. //扩容
    9. //扩容会导致pos失效,扩容需要更新一下pos
    10. size_t len = pos - _start;//计算一下扩容前pos与_start直接的距离以便以计算新pos
    11. reserve(capacity() == 0 ? 4 : capacity() * 2);
    12. pos = _start + len;//得到新pos的位置
    13. }
    14. //移动数据
    15. iterator end = _finish - 1;
    16. while (end >= pos)
    17. {
    18. *(end + 1) = *end;
    19. end--;
    20. }
    21. *pos = x;
    22. _finish++;
    23. return pos;
    24. }

    earse

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos >= _start);
    4. assert(pos <= _finish);
    5. iterator begin = pos + 1;
    6. while (begin < _finish)
    7. {
    8. *(begin - 1) = *begin;
    9. begin++;
    10. }
    11. _finish--;
    12. return pos;
    13. }

    swap

    1. void swap(vector& v)
    2. {
    3. std::swap(_start, v._start);
    4. std::swap(_finish, v._finish);
    5. std::swap(_endofstorage, v._endofstorage);
    6. }

    下标访问

    1. T& operator[](size_t i)
    2. {
    3. assert(i < size());
    4. return _start[i];
    5. }

    完整代码

    1. #pragma once
    2. namespace mwb
    3. {
    4. template<class T>
    5. class vector
    6. {
    7. public:
    8. typedef T* iterator;
    9. typedef const T* const_iterator;
    10. vector()
    11. :_start(nullptr)
    12. , _finish(nullptr)
    13. , _endofstorage(nullptr)
    14. {}
    15. // v2(v1)传统写法
    16. //vector(const vector& v)
    17. // //拷贝构造(深拷贝)
    18. //{
    19. // _start = new T[v.capacity()];
    20. // _finish = _start + v.size();
    21. // _endofstorage = _start + v.capacity();
    22. // memcpy是浅拷贝
    23. // memcpy(_start, v._start, v.size() * sizeof(T));
    24. //}
    25. //一个类模板的成员函数,又可以是一个函数模板
    26. template<class InputIterator>
    27. vector(InputIterator first, InputIterator last)
    28. :_start(nullptr)
    29. ,_finish(nullptr)
    30. ,_endofstorage(nullptr)
    31. {
    32. while (first != last)
    33. {
    34. push_back(*first);
    35. first++;
    36. }
    37. }
    38. void swap(vector& v)
    39. {
    40. std::swap(_start, v._start);
    41. std::swap(_finish, v._finish);
    42. std::swap(_endofstorage, v._endofstorage);
    43. }
    44. //v2(v1)
    45. //现代写法
    46. vector(const vector& v)
    47. {
    48. vector tmp(v.begin(),v.end());
    49. /*swap(_start, tmp._start);
    50. swap(_finish, tmp._finish);
    51. swap(_endofstorage, tmp._endofstorage);*/
    52. //this->swap(tmp);
    53. swap(tmp);
    54. }
    55. //v1=v2
    56. //现代写法
    57. vector& operator=(vector v)
    58. {
    59. /*swap(_start, v._start);
    60. swap(_finish, v._finish);
    61. swap(_endofstorage, v._endofstorage);*/
    62. swap(v);
    63. return *this;
    64. }
    65. ~vector()
    66. {
    67. if (_start)
    68. {
    69. delete[] _start;
    70. _start = _finish = _endofstorage = nullptr;
    71. }
    72. }
    73. const_iterator begin() const
    74. {
    75. return _start;
    76. }
    77. const_iterator end() const
    78. {
    79. return _finish;
    80. }
    81. iterator begin()
    82. {
    83. return _start;
    84. }
    85. iterator end()
    86. {
    87. return _finish;
    88. }
    89. T& operator[](size_t i)
    90. {
    91. assert(i < size());
    92. return _start[i];
    93. }
    94. size_t size() const
    95. {
    96. return _finish - _start;
    97. }
    98. size_t capacity() const
    99. {
    100. return _endofstorage - _start;
    101. }
    102. void reserve(size_t n)
    103. {
    104. if (n > capacity())
    105. {
    106. //扩容
    107. size_t sz = size();
    108. T* tmp = new T[n];
    109. if (_start)
    110. {
    111. //memcpy是浅拷贝
    112. //memcpy(tmp, _start, sizeof(T) * size());
    113. for (size_t i = 0; i < sz; i++)
    114. {
    115. //T 是int,一个一个拷贝没问题
    116. //T 是string,一个一个拷贝调用是T的深拷贝复制,也没问题
    117. tmp[i] = _start[i];
    118. }
    119. delete[] _start;
    120. }
    121. _start = tmp;
    122. _finish = _start + sz;
    123. /*_finish = tmp + size();
    124. _start = tmp;*/
    125. _endofstorage = _start + n;
    126. }
    127. }
    128. void resize(size_t n, const T& val = T())
    129. {
    130. if (n < size())
    131. {
    132. _finish = _start + n;
    133. }
    134. else
    135. {
    136. if (n > capacity())
    137. {
    138. reserve(n);
    139. }
    140. while (_finish != _start + n)
    141. {
    142. *_finish = val;
    143. _finish++;
    144. }
    145. }
    146. }
    147. iterator insert(iterator pos, const T& x)
    148. {
    149. assert(pos >= _start);
    150. assert(pos <= _finish);
    151. //满了就扩容
    152. if (_finish == _endofstorage)
    153. {
    154. //扩容
    155. //扩容会导致pos失效,扩容需要更新一下pos
    156. size_t len = pos - _start;//计算一下扩容前pos与_start直接的距离以便以计算新pos
    157. reserve(capacity() == 0 ? 4 : capacity() * 2);
    158. pos = _start + len;//得到新pos的位置
    159. }
    160. //移动数据
    161. iterator end = _finish - 1;
    162. while (end >= pos)
    163. {
    164. *(end + 1) = *end;
    165. end--;
    166. }
    167. *pos = x;
    168. _finish++;
    169. return pos;
    170. }
    171. iterator erase(iterator pos)
    172. {
    173. assert(pos >= _start);
    174. assert(pos <= _finish);
    175. iterator begin = pos + 1;
    176. while (begin < _finish)
    177. {
    178. *(begin - 1) = *begin;
    179. begin++;
    180. }
    181. _finish--;
    182. return pos;
    183. }
    184. void push_back(const T& x)
    185. {
    186. if (_finish == _endofstorage)
    187. {
    188. 扩容
    189. //size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
    190. size_t sz = size();
    191. //T* tmp = new T[newCapacity];
    192. //if (_start)
    193. //{
    194. // memcpy(tmp, _start, sizeof(T) * size());
    195. // delete[] _start;
    196. //}
    197. _start = tmp;
    198. _finish = _start + sz;
    199. //_finish = tmp + size();
    200. //_start = tmp;
    201. //_endofstorage = _start + newCapacity;
    202. reserve(capacity() == 0 ? 4 : capacity() * 2);
    203. }
    204. *_finish = x;
    205. _finish++;
    206. }
    207. void pop_back()
    208. {
    209. assert(_finish > _start);//检查是否为空
    210. _finish--;
    211. }
    212. private:
    213. iterator _start;
    214. iterator _finish;
    215. iterator _endofstorage;
    216. };
    217. }

  • 相关阅读:
    Java 中的集合框架有哪些主要接口?ArrayList 和 LinkedList 在使用场景上的不同是什么?
    java8-Stream流常用API
    webpack 中 require.context() 的用法
    C++ Reference: Standard C++ Library reference: Containers: deque: deque: back
    【BurpSuite】插件学习之dotnet-Beautifier
    解析全闪对象存储
    【JavaEE进阶系列 | 从小白到工程师】正则表达式的语法&使用
    vue父子组件间数据的双向绑定
    看完这篇 教你玩转ATT&CK红队评估实战靶场Vulnstack(三)
    使用JPA和Hibernate查询分页
  • 原文地址:https://blog.csdn.net/m0_57249790/article/details/127591533