• <C++>多态之纯虚函数与抽象类,学习不一样的析构函数


    在自己的世界里独善其身,在别人的世界顺其自然

    写在前面

            书接上文,  今天继续学习多态的内容。上文提到了虚函数,那么今天就来讲讲什么是纯虚函数,抽象类和纯虚函数关联紧密,所以也学习一下抽象类以及虚析构和纯虚析构。

           ✨✨ 感兴趣的同学可以订阅一下我的这个《C++入门》专栏喔~✨✨


    目录

    纯虚函数与抽象类

    纯虚函数语法

    纯虚函数的意义 

    抽象类特点

    实例讲解

    虚析构与纯虚析构

    基本概念

    虚析构和纯虚析构的异同

    代码详解

    ✨总结 


    纯虚函数与抽象类

    纯虚函数语法

            virtual 返回值类型 函数名 (参数列表) = 0 ;

    纯虚函数的意义 

            在多态中,通常父类中虚函数(函数前加virtual)的实现是毫无意义的,因为基本上都是父类引用或指针指向子类,调用子类重写后的函数,因此我们把虚函数的最后加上“= 0”变为纯虚函数。当一个类中存在纯虚函数,那么这个类就被称为抽象类。这时候就可以限制非抽象类子类必须重写纯虚函数,强制子类拥有某些成员方法。

    抽象类特点

            1、无法实例化对象

            2、子类必须重写抽象类中的纯虚函数,除非子类也是抽象类

    实例讲解

    1. class Animal
    2. {
    3. public:
    4. virtual void sleep() = 0;//纯虚函数
    5. };
    6. class Dog :public Animal
    7. {
    8. public:
    9. void sleep()
    10. {
    11. cout << "狗睡在狗窝" << endl;
    12. }
    13. };
    14. class Cat :public Animal
    15. {
    16. public:
    17. void sleep()
    18. {
    19. cout << "猫睡在猫窝" << endl;
    20. }
    21. };
    22. void Sleep(Animal *animal)
    23. {
    24. animal->sleep();
    25. }
    26. void Sleep(Animal &animal)
    27. {
    28. animal.sleep();
    29. }
    30. int main()
    31. {
    32. Animal *dog = new Dog;
    33. Sleep(dog);
    34. delete dog;
    35. Animal *cat = new Cat;
    36. Sleep(cat);
    37. delete cat;
    38. Dog d;
    39. Sleep(d);
    40. }

            这里的Animal类是抽象类,里面只有一个sleep的纯虚函数。然后让Dog类和Cat类公共继承Animal类,并重写sleep函数。之前提到多态的使用可以是父类引用或者指针,所以我写了一个Sleep函数,参数列表是父类的指针,随后又进行了重载,改为父类引用。然后在主函数中用父类指针在堆区开辟子类的对象,调用之后就删除掉,以免占用堆区空间,最后单独创建Dog类对象d,调用重载的Sleep函数。

    运行效果:

    虚析构与纯虚析构

    基本概念

    意义解决父类指针来释放子类对象。

    原因:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

    虚析构和纯虚析构的异同

    共性:1、都能解决父类指针来释放子类对象
               2、都要有具体函数的实现,即有声明有实现
    区别:存在纯虚析构,则该类为抽象类,无法实例化对象

    代码详解

    1. class Animal
    2. {
    3. public:
    4. virtual void eat() = 0;
    5. Animal()
    6. {
    7. cout << "父类的构造函数调用" << endl;
    8. }
    9. ~Animal()
    10. {
    11. cout << "父类的析构函数调用" << endl;
    12. }
    13. };
    14. class Dog :public Animal
    15. {
    16. public:
    17. string *name;
    18. Dog(string name)
    19. {
    20. cout << "子类的构造函数调用" << endl;
    21. this->name = new string(name);
    22. }
    23. ~Dog()
    24. {
    25. cout << "子类的析构函数调用" << endl;
    26. delete name;
    27. name = NULL;
    28. }
    29. void eat()
    30. {
    31. cout << *name<<"在啃骨头" << endl;
    32. }
    33. };
    34. void test03()
    35. {
    36. Animal* dog = new Dog("拉布拉多");
    37. dog->eat();
    38. delete dog;
    39. }

            这里的Animal类作为抽象父类,里面有纯虚函数eat,此外加上构造和析构函数,用来提示对象函数的调用情况。Dog类公共继承Animal类,并增加string指针类型的name属性,目的就是在堆区开辟数据。Dog类也加上构造函数的调用提示以及析构函数对堆区数据进行释放,另外一定要对eat方法进行重写,这是抽象类的规定,否则Dog类也会是抽象类且无法实例化对象。

    运行效果:

            我们知道有继承关系的构造顺序是先构造父类,后构造子类,而析构顺序相反。但是父类的析构调用前并没有执行子类的析构代码,分析一下:Animal* dog = new Dog("拉布拉多");这里的dog对象是父类引用,那么再执行析构的时候只会调用父类的析构,而进不去子类的析构。解决办法就是利用虚析构或者纯虚析构:

    1. //虚析构的实现
    2. virtual ~Animal()
    3. {
    4. cout << "父类的析构函数调用" << endl;
    5. }
    6. //纯虚析构的声明
    7. virtual ~Animal() = 0;
    8. //纯虚析构的实现
    9. Animal::~Animal()
    10. {
    11. cout << "父类的析构函数调用" << endl;
    12. }

    运行效果:

            这样就解决了父类指针无法释放子类对象的问题,析构前加上virtual关键字后,变为虚析构,这样编译器再调用析构函数时会调用到子类的析构函数,完成堆区对象属性的清理。

    小结:

            并不是只要写多态都要写虚析构或者纯虚析构,只有在对象在堆区开辟属性的时候才加上virtual来调用子类析构完成堆区属性的清理 

    ✨总结 

            到这里多态的内容基本就结束了,接下来我会分享三个具体的案例作为我们多态的实战,巩固所学知识,下篇博客我们不见不散!感觉写得好就支持博主一下,你的三连是我进步的最大动力!!!

  • 相关阅读:
    [附源码]计算机毕业设计JAVA游戏账号交易平台
    QT c++ 堆栈一些理解--限制对象建立在栈上--栈堆区别
    逐点流行正则化方法及其在半监督学习上的应用
    Python基础入门例程8-NP8 为整数增加小数点
    王道链表综合题(下)
    Spring -- Spring jar 包及依赖
    前端面试题
    探索RPA流程自动化在不同行业的应用案例
    [架构之路-230]:计算机硬件与体系结构 - 可靠性、可用性、稳定性;MTTF、MTTR、MTBF
    工业互联网企业身份与访问控制课题研究与探索
  • 原文地址:https://blog.csdn.net/m0_58618795/article/details/125466925