Fork me on GitHub

一探多态

讲完了之前的继承,我们现在再来看一看便(偷)捷(懒)的新花样:多态

多态之前

在多态之前,我们要先了解一个概念:函数的覆盖(重写)。

重写

在重写之前,我们要先了解一个概念:虚函数。

虚函数

在虚函数之前,我们。。。咳咳。。不用了解更多了。
虚函数的重写:派生类中有一个跟基类的完全相同虚函数,我们就称子类的虚函数重写了基类的虚函数,完全相同是指:函数名、参数、返回值都相同。另外虚函数的重写也叫作虚函数的覆盖。
虚函数重写有一个例外:重写的虚函数的返回值可以不同,但是必须分别是基类指针和派生类指针或者基类引用和派生类引用。协变我们了解一下就可以了,这个用得很少。
那么多态是干什么用的呢?
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
简而言之,就是让一个函数拥有魔力:根据调用的对象,去决定产生什么结果。
但是,这个拥有魔力的函数一定是一个虚函数,并且在子类里完成了重写。
之前我们了解了继承的机制,这里我们需要思考一个问题:析构函数是否应该设为虚函数,并且在子类里重写?
答案是肯定的:我们了解过基类指针可以指向子类,没有构成多态的情况下,对这个指针delete会出问题(why?)
但是在完成了虚函数重写后,析构函数就“明白”应该调用子类的析构函数而不是基类的。

接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
继承的目的就是实现继承(子类可以直接用父类的东西),多态是接口继承(继承了框架后自己实现)

重载,覆盖和隐藏

下面我们系统的对比一下这三个概念:

\ 重载 覆盖(重写) 隐藏(重定义)
作用域 在同一作用域 分别在基类和子类作用域 分别在基类和子类作用域
函数名与参数 函数名相同,参数不同 函数,参数,返回值都相同(协变除外) 函数名相同
其他条件 \ 两个函数必须是虚函数 不构成覆盖即为隐藏

抽象类

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

C++11 override和final

另外补充一下的是C++11提供override和final来修饰虚函数。
实际中我们建议多使用纯虚函数+ overrid的方式来强制重写虚函数,因为虚函数的意义就是实现多态,如果没有重写,虚函数就没有意义。

多(偷)态(懒)的实现

虚表

在一个有虚函数的类里面,有一个成员必定是一个指针,实际上它是一个虚函数指针数组指针,为了形象化我们叫它虚表,它指向一张表,这张表里有这个类里所以虚函数的地址,所以它是一个虚函数指针数组指针(有点绕)。
那么这又关我多态什么事呢?在调用虚函数时,我们肯定要找到这个虚函数,去哪找呢?很明显我们去虚表里找:那么只要子类的虚函数地址能够覆盖掉父类的虚函数指针,我们不就实现了上面说的多态了么?答案是肯定的,这也是重写也叫覆盖的原因。
那么虚表存在哪里呢?经过试验在VS下,虚函数和虚表都存在代码段。

多继承下的虚表

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!