内容摘要

本文内容包括多态的概念(通过例子进行理解)、多态的定义和实现包括形成多态的两个必要条件和虚函数的概念、虚函数的重写的详细介绍、抽象类的概念、多态原理的分析,以及单继承表和多继承表的原理 。文章附代码图题解,非常详细。

多态的概念

多态是面向对象语言中一个重要概念,它允许不同的对象通过相同的方式被处理。简单来说就是不同类的对象执行同一行为产生不同的结果,也就是继承基类的不同子类,去调用基类中的函数时产生不同的影响

举个栗子:当我们通过平台进行买票这个行为时,不同的对象在进行买票这个行为会产生不同的结果。例如当我们是学生进行买票这个行为时,会出现折扣,这里的折扣就是学生这个对象在进行买票时产生的影响,当购票的人是消防员、军人时,会享有优先购票的。

多态的定义和实现

形成多态的两个必要条件

  • 基类被调用的函数必须是虚函数,派生类还必须进行虚函数的重写
  • 必须通过基类的指针或引用调用虚函数

虚函数

通过关键字virtual 将函数变成虚函数

虚函数的重写

概念:派生类通过继承基类,并且基类中的函数是虚函数,在派生类中写一个函数名、函数返回类型、函数参数的类型都相同(协变除外),则派生类中的这个函数就是基类中相对应函数的重写。

注意:派生类中进行重写的函数可以不用+virtual,派生类会将重写函数的虚性进行继承,所以这里的关键字是可以不写的。这里其实体现了C++中的接口继承的思想,当派生类进行函数的重写时,其实是继承了基类函数的结构,只是将函数中的内容进行重写。

虚函数重写的两个特例

协变

协变是完成函数重写的特例,当函数返回值类型不同时也有可能构成虚函数的重写,这种情况下就是构成了协变。

协变形成的条件

基类中被重写的函数的返回值是基类指针或引用(这里的基类是可以是别的类的基类),派生类中进行重写的函数是派生类的指针或引用(这里派生类也是可以是其他类的派生类)

以下这种情况也是可以的

析构函数

派生类和基类中的析构函数也构成函数的重写,我们表面上看基类中的析构函数和派生类中的析构函数函数名是不同的,不应该构成函数的重写多态是通过继承进行实现的,但是当构成多态的子类要进行delete会调用析构函数,要是不构成函数重写的话,无法进行相对应的资源释放,所以编译器底层进行实将析构函数都命名为 destructor  

C++11 

虚函数的重写在多态中是十分重要的,C++11中关于虚函数的重写定义了两个关键字override和final,防止我们忘记进行重写,增加容错率

override

检查派生类中是否进行重写了基类中的某个虚函数,如果没有进行重写则会出现编译报错。

final

不允许基类中的虚函数进行重写


抽象类

将虚函数进行为0的赋值操作,这个函数称为纯虚函数,包含纯虚函数的类称为抽象类,抽象类是不能进行实例出对象的,当基类是抽象类,派生类只有将基类中的纯虚函数进行重写后才能进行实例化对象。 

抽象类的目的其一就是就是为了强制子类中的虚函数要进行重写,防止派生类中的重写被忘掉;抽象类的本质就是提供基类的虚函数接口,满足接口继承的思想,派生类中继承基类重写的只是函数中的内容,函数结构是继承基类的。

多态的原理

虚函数表

我们思考Base这个类的大小是多大???

我想很多人都会觉得哎呀,8嘛,结构体中的内存对其原则嘛,这也是我刚开始学的时候的样子,我们通过实践进行验证

进行调试

b对象中除了_a, _b还多出来一个指针(函数指针数组的指针),原来多出来的四字节就是这个指针进行导致的,其中这个函数指针数组的指针称为虚函数表指针,所以当我们计算类的大小时遇到类中包含虚函数时一定要十分小心,多考虑一些。

原理

构成多态的一个必要条件是就是必须通过基类的指针或者引用调用虚函数的的原因是,只有当基类的指针或者引用时,才是动态进行调用也称为动态绑定,动态绑定就是当多态中的虚函数的调用在程序运行时才去决定调用的函数,也就是说我们调Func函数时,根据传参的类型去调用相应的函数。我们传参的类型时student时,对象通过其中的虚函数表指针找到相应的虚函数表,虚函数表通过其中存的函数指针去调用相对应的函数。

虚函数表指针指向的是虚函数表,虚函数表中存放的是函数指针,函数指针指向的是类中的虚函数。虚函数是存放在虚表中的,虚表是在编译的时候就生成好了的。

补充:静态绑定

静态绑定是在编译过程中就已经确定了要调用的函数地址,比如函数重载就是属于这种情况、还有就是 如上图要是Func函数的参数不是引用类型也是静态调用。

单继承虚表和多继承虚表

单继承虚表

派生类中的虚函数表是通过基类进行拷贝过来的,当派生类进行虚函数的重写时,虚表中被重写的函数地址就会被覆盖掉,存放派生类中进行重写的地址,因此基类中没有被重写函数的地址还存在子类的虚表中。

我们来看一下下面的这种情况

通过监视窗口我们没有看到派生类中的虚函数fun4,难道这个虚函数没有存放到虚表中吗??其实不是,监视窗口是会骗人的,我们通过内存窗口进行观察

进行验证

多继承虚表

分析和单继承虚表逻辑相同这里就不进行分析啦

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐