各位村友大家好,好久不见。

之前和学弟聊天,他们不管是在保研,还是在找工作面试的时候,只要问到了C++,那么大多都被问到了一个问题:什么是C++的多态???

本来就想来说这样一个技术点哈,那借此机会,我们就来好好地说一说。那我们这篇文章呢,主要是帮助大家理解C++中的多态性,那么尽量从原理的角度出发,和大家探讨。

那在介绍这篇文章前,先和大家来聊一个事情hhh。就是村长在这段时间消失了很久,一直没有出现,最主要的原因呢,就是一直在筹备一件大事情——就是筹备构建一个更加私域的、没有杂音、没有广告、交流更加充分的知识社区。那么,有很多东西,如果在公众号放不上了的话,村长呢就会放到那个社区里。

那么这个社区就是Yetu社区。大家可以扫描下方的二维码加入就可以啦。也可以通过公众号菜单栏进行获取加入,或者在后台回复“社区”也可以获取二维码加入。

然后这个社区将会倾注我们的全部心血。也是刚刚建立,所以可能相对于别的博主会便宜许多,后续当我们有更加完善的服务了之后,价格肯定也是会有所调整的哈。

图片

当然了,如果没有关注我们公众号的朋友,可以先关注我们的公众号,以免走丢迷路哈~


那这篇文章并不是我们系统知识体系里面的一部分哈,是属于一个知识科普,或者算是知识交流探讨,那么比价适合有一定C++基础的同学来去看哈。如果没有C++基础、只跟着村长看过C语言和数据结构的教程怎么办?也没有关系哈,我们正在制作C++的录播课,那么差不多过一段时间就会上线,到时候我们也会在公众号上发布。这个录播课是和我们C语言教程、数据结构教程是一脉相承的。

好,我们言归正传。我们今天来探讨在C++中一个非常重要的技术的原理——多态。

介绍多态

OK,那么在此之前,我们先介绍一下多态的基本概念。

通俗的来说,多态,就是多种形态。

具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

我们一般情况下,可以这样来分类:

1、静态多态。

2、动态多态。

静态的多态实际上很简单,就是函数重载。那么我们今天是要来重点说所谓的“动态的多态”。对于动态的多态,必须或者说至少需要满足两个条件:

①子类继承父类,完成虚函数的重写。

②父类的指针或者引用去调用这个重写的虚函数。

得到的效果就是:

如果父类的指针或引用指向父类对象,调用的就是父类的虚函数。

如果父类的指针或引用指向子类对象,其调用的就是子类的虚函数。

换句话来说,调用函数跟对象有关,指向哪个对象,就调用哪个虚函数。


那首先来说说,什么叫做虚函数。

很简单,被virtual修饰的类成员函数称为虚函数。

注意,其修饰的必须并且只能是成员函数!不能随随便便对一个全局的函数都可以加virtual。

虚函数的重写(覆盖):

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),

称子类的虚函数重写了基类的虚函数。

并且都要在函数的前面加上关键字virtual。

举个例子:

图片

可以看结果,我们只看子类实际上就可以了。在用虚函数重写多态的时候,虽然说传递给了一个父类的引用,但是调用的还是子类的对象。

但是如果不用虚函数而只是普通的继承,那么就会构成隐藏,在传递到一个父类对象的时候,调用的就是父类的对象。

上面也是多态最典型的一种应用。

对比:重载、覆盖(重写)、隐藏(重定义)

重载:两个函数在同一个作用域中。它们的函数名、参数相同。(参数类型不完全相同)

覆盖(重写):两个函数分别在基类和派生类的作用域;函数名、返回值、参数等必须相同;两个函数必须都要是虚函数。

隐藏:只要函数名相同,并且两个函数分别在基类和派生类中,它们不构成重写,那么就是隐藏。

抽象类

顺便再介绍一下抽象类吧。

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。

包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。

派生类继承后也不能实例化出对象,

只有重写纯虚函数,派生类才能实例化出对象。

纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

说人话,就是你必须要重写我!否则不让你用(实例化)

多态的原理

多态的原理是我们本节的重点内容。那说到多态的原理,我们就不得不说一下虚函数表的概念。

我们先来看这样一个题:

就想问:sizeof(Car)是多少?

图片

求siezeof(Car)?

按理来说,它只有一个成员变量a,那它的大小应该是4。

 那我们还是先来看一下答案吧:

图片

(再次强调一下,我们这里的环境是x86,不是x64,64位平台下可能数据和这里不一样,因为要考虑int 和指针都是8个字节的问题,但是原理是一样的)

为什么?

为什么答案是8?我们只是增加了个virtual而已?

我们打开我们的监视窗口来看一下:

图片

我们可以看到,这c里面有两个东西。

其中,叫_vfptr的是一个指针,二级指针。

更准确的来说,其是一个指针数组的首元素地址。而虚函数表就是一个指针数组,其末尾以nullptr收尾。

这里的_vfptr指向的正是虚函数的虚表。(v可以理解为virtual,f理解为function)

在虚表中,存储着虚函数的地址。

画图理解成:

图片

我们可以再来看看下面的代码:

图片

我们会发现,b中的虚函数表里面有两个函数的指针,即加上virtual的函数。

在d 中的虚函数表中仅有一个虚函数的指针。

在继承的时候, 过程是这个样子的:

将虚表先继承下来,如果对于虚函数进行了重写,那么就将虚表中的地址进行修改,修改成新的函数的地址。

而这里的派生类我们只看到了一个函数的地址可能是由于我们的编译器进行了优化。如果我们想将其打印出来,也是可以做到的:

图片

我们可以看到,只要是用virtual修饰了,那么在虚表中就会存在属于自己的位置。

而我们将Func1进行了重写,而Func2用virtual修饰,但是并未重写,在继承下来的之后,在派生类的虚表中该位置的地址是不改变的(经编译器优化我们看不见)只有当重写时,派生类所对应的地址才会发生相应的改变。

下面这个图就非常好地描绘了虚函数指针的作用:

图片

OK,那以上就是我们关于多态原理的介绍,实际上就是一个虚函数表指针实现了这样的一个功能。希望能够对大家有多帮助哈哈。

 

更多推荐