• C++类模板实战之手写精简版vector容器,详解版


    ✨引言

            上一篇博客讲了类模板的基本使用,那么这篇博客就来做一个类模板实战。让我们自己封装一个数组类,可以适应基本数据类型和自定义数据类型。

            《C++提高编程》专栏主要针对C++泛型编程和STL技术做详细讲解,深入研究C++的使用,对C/C++感兴趣的小伙伴可以订阅专栏共同学习,专栏还在持续更新中✨

    目录

    案例要求

    完成步骤

    1、封装数组类属性并完成有参构造以及析构函数

    2、提供对应的深拷贝构造函数防止调用析构时出错

    3、重载类内的赋值运算符防止浅拷贝问题出现

    4、提供尾部插入和删除的方法

    5、重载[]得到数组中对应下标的数据信息

    6、提供get方法获取当前数组容量及大小

    7、提供打印函数测试基本数据类型和自定义数据类型的存储

    ✨总结 


    案例要求

    • 可以对内置数据类型以及自定义数据类型的数据进行存储
    • 将数组中的数据存储到堆区
    • 构造函数中可以传入数组的容量
    • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
    • 提供尾插法和尾删法对数组中的数据进行增加和删除
    • 可以通过下标的方式访问数组中的元素
    • 可以获取数组中当前元素个数和数组的容量

    完成步骤

    1、封装数组类属性并完成有参构造以及析构函数

    1. #pragma once
    2. #include<iostream>
    3. using namespace std;
    4. template<class T>
    5. class Arrays
    6. {
    7. private:
    8. T* arr;//数组arr存放T类型的数据
    9. int capacity;//数组容量
    10. int size;//数组大小
    11. public:
    12. Arrays(int capacity)
    13. {
    14. this->capacity = capacity;
    15. this->size = 0;
    16. this->arr = new T[this->capacity];
    17. }
    18. ~Arrays()
    19. {
    20. if (this->arr != NULL)
    21. {
    22. delete []this->arr;
    23. this->arr = NULL;
    24. }
    25. }
    26. };

            我把自己的这个数组类模板放到一个.hpp文件里,方便测试的时候调用。代码第一行是为了防止头文件重复包含,template里面的T就是数组的数据类型,根据调用时不同的指定存放不同类型的数据。将数组arr以及数组容量和大小进行封装,写在私有权限下即可 。然后提供该类的有参构造,参数列表传入的是数组容量,有参构造初始化了数组的容量以及大小并将数组开辟到了堆区。析构函数就是来清理堆区数据,如果我们开辟的堆区数组不为空,那就清理掉并将其指向NULL,这样可以防止野指针出现,避免异常。

    2、提供对应的深拷贝构造函数防止调用析构时出错

    1. Arrays(const Arrays& p)
    2. {
    3. this->capacity = p.capacity;
    4. this->size = p.size;
    5. this->arr = new T[p.capacity];
    6. for (int i = 0; i < this->size; i++)
    7. {
    8. this->arr[i] = p.arr[i];
    9. }
    10. }

            如果不提供深拷贝,那么编译器就会有:this->arr=p->arr 这行代码 ,那么一旦我们调用编译器提供的浅拷贝,当运行到析构函数时,就会出现重复删除地址的情况,必然会出现程序错误。所以我们要自己提供深拷贝构造函数,将上面的代码改为 this->arr= new T[p.capacity] ,这样调用析构的时候各自删除各的堆区数据,不会出现上述情况。最后利用for循环将传进来的对象的数据赋值给新开辟的数组。

    3、重载类内的赋值运算符防止浅拷贝问题出现

    1. Arrays& operator=(const Arrays& p)
    2. {
    3. if (this->arr!=NULL)
    4. {
    5. delete []this->arr;
    6. this->arr = NULL;
    7. this->capacity = 0;
    8. this->size = 0;
    9. }
    10. //深拷贝过程
    11. this->capacity = p.capacity;
    12. this->size = p.size;
    13. this->arr = new T[this->capacity];
    14. for (int i = 0; i < p.size; i++)
    15. {
    16. this->arr[i] = p.arr[i];
    17. }
    18. return *this;
    19. }

            当数组数据是对象的类型时,不能简单的将数组进行赋值操作,因为也牵扯到直接赋值出现一样的数组地址的情况,存在着深浅拷贝问题。赋值的时候是将传入参数的数据赋值给自己,因此先把自己的属性清空,然后就是深拷贝的实现了。最后返回的是*this,this指针能够指向不同成员属性,那么*this就是对象本身,然后看到返回值类型是对象引用,这样就可以实现对象间的连续赋值了。

    4、提供尾部插入和删除的方法

    1. void insert_Arrays(const T&value)
    2. {
    3. if (this->capacity == this->size)
    4. {
    5. return;
    6. }
    7. this->arr[size] = value;
    8. this->size++;
    9. }
    10. void delete_Arrays()
    11. {
    12. if (this->size == 0)
    13. {
    14. return;
    15. }
    16. this->size--;
    17. }

            尾插过程:先判断数组是否已经满了,如果不满就将形参赋值给当前数组最后一个下标的位置,然后更新数组下标,这样就能保证每次插入的数据都在数组末尾 

            尾删的实现:先判断数组是否为空,不为空的时候直接把数组大小减一即可,让编译器访问不到当前的最后一个数组元素。注意尾删的只是数据的指针,数组的地址并未删除。

    5、重载[]得到数组中对应下标的数据信息

    1. T& operator[](int index)
    2. {
    3. return this->arr[index];
    4. }

           如果数组内容是对象类型,是不存在对象数组的,所以要对[]运算符进行重载。返回值类型为数据类型的引用,也就是具体的数组内的值,传进去的整型参数就是数组下标。

    6、提供get方法获取当前数组容量及大小

    1. int getSize()
    2. {
    3. return this->size;
    4. }
    5. int getCapacity()
    6. {
    7. return this->capacity;
    8. }

            这里就是经典的get方法了,返回对应封装的成员属性 ,不做多解释。

    7、提供打印函数测试基本数据类型和自定义数据类型的存储

    1. #include"arrays.hpp"
    2. class Hero
    3. {
    4. friend void printHero(Arrays<Hero>&hero);
    5. private:
    6. string name;
    7. string position;
    8. public:
    9. Hero() {}
    10. Hero(string name, string position)
    11. {
    12. this->name = name;
    13. this->position = position;
    14. }
    15. };
    16. void printArrays(Arrays<int>arr)
    17. {
    18. for (int i = 0; i < arr.getSize(); i++)
    19. {
    20. cout << arr[i] << " ";
    21. }
    22. cout << endl;
    23. }
    24. void printHero(Arrays<Hero>&hero)
    25. {
    26. for (int i = 0; i < hero.getSize(); i++)
    27. {
    28. cout << "姓名:"<<hero[i].name<<" 位置:"<<hero[i].position<<endl;
    29. }
    30. }
    31. void test()
    32. {
    33. cout << "普通类型数组测试:" << endl;
    34. cout << "输入数组容量为:" ;
    35. int n = 0; cin >> n;
    36. Arrays<int> array(n);
    37. cout << "输入数据:";
    38. for (int i = 0; i < array.getCapacity(); i++)
    39. {
    40. int value = 0;
    41. cin >> value;
    42. array.insert_Arrays(value);
    43. }
    44. cout << "打印数组信息:"<<endl;
    45. printArrays(array);
    46. array.delete_Arrays();
    47. cout << "删除一次尾部数据后打印数组信息:" << endl;
    48. printArrays(array);
    49. }
    50. void test1()
    51. {
    52. cout << "自定义类型数组测试:" << endl;
    53. Hero h1("火舞","中单");
    54. Hero h2("韩信","打野");
    55. Hero h3("桑启","游走");
    56. Hero h4("守约","发育");
    57. Hero h5("关羽","对抗");
    58. Arrays<Hero> array(5);
    59. array.insert_Arrays(h1);
    60. array.insert_Arrays(h2);
    61. array.insert_Arrays(h3);
    62. array.insert_Arrays(h4);
    63. array.insert_Arrays(h5);
    64. printHero(array);
    65. array.delete_Arrays();
    66. cout << "删除一次尾部数据后打印数组信息:"<<endl;
    67. printHero(array);
    68. }

            首先引入之前封装的数组类头文件,提供printArrays和printHero函数来进行数组信息的打印,test和test1函数分别是整型数组对象数组的测试。接下来看运行效果。

    运行效果:

    总结 

            这里封装的数组类和后面即将提到的vector容器有不少相似之处,那么下一篇博客就会分享vector容器的知识了,希望这种讲解能让大家容易的理解,我们下篇博客不见不散!

  • 相关阅读:
    文件上传失败原因汇总(个人情况总结)
    SSM - Springboot - MyBatis-Plus 全栈体系(二十)
    概率论与数理统计学习:随机事件(二)——知识总结与C语言实现案例
    48V LDO三端稳压IC 60v 100V 300V电源降压芯片系统解决方案
    QT lambda表达式
    SQLServer和Oracle,存储过程区别,常用函数对比
    从零手写实现 nginx-19-HTTP CORS(Cross-Origin Resource Sharing,跨源资源共享)介绍+解决方案
    Java学习 ---- 类变量(静态变量)
    猿创征文 | 常见的五款BI报表介绍
    Kafka系列之:Kafka Connect错误报告设置
  • 原文地址:https://blog.csdn.net/m0_58618795/article/details/125592238