• C++ STL --- vector类模拟实现


    目录

    1.构造模块

      (1)无参构造

      (2)半缺省构造

      (3)区间构造的必备知识

      (4)区间构造

      (5)拷贝构造

      (6)赋值运算符重载

      (7)析构函数

    2.迭代器模块

      (1)正向迭代器

      (2)反向迭代器

    3.容量模块

      (1)获取有效元素个数

      (2)获取容量大小

      (3)判空函数

      (4)扩容函数

      (5)设置有效元素个数

    4.元素访问模块

      (1)获取首元素

      (2)获取尾元素

      (3)重载[]

    5.修改模块

      (1)尾插

      (2)尾删

      (3)任意位置插入   

      (4)指定位置删除

      (5)清空有效元素

    6.总体代码


            vector是这是C++中的典型容器,它是动态类型的顺序表任何类型的元素都可以存放。接下来将在以下几个方面进行模拟实现:构造模块、迭代器模块、容量模块、元素访问模块、修改模块

            因为类中的函数非常多,所以只着重实现一些常用的函数。(本文代码均在win10系统下的vs2019中验证)

    1.构造模块

            在构造模块,先来思考一下该设置些什么成员变量?首先要有一个 _start 指针指向一块空间,其次一个 _finish 指针指向这块空间中最有一个有效元素的下一个位置,_end指针指向空间的最后一个位置的下一个位置。而空间中的有效元素个数和容量可以通过指针相减得到。

            用下面这幅图理解一下三个指针的位置:总空间有8个位置,前五个位置有元素,_finish指向第六个位置,_end需要指向第八个位置后面的空间。当有效元素把位置占满的时候,_finish和_end自然就指向同一块空间。

      (1)无参构造

            由于vector类是可以存放任意类型的元素的,要模拟实现它自然也要做到存放任意类型。那么就需要用到模板了。T是存放的元素类型,T*是指向T类型元素的指针。

            代码一:注意创建对象时必须声明存储什么类型的对象。语法:Vector v

            既然是空参构造,那么说明现在并不给对象中存放元素,那直接把三个指针都置空就好。

    1. //代码一
    2. #include "iostream"
    3. using namespace std;
    4. //Vector类存储的数据是任意类型,所以需要设置模板参数
    5. template<typename T>
    6. class Vector {
    7. public:
    8. T* _start;//指向T类型元素的首元素的指针
    9. T* _finish;//指向最后一个有效元素下一个位置
    10. T* _end;//指向顺序表空间最后一个位置的下一个位置。
    11. public:
    12. Vector()
    13. :_start(nullptr)
    14. ,_finish(nullptr)
    15. ,_end(nullptr)
    16. {}
    17. };
    18. int main() {
    19. Vector<int>v;
    20. }

      (2)半缺省构造

            代码二:这里n的类型一定要给成int,不可以给成size_t。不然运行时会遇到错误,在区间构造时就知道了。

    1. //代码二
    2. Vector(int n, const T& val = T())
    3. :_start(new T[n])//在初始化时直接从堆上申请空间
    4. {
    5. for (int i = 0; i < n; i++) {
    6. _start[i] = val;
    7. }
    8. //_finish指向最后一个有效元素的下一个位置
    9. _finish = _start + n;
    10. //_end指向顺序表最后一个位置的下一个位置
    11. _end = _finish;
    12. }

      (3)区间构造的必备知识

            vector类的区间构造很强大,不仅是在使用方法中介绍的利用数组来进行区间构造,也可以用链表来进行区间构造,所以实现时要格外注意不可以直接拷贝,需要将区间内元素遍历一遍才可以。

            在区间构造时候因为可以用链表来构造,所以传入的迭代器自然也是链表的迭代器,这样一来,T类型的迭代器就不能满足条件了,所以我们也要为迭代器设置模板参数,让任意类型的迭代器都可以进行区间构造。

      (4)区间构造

            代码三:这里搭配测试用例解释一下第二个循环。

            看一下区间构造,还记得上面为什么需要把参数类型给成int吗?如果你想构造对象时初始化10个5,是不是这样写 Vectorv(10,5)

            模板不会进行类型转换,根据你的参数推演,如果能找到匹配的就使用。检测到size_t发现不匹配就继续找,找到区间构造后发现可以使用,因为它可以把 Iterator 当成int类型。

            可是原本的迭代器是需要解引用的,现在对整形数字解引用,可不就出错了么?

    1. //代码三
    2. template<typename Iterator>
    3. Vector(Iterator first, Iterator last) {
    4. Iterator it = first;
    5. size_t num = 0;//统计元素个数
    6. while (it != last) {
    7. num++;
    8. it++;
    9. }
    10. _start = new T[num];
    11. _finish = _start;
    12. while (first != last) {
    13. *_finish = *first;
    14. _finish++;
    15. first++;
    16. }
    17. _end = _finish;
    18. }
    19. int main() {
    20. int arr[] = { 1,2,3,4,5,6 };
    21. Vector<int>v(arr,arr+sizeof(arr)/sizeof(arr[0]));
    22. }

            搭配图片理解一下第二个循环:

            arr指向的是首元素,sizeof(arr)/sizeof(arr[0]) 是求数组元素个数,共6个。arr+6就指向了数组外的空间。当first移动到last位置说明元素遍历完毕,此时对应的_finish也指向了最后一个有效元素的后一个位置。(同时这也是顺序表的最后一个位置的后一个位置了,因为申请时只申请了这么多空间)

            重点来了:注意first迭代器也是可以++的,这是因为对应的类中现在基本都会重载++,这样链表也就不需要用指针指向next这样的操作来访问了,会方便很多,也更直观。

      (5)拷贝构造

            代码四:

    1. //代码四
    2. Vector(const Vector& v)
    3. :_start(new T[v.Size()]) //这里用到的Size函数后面会实现的
    4. {
    5. size_t num = v.Size();
    6. for (size_t i = 0; i < num; i++) {
    7. _start[i] = v._start[i];
    8. }
    9. //让对应的指针指向应该指向的位置
    10. _finish = _start + num;
    11. _end = _finish;
    12. }

      (6)赋值运算符重载

            代码五:因为这里传递的是值,会发生值拷贝,产生临时对象,那么我们把两个对象的指针指向的空间交换即可。一定要返回,因为要实现连续赋值。

    1. //代码五:
    2. Vector& operator=(const Vector v) {
    3. std::swap(_start, v._start);
    4. std::swap(_finish, v._finish);
    5. std::swap(_end, v._end);
    6. return *this;
    7. }

      (7)析构函数

            代码六:这里检查指针是否为空,虽然经过测试指针指向空值时,delete[] 指针不会报错,但出于谨慎,还是加上检查比较好。

    1. //代码六
    2. ~Vector() {
    3. if (_start) {
    4. delete[] _start;
    5. }
    6. _start = _finish = _end = nullptr;
    7. }

    2.迭代器模块

            迭代器模块的模板参数要单独给出,不可以使用区间构造那块的,因为迭代器不涉及多种元素,它只返回本类的元素迭代器,只有一种。

      (1)正向迭代器

            代码七:

    1. //代码七
    2. typedef T* iterators;
    3. iterators Begin() {
    4. return _start;
    5. }
    6. iterators End() {
    7. return _finish;
    8. }

      (2)反向迭代器

            代码八:反向迭代器可以直接复用正向迭代器。

    1. //代码八
    2. typedef T* iterators;
    3. iterators Rbegin() {
    4. return End();
    5. }
    6. iterators Rend() {
    7. return Begin();
    8. }

    3.容量模块

      (1)获取有效元素个数

            这里就可以体会到指针的便捷了,直接相减就得到了元素个数。

            代码九:

    1. //代码九
    2. size_t Size() const{
    3. return _finish - _start;
    4. }

      (2)获取容量大小

            代码十:道理同上。

    1. //代码十
    2. size_t Capacity()const {
    3. return _end - _start;
    4. }

      (3)判空函数

          代码十一:  这两个指针相同说明并没有元素的存在。

    1. //代码十一
    2. bool Empty()const {
    3. return _start == _finish;
    4. }

      (4)扩容函数

               代码十二:只考虑扩大,不考虑缩小。

    1. //代码十三
    2. void Reserve(size_t newCapacity) {
    3. size_t oldCapacity = Capacity();
    4. size_t oldSize = Size();
    5. T* temp = new T[newCapacity];
    6. //当要设置的新空间大于旧空间
    7. if (newCapacity > oldCapacity) {
    8. //判断首指针是否是空
    9. if (_start) {
    10. //进行深拷贝
    11. //思考:memcpy可以使用吗?
    12. for (size_t i = 0; i < oldSize; i++) {
    13. temp[i] = _start[i];
    14. }
    15. //释放旧空间
    16. delete[] _start;
    17. }
    18. _start = temp;
    19. _finish = _start + oldSize;
    20. _end = _start + newCapacity;
    21. }
    22. }

        在扩容函数中思考一个问题,为什么没有使用memcpy函数?原因是memcpy是按字节拷贝,会有程序崩溃的风险。使用下面的例子来验证。

            代码十三:举例,假设使用扩容函数中使用memcpy函数,并且vector中存储S类型的对象。这里因为是举例子,所以代码给的很粗糙,理解一下意思就好。

            首先使用s1和s2构造v对象,然后进行尾插,这样会导致扩容。

    1. //代码十三
    2. class S {
    3. public:
    4. char* str;
    5. };
    6. int main(){
    7. S s1;
    8. S s2;
    9. vector v{s1,s2};
    10. S s3;
    11. v.push_back(s3);
    12. }

            下面这幅图就是验证图。

            S对象内存储的是指针,构造Vector时里面有两个S类型的元素,两个s元素中分别有一个指针指向一个有"\0"元素的空间。然后尾插一个元素,导致扩容。

            扩容后使用memcpy按字节将vector中的元素拷贝到新空间中。一切看起来都那么完美。可是旧的vector对象中地址指针(蓝色的地址线)需要释放,释放后新空间中的指针(红色地址线)却依然指向被释放的空间。这不就是野指针吗?这样一来,一旦试图访问这块空间,必然程序崩溃。

      (5)设置有效元素个数

            这里要告诉大家,不要使用memset函数。因为memset函数也是按字节将元素直接放到指定空间中。C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

            字符的字节是1字节,如果你要用它放n个T类型数据到指向的空间,它是怎么做的?它会把T类型的数据往空间中的前n个字节,每个字节都放一个!

            代码十四:

    1. //代码十四:
    2. void Resize(size_t newSize, const T& val = T()) {
    3. size_t oldCapacity = Capacity();
    4. size_t oldSize = Size();
    5. //要设置的有效元素个数大于现在的有效元素
    6. if (newSize > oldSize) {
    7. //要设置的有效元素个数大于现有空间,扩容
    8. if (newSize > oldCapacity) {
    9. Reserve(newSize);
    10. }
    11. //思考:memeset可以用吗?
    12. for (size_t i = oldSize; i < newSize; i++) {
    13. _start[i] = val;
    14. }
    15. }
    16. _finish = _start + newSize;
    17. }

    4.元素访问模块

      (1)获取首元素

            代码十五:这里给出两种类型,以便于const对象使用。

    1. //代码十五
    2. T& Front() {
    3. return _start[0];
    4. }
    5. const T& Front()const {
    6. return _start[0];
    7. }

      (2)获取尾元素

            代码十六:两种类型,道理同上。

    1. //代码十六
    2. T& Back() {
    3. return *(_finish - 1);
    4. }
    5. const T& Back()const {
    6. return *(_finish - 1);
    7. }

      (3)重载[]

            代码十七:也是两种类型。注意assert函数使用时需要包含头文件。

    1. //代码十七
    2. #include
    3. T& operator[](size_t index) {
    4. //需要判断是否越界
    5. assert(-1Size());
    6. return _start[index];
    7. }
    8. const T& operator[](size_t index)const {
    9. assert(-1Size());
    10. return _start[index];
    11. }

    5.修改模块

      (1)尾插

            代码十八:尾插可能需要扩容,一旦扩容,就给的空间大一点,尽量减少扩容次数。

    1. //代码十八
    2. void Push_back(const T& val) {
    3. //判断顺序表是否已经存满了
    4. if (_finish == _end) {
    5. Reserve(Capacity() * 2);
    6. }
    7. *_finish = val;
    8. _finish++;
    9. }

      (2)尾删

            代码十九:

    1. //代码十九
    2. void Pop_back() {
    3. if (Empty()) {
    4. return;
    5. }
    6. _finish--;
    7. }

      (3)任意位置插入   

            代码二十:  需要考虑越界问题

    1. //代码二十
    2. iterators Insert(iterators index, const T& val) {
    3. //判断迭代器是否越界
    4. if (index < _start || index > _finish)
    5. return End();
    6. //判断空间是否存满
    7. if (_start == _finish)
    8. Reserve(Capacity() * 2);
    9. auto it = _finish;
    10. while (it != index) {
    11. *it = *(it - 1);
    12. it--;
    13. }
    14. *index = val;
    15. _finish++;
    16. return index;
    17. }

      (4)指定位置删除

            代码二十一:需要考虑越界问题

    1. //代码二十一
    2. iterators Erase(iterators index) {
    3. //判断迭代器是否越界
    4. if (index < _start || index > _finish)
    5. return End();
    6. auto it = index;
    7. //将index后的元素往前搬移一位
    8. while (it != _finish) {
    9. *it = *(it + 1);
    10. it++;
    11. }
    12. _finish--;
    13. return index;
    14. }

      (5)清空有效元素

            代码二十二:

    1. //代码二十二
    2. void Clear() {
    3. _finish = _start;
    4. }

    6.总体代码

            将代码进行调整后改成整体代码如下:

    1. #include
    2. #include "iostream"
    3. using namespace std;
    4. //Vector类存储的数据是任意类型,所以需要设置模板参数
    5. template<typename T>
    6. class Vector {
    7. private:
    8. T* _start;//指向T类型元素的首元素的指针
    9. T* _finish;//指向最后一个有效元素下一个位置
    10. T* _end;//指向有效空间的下一个位置。
    11. public:
    12. //构造模块
    13. //
    14. //无参构造
    15. Vector()
    16. :_start(nullptr)
    17. ,_finish(nullptr)
    18. ,_end(nullptr)
    19. {}
    20. //半缺省构造
    21. Vector(int n, const T& val = T())
    22. :_start(new T[n])//在初始化时直接从堆上申请空间
    23. {
    24. for (int i = 0; i < n; i++) {
    25. _start[i] = val;
    26. }
    27. //_finish指向最后一个有效元素的下一个位置
    28. _finish = _start + n;
    29. //_end指向顺序表最后一个位置的下一个位置
    30. _end = _finish;
    31. }
    32. //区间构造
    33. template<typename Iterator>
    34. Vector(Iterator first, Iterator last) {
    35. Iterator it = first;
    36. size_t num = 0;//统计元素个数
    37. while (it != last) {
    38. num++;
    39. it++;
    40. }
    41. _start = new T[num];
    42. _finish = _start;
    43. while (first != last) {
    44. *_finish = *first;
    45. _finish++;
    46. first++;
    47. }
    48. _end = _finish;
    49. }
    50. //拷贝构造函数
    51. Vector(const Vector& v)
    52. :_start(new T[v.Size()]) //这里用到的Size函数后面会实现的
    53. {
    54. size_t num = v.Size();
    55. for (size_t i = 0; i < num; i++) {
    56. _start[i] = v._start[i];
    57. }
    58. //让对应的指针指向应该指向的位置
    59. _finish = _start + num;
    60. _end = _finish;
    61. }
    62. //赋值运算符重载
    63. Vector& operator=(const Vector v) {
    64. std::swap(_start, v._start);
    65. std::swap(_finish, v._finish);
    66. std::swap(_end, v._end);
    67. return *this;
    68. }
    69. //析构函数
    70. ~Vector() {
    71. if (_start) {
    72. delete[] _start;
    73. }
    74. _start = _finish = _end = nullptr;
    75. }
    76. //迭代器模块
    77. //
    78. typedef T* iterators;
    79. //正向迭代器
    80. iterators Begin() {
    81. return _start;
    82. }
    83. iterators End() {
    84. return _finish;
    85. }
    86. //反向迭代器
    87. iterators Rbegin() {
    88. return End();
    89. }
    90. iterators Rend() {
    91. return Begin();
    92. }
    93. //容量模块
    94. //
    95. //有效元素个数
    96. size_t Size() const{
    97. return _finish - _start;
    98. }
    99. //容量大小
    100. size_t Capacity()const {
    101. return _end - _start;
    102. }
    103. //判空函数
    104. bool Empty()const {
    105. return _start == _finish;
    106. }
    107. //扩容函数
    108. void Reserve(size_t newCapacity) {
    109. size_t oldCapacity = Capacity();
    110. size_t oldSize = Size();
    111. T* temp = new T[newCapacity];
    112. //当要设置的新空间大于旧空间
    113. if (newCapacity > oldCapacity) {
    114. //判断首指针是否是空
    115. if (_start) {
    116. //进行深拷贝
    117. //思考:memcpy可以使用吗?
    118. for (size_t i = 0; i < oldSize; i++) {
    119. temp[i] = _start[i];
    120. }
    121. //释放旧空间
    122. delete[] _start;
    123. }
    124. _start = temp;
    125. _finish = _start + oldSize;
    126. _end = _start + newCapacity;
    127. }
    128. }
    129. //设置有效元素函数
    130. void Resize(size_t newSize, const T& val = T()) {
    131. size_t oldCapacity = Capacity();
    132. size_t oldSize = Size();
    133. //要设置的有效元素个数大于现在的有效元素
    134. if (newSize > oldSize) {
    135. //要设置的有效元素个数大于现有空间,扩容
    136. if (newSize > oldCapacity) {
    137. Reserve(newSize);
    138. }
    139. //思考:memeset可以用吗?
    140. for (size_t i = oldSize; i < newSize; i++) {
    141. _start[i] = val;
    142. }
    143. }
    144. _finish = _start + newSize;
    145. }
    146. //元素访问模块
    147. //
    148. //获取首元素
    149. T& Front() {
    150. return _start[0];
    151. }
    152. const T& Front()const {
    153. return _start[0];
    154. }
    155. //获取尾部元素
    156. T& Back() {
    157. return *(_finish - 1);
    158. }
    159. const T& Back()const {
    160. return *(_finish - 1);
    161. }
    162. //重载[]
    163. T& operator[](size_t index) {
    164. //需要判断是否越界
    165. assert(-1Size());
    166. return _start[index];
    167. }
    168. const T& operator[](size_t index)const {
    169. assert(-1Size());
    170. return _start[index];
    171. }
    172. //修改模块
    173. //
    174. //尾插
    175. void Push_back(const T& val) {
    176. //判断顺序表是否已经存满了
    177. if (_finish == _end) {
    178. Reserve(Capacity() * 2);
    179. }
    180. *_finish = val;
    181. _finish++;
    182. }
    183. //尾删
    184. void Pop_back() {
    185. if (Empty()) {
    186. return;
    187. }
    188. _finish--;
    189. }
    190. //任意位置的插入
    191. iterators Insert(iterators index, const T& val) {
    192. //判断迭代器是否越界
    193. if (index < _start || index > _finish)
    194. return End();
    195. //判断空间是否存满
    196. if (_start == _finish)
    197. Reserve(Capacity() * 2);
    198. auto it = _finish;
    199. while (it != index) {
    200. *it = *(it - 1);
    201. it--;
    202. }
    203. *index = val;
    204. _finish++;
    205. return index;
    206. }
    207. //任意位置删除
    208. iterators Erase(iterators index) {
    209. //判断迭代器是否越界
    210. if (index < _start || index > _finish)
    211. return End();
    212. auto it = index;
    213. //将index后的元素往前搬移一位
    214. while (it != _finish) {
    215. *it = *(it + 1);
    216. it++;
    217. }
    218. _finish--;
    219. return index;
    220. }
    221. //清空有效元素
    222. void Clear() {
    223. _finish = _start;
    224. }
    225. };
    226. int main() {
    227. Vector<int> v(10, 5);
    228. v.Push_back(9);
    229. v.Erase(v.End());
    230. }

  • 相关阅读:
    用DIV+CSS技术设计的凤阳旅游网站(web前端网页制作课作业)HTML+CSS+JavaScript
    如何理解ROS的ros::NodeHandle,学习ROS一年后的体会
    HTTP 原理与CND原理
    A40I工控主板(SBC-X40I)CAN接口测试
    Vue数据响应原理及Diff比对
    【干货】交换机的接口类型完全实物了解
    用C++写个示例 linux WebAssembly技术支持的js调用串行通信
    学成在线第一天
    NFT交易系统平台开发 数字藏品平台开发解决方案:区块链+艺术品搭建 打造元宇宙市场数字化经济
    django配置静态文件
  • 原文地址:https://blog.csdn.net/weixin_57761086/article/details/126577389