多态
- 多态分类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
- 静态多态和动态多态的区别
- 静态多态的函数地址早绑定—编译阶段确定函数地址
- 动态多态的函数地址晚绑定—运行阶段确定函数地址
- 静态多态就是我们常用的那些函数等
- 动态多态满足等条件
- 有继承关系
- 子类要重写(重写和重载是两个不同的概念)父类的虚函数
- 几点分析
- 重载的前提是函数名相同即可,重写的前提是函数类型相同(包括函数名和参数及返回值参数)
- 虚函数即为在父类中用virtual修饰的函数,必须要在子类中重写,子类在重写过程中不必要再次写virtual
- 动态多态使用
- 父类的指针或者引用执行子类对象
void doSpeak(Animal& animal){ animal.speak(); } int main(){ Cat cat; doSpeak(cat); }
- 深入分析多态底层
class Animal{ public: void speak(){ cout << "动物在说话" << endl; } } //sizeof--->1字节 class Animal{ public: virtual void speak(){ cout << "动物在说话" << endl; } } //sizeof--->4字节--->指针
- 上述代码结果其实就反应了其变为一个指针—>vfptr—虚函数指针
- vfptr指向虚函数表vftable
- 父类vftable虚函数表记录虚函数的地址—此例中存&Animal::speak
- 子类的vftable虚函数表记录虚函数的地址—此例中存&Cat::speak
- 当父类的指针或者引用指向子类对象时候,发生多态—Animal& animal = cat; animal.speak();
- 总结
- 就是存在虚函数表,相当于一个函数指针内保存了不同的访问接口(函数入口)
- 保证了接口一样但是内部实现不一样
- 多态实现的方式
- 建立一个抽象类virtual修饰一个函数但父类不做任何实现
- 子类可以更多的实现virtual这个类,通过这种方式来实现不同的功能
- 多态的优点
- 代码结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
- 纯虚函数和抽象类
- 多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此,可以直接将虚函数变为纯虚函数
- 语法
- virtual 返回值类型 函数名(参数列表) = 0;
- 抽象类
- 当类中有了纯虚函数,这个类也称为抽象类
- 抽象类的特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
// 抽象类 class BaseTest{ public: virtual void func() = 0; };
- 注意使用多态的两种方式
Base *p = new Son;// 指针 Son s; Base &p = s;// 引用
- 抽象类和子类的常用背景
- 抽象类更趋向于一种方式
- 子类实现更趋向于一种这个方式在做什么—每一个具体做什么构成一个子类
- 一个案例—武器更换
#include <iostream> #include <string> using namespace std; class Gun{ public: virtual void TakeGun() = 0; virtual void ReloadGun() = 0; virtual void EmissionGun() = 0; virtual void LiftedGun() = 0; void PleaseGun(void){ TakeGun(); ReloadGun(); EmissionGun(); LiftedGun(); } }; class AK47:public Gun{ virtual void TakeGun(){ cout << "takeak47" << endl; } virtual void ReloadGun(){ cout << "reloadak47" << endl; } virtual void EmissionGun(){ cout << "emissionak47" << endl; } virtual void LiftedGun(){ cout << "liftedgunak47" << endl; } }; class M4A1:public Gun{ virtual void TakeGun(){ cout << "takem4a1" << endl; } virtual void ReloadGun(){ cout << "reloadm4a1" << endl; } virtual void EmissionGun(){ cout << "emissionm4a1" << endl; } virtual void LiftedGun(){ cout << "liftedgunm4a1" << endl; } }; void HaveGun(Gun* gun){ gun->PleaseGun(); } int main(){ HaveGun(new AK47); return 0; }
- 虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
- 解决
- 将父类中的析构函数改为虚析构函数或者纯虚析构
- 虚析构和纯虚析构共性
- 可以解决父类指针释放子类对象
- 都需要有具体函数实现
- 虚析构和纯虚析构区别
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
- 纯虚析构和纯虚函数的区别
- 在父类存在内存释放问题时,必须使用析构函数,因此纯虚析构既要声明又要去实现内容
- 父类纯虚函数的实现,可以在类外采用作用域方式进行
- 语法
- 虚析构语法
- virtual ~类名(){}
- 纯虚析构语法
- virtual ~类名() = 0;
- 类名::~类名(){}
- 虚析构语法
- 作用
- 释放内存问题
- 总结
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类