在自己的世界里独善其身,在别人的世界顺其自然
✨写在前面
书接上文, 今天继续学习多态的内容。上文提到了虚函数,那么今天就来讲讲什么是纯虚函数,抽象类和纯虚函数关联紧密,所以也学习一下抽象类以及虚析构和纯虚析构。
✨✨ 感兴趣的同学可以订阅一下我的这个《C++入门》专栏喔~✨✨
✨目录
virtual 返回值类型 函数名 (参数列表) = 0 ;
在多态中,通常父类中虚函数(函数前加virtual)的实现是毫无意义的,因为基本上都是父类引用或指针指向子类,调用子类重写后的函数,因此我们把虚函数的最后加上“= 0”变为纯虚函数。当一个类中存在纯虚函数,那么这个类就被称为抽象类。这时候就可以限制非抽象类子类必须重写纯虚函数,强制子类拥有某些成员方法。
1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,除非子类也是抽象类
- class Animal
- {
- public:
- virtual void sleep() = 0;//纯虚函数
- };
- class Dog :public Animal
- {
- public:
- void sleep()
- {
- cout << "狗睡在狗窝" << endl;
- }
- };
- class Cat :public Animal
- {
- public:
- void sleep()
- {
- cout << "猫睡在猫窝" << endl;
- }
- };
- void Sleep(Animal *animal)
- {
- animal->sleep();
- }
- void Sleep(Animal &animal)
- {
- animal.sleep();
- }
- int main()
- {
- Animal *dog = new Dog;
- Sleep(dog);
- delete dog;
- Animal *cat = new Cat;
- Sleep(cat);
- delete cat;
- Dog d;
- Sleep(d);
- }
这里的Animal类是抽象类,里面只有一个sleep的纯虚函数。然后让Dog类和Cat类公共继承Animal类,并重写sleep函数。之前提到多态的使用可以是父类引用或者指针,所以我写了一个Sleep函数,参数列表是父类的指针,随后又进行了重载,改为父类引用。然后在主函数中用父类指针在堆区开辟子类的对象,调用之后就删除掉,以免占用堆区空间,最后单独创建Dog类对象d,调用重载的Sleep函数。
运行效果:
意义:解决父类指针来释放子类对象。
原因:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
共性:1、都能解决父类指针来释放子类对象
2、都要有具体函数的实现,即有声明有实现
区别:存在纯虚析构,则该类为抽象类,无法实例化对象
- class Animal
- {
- public:
- virtual void eat() = 0;
- Animal()
- {
- cout << "父类的构造函数调用" << endl;
- }
- ~Animal()
- {
- cout << "父类的析构函数调用" << endl;
- }
- };
- class Dog :public Animal
- {
- public:
- string *name;
- Dog(string name)
- {
- cout << "子类的构造函数调用" << endl;
- this->name = new string(name);
- }
- ~Dog()
- {
- cout << "子类的析构函数调用" << endl;
- delete name;
- name = NULL;
- }
- void eat()
- {
- cout << *name<<"在啃骨头" << endl;
- }
- };
- void test03()
- {
- Animal* dog = new Dog("拉布拉多");
- dog->eat();
- delete dog;
- }
这里的Animal类作为抽象父类,里面有纯虚函数eat,此外加上构造和析构函数,用来提示对象函数的调用情况。Dog类公共继承Animal类,并增加string指针类型的name属性,目的就是在堆区开辟数据。Dog类也加上构造函数的调用提示以及析构函数对堆区数据进行释放,另外一定要对eat方法进行重写,这是抽象类的规定,否则Dog类也会是抽象类且无法实例化对象。
运行效果:
我们知道有继承关系的构造顺序是先构造父类,后构造子类,而析构顺序相反。但是父类的析构调用前并没有执行子类的析构代码,分析一下:Animal* dog = new Dog("拉布拉多");这里的dog对象是父类引用,那么再执行析构的时候只会调用父类的析构,而进不去子类的析构。解决办法就是利用虚析构或者纯虚析构:
- //虚析构的实现
- virtual ~Animal()
- {
- cout << "父类的析构函数调用" << endl;
- }
- //纯虚析构的声明
- virtual ~Animal() = 0;
- //纯虚析构的实现
- Animal::~Animal()
- {
- cout << "父类的析构函数调用" << endl;
- }
运行效果:
这样就解决了父类指针无法释放子类对象的问题,析构前加上virtual关键字后,变为虚析构,这样编译器再调用析构函数时会调用到子类的析构函数,完成堆区对象属性的清理。
小结:
并不是只要写多态都要写虚析构或者纯虚析构,只有在对象在堆区开辟属性的时候才加上virtual来调用子类析构完成堆区属性的清理
到这里多态的内容基本就结束了,接下来我会分享三个具体的案例作为我们多态的实战,巩固所学知识,下篇博客我们不见不散!感觉写得好就支持博主一下,你的三连是我进步的最大动力!!!