💯 博客内容:多态
😀 作 者:陈大大陈
🚀 个人简介:一个正在努力学技术的准C++后端工程师,专注基础和实战分享 ,欢迎私信!
💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘
目录
虚函数继承的条件
普通调用
多态调用
例:
易错练习题
override 和 final
纯虚函数
多态的本质
为什么对象不能实现多态?
虚函数继承的条件
1.虚函数重写
2.必须是父类的引用或者指针来调用
普通调用
调用函数的类型是谁,就调用哪个类型的函数
多态调用
调用指针或者引用指向的对象,指向父类就调用父类,指向子类就调用子类。
例:
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:virtual void BuyTickets(){cout << "全价买票" << endl;}
};
class Student :public Person
{virtual void BuyTickets(){cout << "半价买票" << endl;}
};
void Func(Person& p)
{p.BuyTickets();
}
int main()
{Student st;Func(st);Person p;Func(p);}
上面的代码要是把引用去了,就是两个全价买票,因为普通调用只看参数类型。
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:virtual void BuyTickets(){cout << "全价买票" << endl;}~Person(){cout << "~Person" << endl;}
};
class Student :public Person
{
public:virtual void BuyTickets(){cout << "半价买票" << endl;}~Student(){cout << "~Student" << endl;}
};
void Func(Person& p)
{p.BuyTickets();
}
int main()
{Student st;Func(st);Person p;Func(p);}
一般情况下,析构没有问题。
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:virtual void BuyTickets(){cout << "全价买票" << endl;}~Person(){cout << "~Person" << endl;}
};
class Student :public Person
{
public:virtual void BuyTickets(){cout << "半价买票" << endl;}~Student(){cout << "~Student" << endl;}
};
void Func(Person& p)
{p.BuyTickets();
}
int main()
{/*Student st;Func(st);Person p;Func(p);*/Person* ptr = new Person;delete ptr;ptr = new Student;delete ptr;}
在new的情况下就会有问题,因为析构没有满足多态的条件,所以析构是普通调用。
加上virtual即可。
接下来来看一道易错练习题。
易错练习题
答案是B,容易选成D,需要注意的是,虚函数继承声明,重写实现。
派生类可以不加virtual,所以B的func接口继承了A。
所以val的值是1。
之后B对象切片后传参调用A的test,A的test调用B的func。
如果调用的语句是,p->func(),就不构成多态,答案会是B->0。
override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
1. final:修饰虚函数,表示该虚函数不能再被重写
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car{ public:virtual void Drive() final {}};class Benz :public Car{ public:virtual void Drive() {cout << "Benz-舒适" << endl;}};
class Car{public:virtual void Drive(){}};class Benz :public Car {public:virtual void Drive() override {cout << "Benz-舒适" << endl;}};
纯虚函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class Car
{ public:virtual void Drive() = 0;
};class Benz :public Car{ public:virtual void Drive(){cout << "Benz-舒适" << endl;} };class BMW :public Car{ public:virtual void Drive(){cout << "BMW-操控" << endl;} };void Test(){Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive(); }
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class base
{
public:virtual void func1(){cout << "func1()" << endl;}virtual void func2(){cout << "func2()" << endl;}void func3(){cout << "func3()" << endl;}
private:int _b = 1;char _ch;
};int main()
{cout << sizeof(base) << endl;base b;return 0;
}
虚函数里会存一个虚函数表指针来保存地址。
vfptr(virtual function pointer);
它是一个函数指针数组。
父类和派生类的虚表指针不尽相同,重写了哪个虚函数,哪个虚表指针就会被覆盖。
也就是说:
至此,我们明白了多态的本质
多态的本质
多态为什么能指向父类调父类,指向子类调子类?
虚函数的重写也叫虚函数的覆盖。
编译器首先在对象里找到虚函数表的地址。
查找虚函数表中存储的地址,是父类就调用父类,是子类就切割成父类对象。
是因为虚表指针会去寻找所调用的类的地址。
为什么对象不能实现多态?
因为指针和引用可以指向子类对象中切割出来的父类的一部分。
而对象的拷贝不会拷贝虚表指针。
如果拷贝虚表指针的话,父类和子类的虚函数以及析构函数极易混淆,造成报错。