天天看点

c++ 多态 运行时多态和编译时多态_C++多态性浅析

C++多态性浅析

1

导引

什么是多态?

多态是c++提供的一种机制,这种机制能达到相同的操作(函数)跟随传递或者绑定对象的不同(父或不同的子对象)来做出不同的反应,在动态多态中,允许将子类对象(指针或引用)传递给父类对象(指针或引用)从而实现这种效果,而在静态多态中利用编译器能力来实现。(咳,说人话!)

下面我们来举个🌰:

比如你在玩LOL(不你没时间),游戏中有很多英雄,每种英雄都有一个「类」与之对应,每个英雄就是一个「对象」。

我们以最基本的动作Attack/Hurt为例,英雄之间能够互相攻击,攻击敌人和被攻击时都有相应的动作,动作是通过对象的成员函数实现的。

那你怎么去实现每个英雄的动作呢?一个个写吗?不仅很麻烦,而且不容易修改。

这时候你可以写一个基类CHero,其他英雄的子类继承这个基类,再在每个子类里面写对其他英雄子类对象的Attack与Hurt函数。

c++ 多态 运行时多态和编译时多态_C++多态性浅析

可是这样的话,对于每个英雄的子类,有多少个其他英雄就有多少个Attack/Hurt,而且如果出现新英雄了,还要在已有的子类中增加新的Attack/Hurt函数,所以这样还是很繁琐。

这时候多态的优势就体现出来了,对Attack(CHero *obj_Hero)和Hurt(CHero *obj_Hero)各自写一个虚函数,放在基类CHero的public类中,其他英雄可以在自己的子类中重写Attack/Hurt对于函数对象的动作(这个对象可以是任何一个子类英雄)。当新的英雄出现时,也无需修改已有的子类。

2

多态性实现原理

多态分为静态多态和动态多态。

静态多态的主要应用之一是重载,因为在编译期间确定,所以称为静态多态。在编译时就可以确定函数地址。

动态多态是通过继承、重写基类的虚函数实现的多态,因为是在运行时确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。在使用时,需要在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。下面主要说明动态多态:

用virtual关键字申明的函数叫做虚函数,虚函数是类的成员函数。

存在虚函数的类(无论基类还是派生类)都有一个一维的虚函数表叫做虚表。当类中声明虚函数时,编译器会在类中生成一个虚函数表。类的对象有一个指向虚表开始的虚表指针。虚表是和类对应的,虚表指针是和对象对应的。虚函数表是一个存储类成员函数指针的数据结构。

假如我们申明了一个基类Base和派生类Derived,二者各具有一个虚函数Print,那么它「虚函数表」的形式可以理解成下图:

c++ 多态 运行时多态和编译时多态_C++多态性浅析

当存在虚函数时,每个对象中都有一个指向虚函数表的指针_vfptr,当调用function(parent *base)函数的时候,C++编译器不需要区分子类或者父类对象,只需要在base指针中,找到_vfptr指针即可。

总体而言就是,虚函数表的指针指向虚函数表,虚函数表里存放的是类里的虚函数地址,那么在调用过程中,就能实现多态的特性。

PS:还有一类纯虚函数,如果在虚函数的后面写上“=0”,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,派生类继承后也不能实例化出对象。只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规定了派生类必须重写,另外纯虚函数更体现了接口继承。

3

多态性应用

c++ 多态 运行时多态和编译时多态_C++多态性浅析

通过导引部分我们了解到:多态分为静态多态和动态多态;下面将结合具体的程序实例,来观察两类多态性是怎样实现的:

一、动态多态

动态多态是通过继承、重写基类的虚函数实现的多态,运行时将会根据对象的实际类型来调用相应的函数。如:

c++ 多态 运行时多态和编译时多态_C++多态性浅析
c++ 多态 运行时多态和编译时多态_C++多态性浅析

该例分别通过基类指针指向hextype类和octtype类的对象来分别实现对两个派生类的show函数的调用,可以看出,通过多态性的运用,我们避免了复杂的函数声明,使得程序更加简洁高效。

需要注意的是,派生类中覆盖函数的返回类型与基类中虚函数的返回类型是必须一致的。

当C++编译器不管决定调用哪个重载函数时 , 它只检测参数类型 , 忽视返回类型 , 而对虚函数返回类型必须完全相匹配 , 因为编译器不能保证根据实时对象类型实行不同的转换 。

如:

c++ 多态 运行时多态和编译时多态_C++多态性浅析

二、静态多态

静态多态性的表现之一是函数重载,函数重载又可以进一步分为一般函数重载和运算符重载。

其中函数重载允许用同一函数名定义多个函数,这些函数的参数类型或者参数个数不同,这就是函数的重载。重载函数的参数个数,参数类型,或参数顺序三者中必须至少有一种不同,函数返回值类型可以相同也可以不同。此外,是否为const函数,也用于区分不同的重载形式。下列程序的功能是利用函数重载求两个整数或三个整数最大数。

c++ 多态 运行时多态和编译时多态_C++多态性浅析

函数重载以后,根据实参的类型及个数决定调用哪个重载的函数。上例中实参是两个就调用求两个数最大值函数,同理实参是三个就调用求三个数最大值的函数。

运算符重载本质上也是函数重载,既可以重载为类的成员函数也可以重载为类的友元函数。下列程序的功能是通过运算符重载为类的成员函数完成复数的算术运算。

c++ 多态 运行时多态和编译时多态_C++多态性浅析
c++ 多态 运行时多态和编译时多态_C++多态性浅析

C++中的运算符只能对基本数据类型进行运算。复数是一种自定义数据类型,是不能用C++提供的运算符完成算术运算的。自定义数据类型--复数类只有通过运算符重载才能利用C++已有的运算符完成运算。所以,运算符重载解决的是自定义数据类型使用运算符的问题。同理通过对流插入运算符和流提取运算符重载可以解决自定义数据类型的输入输出问题,在这里就不再举例。

4

多态性作用总结

C++语言中,多态性分为两类。一是静态多态,其中的运算符重载用于解决自定义数据类型运算问题;二是动态多态,其中的虚函数实现,为程序带来了灵活与方便。

1

END

1

作者:徐雅凝 王浩霖

审核:刘政宁

指导老师:李超

继续阅读