今天学习黑马视频中的类的特性之一:多态,根据学习内容进行复习,如下,参考《黑马》——C++核心编程_漏网小鱼的博客-CSDN博客,若侵权,请告知,会删。
- 基本语法
- 原理剖析
- 纯虚函数和抽象类
- 虚析构和纯虚析构
多态分类:
静态多态:函数重载和运算符重载等,复用了函数名
动态多态:派生类和虚函数在运行时多态
静态多态为函数地址早绑定,在编译阶段已经确定函数地址
多态多态为函数地址晚绑定,在运行时确定函数地址
1、基本语法
满足条件:
- 有继承关系
- 子类重写父类中的虚函数
使用方法:
- 父类指针或引用指向子类对象
- 调用的为父类中的(虚)函数,不同的接口进入
如:Cat cat; Animal &animal =cat(类实例化对象);
Animal *animal=new Cat(类);
实例化的为一个子类(可以直接类实例化如Cat cat,也可以在堆区开辟一块新的内存空间如new Cat),然后用父类指针或者引用指向该子类,然后(可以初始化父类中的成员数据,)调用父类中的(虚)函数即实现多态。相当于后续操作都是针对父类,只是前期将父类指针或引用指向子类,父类中的函数为虚函数,所以会从子类中进入。
个人理解为父类指针或引用指向子类对象,当调用父类中的(虚)函数时,会提供不同的接口,根据下一节知识可知,父类中当有虚函数时,会有一个虚函数指针vfptr,该指针会指向虚函数表vftable,而由于指针的指向为子类,实际运行函数会为子类中重写的父类虚函数,所以当父类指针或引用指向不同的子类对象时,会从不同的接口进入运行。
重写的定义:函数返回值类型,函数名,参数列表完全一致
多态优点:
- 代码结构清晰
- 可读性强
- 利于前期和后期的扩展和维护
提倡多态程序架构,好处多多~
2、原理剖析
当父类中只有函数时,为1个字节,当该函数变为虚函数时,为4个字节,因为存储变成了一个指针。
参考:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili
Animal中有一个4个字节的指针vfptr,该指针指向虚函数表, 虚函数表记录的为虚函数函数入口的地址,当子类重写父类的虚函数时,子类中的虚函数表会替换成子类的虚函数地址,当父类的指针或者引用指向子类对象时,本身还是子类对象,如创建的是cat对象,当调用公共接口speak时,会从子类中找函数的实际入口地址即Cat::speak。
开发命令窗口验证:
Animal 类
Cat类speak函数没有重写时,直接继承Animal类:speak函数继承Animal::speak
Cat类speak函数重写时:speak变成Cat::speak
3、纯虚函数和抽象类
多态中父类的虚函数的实现都是无意义的,因为调用的都是子类中的重写的内容,因此将虚函数改为纯虚函数。
语法:virtual 返回值类型 函数名(参数列表)=0;
如:virtual void func() = 0;
当类中有纯虚函数时,该类也称为抽象类。
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,不然子类也属于抽象类
4、虚析构和纯虚析构
当子类中有属性开辟到堆区时,父类指针在释放时无法调用子类的析构函数,造成内存泄漏,将父类中的析构函数改为虚析构或者纯虚析构可以解决该问题。若子类中无属性开辟到堆区,则不需要父类中的析构函数改为虚析构和纯虚析构。
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要具体的函数实现
虚析构和纯虚析构个性:
- 纯虚析构所在的类为抽象类,无法实例化对象
虚析构:virtual ~类名(){}
纯虚析构:virtual ~类名() = 0;
类名::~类名(){}
(纯虚析构函数相当于类内声明,类外实现)
释放子类堆区对象:
~Cat()
{
cout<<"Cat析构函数调用"<<endl;
if (this->m_Name!=NULL)
{
delete m_Name;
m_Name=NULL;
}
}