• 【C++】vector的模拟实现【完整版】


    目录

    一、vector的默认成员函数 

    1、vector类的大体结构 

      2、无参构造函数

     3、拷贝构造函数

      4、Swap(operator=需要用)

     5、赋值重载operator=

     6、析构函数 

    二、vector的三种遍历方式

     1、size和capacity(大小和容量)

    2、 operator[]遍历 

     3、迭代器iterator遍历和范围for

    三、vector相关的增容和删除

     1、reserve (指定容量)

    2、resize(指定大小) 

     3、push_back(尾插)

     4、pop_back(尾删)

    5、insert 

    6、erase 

    四、完整代码 

    vector.h: 

    test.cpp:


    一、vector的默认成员函数 

    1、vector类的大体结构 

    vector的成员变量本质是由三个T类型(模板)的指针变量组成的,T是因为用了模板,因为vector具体存储什么类型的需要指定

    1. namespace mz
    2. {
    3. using std::cout;
    4. using std::endl;
    5. using std::string;
    6. template<class T>
    7. class vector
    8. {
    9. public:
    10. typedef T* iterator;
    11. typedef const T* const_iterator;
    12. private:
    13. iterator _start;
    14. iterator _finish;
    15. iterator _endofstorage;
    16. }
    17. }

     ①、为什么要在新的命名空间中模拟实现vector?

    防止与库里面的vector冲突,因为我们要自己模拟实现一个

    ②、iterator为什么要用两个版本(const和非const)?

    因为迭代器遍历时我们要有只读又能读又能写的状态,所以要有个const版本的


      2、无参构造函数

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

     3、拷贝构造函数

    注:此函数最好放最后面看,因为其内部调用了下文才讲的成员函数

    ①、正常版本

    首先我们不自己实现,就是浅拷贝,会出现问题,故需我们自己实现深拷贝,v2(v1),即只要让v2拷贝一份新的数据即可(使v2和v1不是指向同一份数据)

    ②、现代版本

    v2(v1),先让v2 reserve和v1一样的容量大小,防止频繁增容降低效率,再把v1中的每个数据尾插到v2中即可

    问题:为什么要const auto&e?

    其是一种用于迭代容器元素的引用方式。它表示在循环过程中,我们用一个常量引用来访问容器中的元素。好处是不会对容器进行修改,并不会产生拷贝操作,可提高代码的效率。

    在用范围for循环时,如果我们只是想读取容器中的元素而不对其进行修改,可以使用const auto&来声明循环变量。例如,当我们遍历一个vector或者数组时,可以使用const auto&来避免对容器进行修改操作。

    例如,当我们遍历一个数组时,如果使用const auto&来声明循环变量,则不能修改数组中的元素。例如,对于以下代码:

    int arr = {0, 1, 2, 3, 4};
    for (const auto& a : arr) {
    a = 1; // 错误,不能修改
    cout << a << “\t”;
    }

    由于使用了const auto&声明循环变量a,所以对a进行修改会导致编译错误。

    1. //v2(v1) 正常版本的实现
    2. /*vector(const vector& v)
    3. {
    4. _start = new T[v.capacity()];
    5. _finish = _start;
    6. _endofstorage = _start + v.capacity();
    7. for (size_t i = 0; i < v.size(); ++i)
    8. {
    9. *_finish = v[i];
    10. ++_finish;
    11. }
    12. }*/
    13. //v2(v1) 现代版本的实现(更推荐)
    14. vector(const vector& v)
    15. :_start(nullptr)
    16. , _finish(nullptr)
    17. , _endofstorage(nullptr)
    18. {
    19. reserve(v.capacity());
    20. for (const auto& e : v)
    21. push_back(e);
    22. }

      4、Swap(operator=需要用)

    为什么需要自己提供swap,而不调用里提供的?

    因为库里面提供的对于自定义类型代价极大,因为需要深拷贝,但对于内置类型用库函数里面的还好,所以应该自己写一个成员函数swap

    1. void swap(vector& v)
    2. {
    3. //调用全局的swap,交换指针,其为内置类型,无需深拷贝
    4. //代价相比于直接调用库里面函数交换比较小
    5. std::swap(_start, v._start);
    6. std::swap(_finish, v._finish);
    7. std::swap(_endofstorage, v._endofstorage);
    8. }

     5、赋值重载operator=

    ①、正常版本

    先要避免自己给自己赋值,先要用tmp拷贝空间,再释放旧空间,再使_start指向一份自己新开辟的新空间,再把需要赋值的数据拷过去即可(为什么要用tmp?因为new动态开辟可能会出错,若先释放旧空间,再直接用_start开辟空间且开辟失败,_str也被销毁了,赔礼夫人又折兵)

    ②、现代版本(更推荐)

    v是v3的临时拷贝,然后v会与v1交换,出了函数v就被销毁了,不用我们自己再销毁了,省力

    1. //v1 = v3 正常版本的实现
    2. /*vector& operator=(const vector& v)
    3. {
    4. if (this != &v)
    5. {
    6. delete[] _start;
    7. _start = new T[v.capacity()];
    8. memcpy(_start, v._start, sizeof(T) * v.size());
    9. }
    10. return *this;
    11. }*/
    12. //v1 = v3 现代版本
    13. vector& operator=(vector v)
    14. {
    15. swap(v);//this->swap(v);
    16. return *this;
    17. }

     6、析构函数 

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

    二、vector的三种遍历方式

     1、size和capacity(大小和容量)

    原理:指针相减就是指针间的元素个数

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

    ①、为什么要加const?

    成员函数只要不改变成员变量的,最好都要加上const  


    2、 operator[]遍历 

    1. T& operator[](size_t i)
    2. {
    3. //确保范围的合法性
    4. assert(i < size());//记得引头文件assert.h
    5. return _start[i];
    6. }
    7. const T& operator[](size_t i)const
    8. {
    9. assert(i < size());
    10. return _start[i];
    11. }

    ①、为什么要实现两个版本的operator[]?

    直接把非const的operator[]加上const不行吗?不可以,因为operator[]访问完后,有两种状态:1、只读 2、可读可写  你直接加上const就只读了,万一我想改数据又不行了,故写两个版本的(const和非const版本) 


     3、迭代器iterator遍历和范围for

    迭代器有只读的和可读可写的,故也要写两个版本,且一个容器只要支持迭代器,就可以用范围for操作,因为范围for本质也是迭代器

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

    只要把迭代器写出来了,迭代器遍历和范围for遍历就都可以用了

    测试operator[]迭代器范围for三种遍历

    1. void test1()
    2. {
    3. vector<int>v;
    4. v.push_back(1);
    5. v.push_back(2);
    6. v.push_back(3);
    7. v.push_back(4);
    8. cout << v.size() << endl;
    9. cout << v.capacity() << endl;
    10. vector<int>::iterator it = v.begin();
    11. while (it != v.end())
    12. {
    13. *it += 1;
    14. cout << *it << " ";
    15. ++it;
    16. }
    17. cout << endl;
    18. for (auto& e : v)
    19. {
    20. e -= 1;
    21. cout << e << " ";
    22. }
    23. cout << endl;
    24. for (size_t i = 0; i < v.size(); ++i)
    25. {
    26. cout << v[i] << " ";
    27. }
    28. cout << endl;

    运行结果:


    三、vector相关的增容和删除

     1、reserve (指定容量)

    1. void reserve(size_t n)
    2. {
    3. if (n > capacity())
    4. {
    5. size_t sz = size();//大小要在增容前就算好
    6. T* tmp = new T[n];
    7. if (_start)
    8. {
    9. //memcpy(tmp, _start, sizeof(T) * sz);//按字节拷贝,浅拷贝
    10. //作深拷贝,使新旧空间指向自己对应的数据
    11. for (size_t i = 0; i < sz; ++i)
    12. {
    13. tmp[i] = _start[i];//调用的是T的operator=,深拷贝
    14. }
    15. delete[]_start;
    16. }
    17. _start = tmp;
    18. _finish = tmp + sz;//不能写为=tmp + size()
    19. _endofstorage = tmp + n;
    20. }
    21. }

    ①、为什么要在增容前就算出大小?

    因为增容涉及新和旧空间的问题,比如_finish = tmp + size(),而size的求法是用_finish - _start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了

    ②、为什么reserve要更新_finish?

    reserve不是只改变容量吗,跟大小有什么关系,因为_finish是指针,如果一个对象它上来就reserve保存一定容量,然后直接扩容,这个操作没问题吧,但是插入数据肯定涉及*_finish操作,若不更新_finish,_finish初始为nullptr就非法访问内存了,故reserve中也要更改_finish

    ③、为什么不能用memcpy拷贝数据?

    测试时,当用string作为vector存储的类型时,程序崩溃了,为什么?

    本质原因是因为增容中用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题, memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成非法访问,所以新空间与旧空间不应指向同一份数据,应该不用memcpy,写成深拷贝

    解决(reserve的实现不用memcpy): 

    把旧空间的每个值赋值给新空间对应的每个值就好了,就能实现深拷贝了,指向不同的数据


    2、resize(指定大小) 

    1. void resize(size_t n, const T& val = T())//因为不知道T的类型,故给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. //填数据不能用memset
    14. //填数据
    15. while (_finish < _start + n)
    16. {
    17. *_finish = val;
    18. ++_finish;
    19. }
    20. }
    21. }

    ①、填数据为什么不能用memset?

    当对于a数组,设置为0时,没问题,但各设置为1和2时,就出现随机值了 ,因为memset只适合把所有值初始化为0,当初始化为1时,是把每个字节初始化为1 

    即00000001 00000001 00000001 00000001,这就不是整形的1了(2也同理,就初始化0可以)!memset连内置类型都处理不好,别说自定义类型了,问题会更大,本质上就是因为memxxx类函数都是按字节处理的,

    即慎用memxxx,因为其按字节进行操作

    ②、const T& val = T()中的T()使什么意思?

    因为不知道T的类型,故用T的缺省值

    C++中内置类型也可以像自定义类型那样调用构造函数,严格来说,内置类型是没有构造函数的,但C++强行赋予了这个构造函数概念,是为了更好地支持模板

    1. int i = int();//0
    2. int j = int(1);//1
    3. double d = double();//0.0000000000
    4. double e = double(1.1);//1.100000000

     3、push_back(尾插)

    1. void push_back(const T& x)
    2. {
    3. //方法一
    4. if (_finish == _endofstorage)
    5. {
    6. size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
    7. reserve(newcapacity);
    8. }
    9. *_finish = x;
    10. ++_finish;
    11. //方法二、调用insert
    12. //insert(_finish, x);
    13. }

     4、pop_back(尾删)

    1. void pop_back()
    2. {
    3. //方法一
    4. assert(_start < _finish);
    5. --_finish;
    6. //方法二、复用erase
    7. //erase(_finish - 1);
    8. }

    5、insert 

    1. void insert(iterator pos, const T& x)
    2. {
    3. assert(pos <= _finish);//pos == _finish时,相当于尾插
    4. //满了需要扩容
    5. if (_finish == _endofstorage)
    6. {
    7. size_t n = pos - _start;//算出原pos距离
    8. size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
    9. reserve(newcapacity);
    10. //增完容后要更新pos的位置,因为增容后原来的pos就失效了
    11. pos = _start + n;//新的_start + n
    12. }
    13. //插入之前需要往后挪动1个数据
    14. iterator end = _finish;
    15. while (end > pos)
    16. {
    17. *end = *(end - 1);
    18. --end;
    19. }
    20. //插入数据
    21. *pos = x;
    22. ++_finish;
    23. }

    为什么需要提前算出n?

    有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算n,即原pos的位置


    6、erase 

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos < _finish);
    4. iterator it = pos + 1;
    5. while (it < _finish)
    6. {
    7. *(it - 1) = *it;
    8. ++it;
    9. }
    10. --_finish;
    11. return pos;//返回被删除数据的下一位置,那就还是原位置,因为那个数据被删除了
    12. }

     注意:erase是返回被删除数据的下一位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了


    最后一个问题

    如果T是字符串的话代码逻辑会不会忘掉'\0'?

    不会,vector末尾没有'\0',string才有,这也是他们的差别

    四、完整代码 

    总共分为两个文件:vector.h和test.cpp

    vector.h: 

    1. #pragma once
    2. #include
    3. #include
    4. namespace mz
    5. {
    6. using std::cout;
    7. using std::endl;
    8. using std::string;
    9. template<class T>
    10. class vector
    11. {
    12. public:
    13. typedef T* iterator;
    14. typedef const T* const_iterator;
    15. vector()
    16. :_start(nullptr)
    17. ,_finish(nullptr)
    18. ,_endofstorage(nullptr)
    19. {}
    20. //v2(v1) 正常版本的实现
    21. /*vector(const vector& v)
    22. {
    23. _start = new T[v.capacity()];
    24. _finish = _start;
    25. _endofstorage = _start + v.capacity();
    26. for (size_t i = 0; i < v.size(); ++i)
    27. {
    28. *_finish = v[i];
    29. ++_finish;
    30. }
    31. }*/
    32. //v2(v1) 现代版本的实现(更推荐)
    33. vector(const vector& v)
    34. :_start(nullptr)
    35. , _finish(nullptr)
    36. , _endofstorage(nullptr)
    37. {
    38. reserve(v.capacity());
    39. for (const auto& e : v)
    40. push_back(e);
    41. }
    42. //v1 = v3 正常版本的实现
    43. /*vector& operator=(const vector& v)
    44. {
    45. if (this != &v)
    46. {
    47. delete[] _start;
    48. _start = new T[v.capacity()];
    49. memcpy(_start, v._start, sizeof(T) * v.size());
    50. }
    51. return *this;
    52. }*/
    53. //v1 = v3 现代版本
    54. vector& operator=(vector v)
    55. {
    56. swap(v);//this->swap(v);
    57. return *this;
    58. }
    59. void swap(vector& v)
    60. {
    61. //调用全局的swap,交换指针,其为内置类型,无需深拷贝
    62. //代价相比于直接调用库里面函数交换比较小
    63. std::swap(_start, v._start);
    64. std::swap(_finish, v._finish);
    65. std::swap(_endofstorage, v._endofstorage);
    66. }
    67. iterator begin()
    68. {
    69. return _start;
    70. }
    71. iterator end()
    72. {
    73. return _finish;
    74. }
    75. const_iterator begin()const
    76. {
    77. return _start;
    78. }
    79. const_iterator end()const
    80. {
    81. return _finish;
    82. }
    83. void reserve(size_t n)
    84. {
    85. if (n > capacity())
    86. {
    87. size_t sz = size();//大小要在增容前就算好
    88. T* tmp = new T[n];
    89. if (_start)
    90. {
    91. //memcpy(tmp, _start, sizeof(T) * sz);//按字节拷贝,浅拷贝
    92. //作深拷贝,使新旧空间指向自己对应的数据
    93. for (size_t i = 0; i < sz; ++i)
    94. {
    95. tmp[i] = _start[i];//调用的是T的operator=,深拷贝
    96. }
    97. delete[]_start;
    98. }
    99. _start = tmp;
    100. _finish = tmp + sz;//不能写为=tmp + size()
    101. _endofstorage = tmp + n;
    102. }
    103. }
    104. void resize(size_t n, const T& val = T())//因为不知道T的类型,故给T类型的缺省值
    105. {
    106. if (n < size())
    107. {
    108. _finish = _start + n;
    109. }
    110. else
    111. {
    112. if (n > capacity())
    113. {
    114. reserve(n);
    115. }
    116. //填数据不能用memset
    117. //填数据
    118. while (_finish < _start + n)
    119. {
    120. *_finish = val;
    121. ++_finish;
    122. }
    123. }
    124. }
    125. void push_back(const T& x)
    126. {
    127. //方法一
    128. if (_finish == _endofstorage)
    129. {
    130. size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
    131. reserve(newcapacity);
    132. }
    133. *_finish = x;
    134. ++_finish;
    135. //方法二、调用insert
    136. //insert(_finish, x);
    137. }
    138. void pop_back()
    139. {
    140. //方法一
    141. assert(_start < _finish);
    142. --_finish;
    143. //方法二、复用erase
    144. //erase(_finish - 1);
    145. }
    146. void insert(iterator pos, const T& x)
    147. {
    148. assert(pos <= _finish);//pos == _finish时,相当于尾插
    149. //满了需要扩容
    150. if (_finish == _endofstorage)
    151. {
    152. size_t n = pos - _start;//算出原pos距离
    153. size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
    154. reserve(newcapacity);
    155. //增完容后要更新pos的位置,因为增容后原来的pos就失效了
    156. pos = _start + n;//新的_start + n
    157. }
    158. //插入之前需要往后挪动1个数据
    159. iterator end = _finish;
    160. while (end > pos)
    161. {
    162. *end = *(end - 1);
    163. --end;
    164. }
    165. //插入数据
    166. *pos = x;
    167. ++_finish;
    168. }
    169. iterator erase(iterator pos)
    170. {
    171. assert(pos < _finish);
    172. iterator it = pos + 1;
    173. while (it < _finish)
    174. {
    175. *(it - 1) = *it;
    176. ++it;
    177. }
    178. --_finish;
    179. return pos;//返回被删除数据的下一位置,那就还是原位置,因为那个数据被删除了
    180. }
    181. T& operator[](size_t i)
    182. {
    183. //确保范围的合法性
    184. assert(i < size());//记得引头文件assert.h
    185. return _start[i];
    186. }
    187. const T& operator[](size_t i)const
    188. {
    189. assert(i < size());
    190. return _start[i];
    191. }
    192. size_t size()const
    193. {
    194. return _finish - _start;
    195. }
    196. size_t capacity()const
    197. {
    198. return _endofstorage - _start;
    199. }
    200. ~vector()
    201. {
    202. if (_start)
    203. {
    204. delete[]_start;
    205. _start = _finish = _endofstorage = nullptr;
    206. }
    207. }
    208. private:
    209. iterator _start;
    210. iterator _finish;
    211. iterator _endofstorage;
    212. };
    213. void print_vector(const vector<int>& v)
    214. {
    215. vector<int>::const_iterator it = v.begin();
    216. while (it != v.end())
    217. {
    218. cout << *it << " ";
    219. ++it;
    220. }
    221. cout << endl;
    222. }
    223. void test1()
    224. {
    225. vector<int>v;
    226. v.push_back(1);
    227. v.push_back(2);
    228. v.push_back(3);
    229. v.push_back(4);
    230. cout << v.size() << endl;
    231. cout << v.capacity() << endl;
    232. vector<int>::iterator it = v.begin();
    233. while (it != v.end())
    234. {
    235. *it += 1;
    236. cout << *it << " ";
    237. ++it;
    238. }
    239. cout << endl;
    240. for (auto& e : v)
    241. {
    242. e -= 1;
    243. cout << e << " ";
    244. }
    245. cout << endl;
    246. for (size_t i = 0; i < v.size(); ++i)
    247. {
    248. cout << v[i] << " ";
    249. }
    250. cout << endl;
    251. }
    252. void test2()
    253. {
    254. vector<int>v;
    255. v.push_back(1);
    256. v.push_back(2);
    257. v.push_back(3);
    258. v.push_back(4);
    259. v.push_back(5);
    260. v.push_back(6);
    261. v.insert(v.begin(), 0);//头插0
    262. print_vector(v);
    263. //删除偶数
    264. vector<int>::iterator it = v.begin();
    265. while (it != v.end())
    266. {
    267. if (*it % 2 == 0)
    268. {
    269. it = v.erase(it);
    270. }
    271. else
    272. {
    273. ++it;
    274. }
    275. }
    276. print_vector(v);
    277. }
    278. void test3()
    279. {
    280. vector<int>v;
    281. v.reserve(10);
    282. v.push_back(1);
    283. v.push_back(2);
    284. v.push_back(3);
    285. v.push_back(4);
    286. v.push_back(5);
    287. v.push_back(6);
    288. v.resize(4);
    289. print_vector(v);
    290. cout << v.size() << endl;
    291. cout << v.capacity() << endl;
    292. v.resize(8);
    293. print_vector(v);
    294. cout << v.size() << endl;
    295. cout << v.capacity() << endl;
    296. v.resize(12,int());
    297. print_vector(v);
    298. cout << v.size() << endl;
    299. cout << v.capacity() << endl;
    300. int i = int();//0
    301. int j = int(1);//1
    302. double d = double();//0.0000000000
    303. double e = double(1.1);//1.100000000
    304. }
    305. void test4()
    306. {
    307. vector<int>v1;
    308. v1.push_back(1);
    309. v1.push_back(2);
    310. v1.push_back(3);
    311. v1.push_back(4);
    312. vector<int>v2(v1);
    313. for (size_t i = 0; i < v1.size(); ++i)
    314. {
    315. cout << v1[i] << " ";
    316. }
    317. cout << endl;
    318. for (size_t i = 0; i < v2.size(); ++i)
    319. {
    320. cout << v2[i] << " ";
    321. }
    322. cout << endl;
    323. vector<int>v3;
    324. v3.push_back(10);
    325. v3.push_back(20);
    326. v3.push_back(30);
    327. v3.push_back(40);
    328. v1 = v3;
    329. for (auto e : v1)
    330. {
    331. cout << e << " ";
    332. }
    333. cout << endl;
    334. }
    335. void test5()
    336. {
    337. vectorv;
    338. v.push_back("111111111111111111111");
    339. v.push_back("222222222222222222222");
    340. v.push_back("333333333333333333333");
    341. v.push_back("444444444444444444444");
    342. for (auto e : v)
    343. {
    344. cout << e << " ";
    345. }
    346. cout << endl;
    347. }
    348. }

    test.cpp:

    1. #include
    2. #include"vector.h"
    3. int main()
    4. {
    5. mz::test1();
    6. //memxxx 按字节处理
    7. /*int a[10];
    8. memset(a, 0, sizeof(int) * 10);
    9. memset(a, 1, sizeof(int) * 10);
    10. memset(a, 2, sizeof(int) * 10);*/
    11. return 0;
    12. }
  • 相关阅读:
    信创办公–基于WPS的EXCEL最佳实践系列 (数据整理复制粘贴)
    un7.29:Linux——如何在docker中安装tomcat?
    工业镜头的景深、分辨率及如何匹配合适的工业相机-51camera
    React(12)-react的生命周期(important)没写完
    大数据分析实践 | 过滤和抽样
    React Native 之 expo-cli使用 (二十四)
    2023年10月12日
    Oracle数据库的配置文件丢失或损失,重新执行pfile启动
    [golang]在Gin框架中使用JWT鉴权
    真香,Java架构进阶全靠这份阿里大佬整理的笔记,图文并茂
  • 原文地址:https://blog.csdn.net/m0_74044018/article/details/132744051