(三)final和override关键字
在继承和多态当中我们还会很经常看到这两个关键字final和override。这两个关键字的作用其实很简单。
final关键字字面意思上理解就是我们这个虚函数是最后一个虚函数,之后不能够被重写。所以我们以后想要定义一个虚函数只能够被有限次重写,之后就不允许其发挥重写功能的时候就可以添加上这个关键字。同时在继承当中我们同样使用。我们可以在类定义之后加上final,加上final关键字之后的类就是最后一个类,不允许之后再被继承。样例如下:
我们会发现程序运行的结果跟我们想想中的一样,使用final修饰的虚函数跟类都只能作为最后一个类或者虚函数进行使用,不允许重写和继承。
对于我们的override关键字来说,其仅在多态当中发挥作用。我们可以使用override进行修饰我们派生类当中的虚函数,经过override限定的虚函数会自动进行检查,如果继承的基类当中该函数没有被设置成为虚函数就会产生报错。
对于没有经过override修饰的函数,我们系统会认为它不是一个虚函数。之后我们在预期的派生类当中对其进行重写操作也不会出现我们想要的多态的效果,并且系统也不会进行报错,需要我们自己进行错误的查找操作,会带来很多不必要的麻烦。
这个时候我们就可以使用override关键字对我们的派生类进行限定。当我们在派生类当中对虚函数进行重写的时候编译器就会自动向前进行检查操作,如果我们之前的基类没有将我们重写的函数定义为虚函数就会直接产生报错,节省了我们查找结果不匹配的错误的时间。
(四)重载,重写,重定义的区别与对比
相信大家在刚开始遇到这三种概念的时候都会产生疑惑。重载,重写,重定义很像诶,那么这三者有什么区别呢?我们需要注意什么吗?
1.函数的重载
函数的重载实际上就是同名函数因为参数类型的不同进行指定的匹配的情况。对于函数名相同的函数来说,我们根据函数名修饰规则会将参数跟我们的函数名进行混合得到一个结合体。只要有一点不同就可以进行特定的区分操作,进而得到同名函数发挥不同作用的效果。在函数重载的时候需要我们注意:函数的重载强制要求函数名相同,但是参数不同。并且重载的函数一定要在同一个作用域当中才可以发挥作用。其实很好理解,在不同的作用域当中允许定义同名数据。相应的数据只会在相应的作用域当中发挥作用。
※重点:同一作用域,函数名相同,参数不同
2.函数的重写(覆盖)
函数的重写就是我们正在介绍的,在多态当中进行的操作。重写的函数我们要求首先其必须是虚函数,之后必须满足重写的要求,即三同(函数名相同,函数返回值相同,函数的参数类型必须相同)其中也可以根据特定的语法允许协变的存在。之后还必须要求我们使用基类的指针或引用进行访问我们重写的虚函数。满足以上几点就可以构成多态。函数的重写,要求我们重写的函数分别在基类和派生类的作用域当中。
※重点:分别在基类和派生类的作用域当中,要求三同(协变除外),要求重写的函数是虚函数。
在这里需要着重强调一点:实际上重写函数的判定中三同的要求只跟函数的参数类型,返回值类型,函数名有关。跟我们其他数据没有关系,加入我们在重写的虚函数当中加上默认参数,即使我们基类当中虚函数的默认参数跟派生类当中的虚函数的默认参数不同也是允许存在的。
经过重写的虚函数会自动进行组装,将我们原本的虚函数的头部拿出来,之后再将我们重写的虚函数的函数体拿出来最终形成一个新的函数。这句语法看起来很奇怪,我们了解即可。代码样例如下:
实际原理就跟我们讲到的那样会将原有虚函数的头部跟新的重写的函数体进行拼接,那么我们的默认参数的值就应该是A类当中的11,而不是我们B类当中的22。这么来看一切运行都跟我们的预期完全符合。
3.函数的重定义(隐藏)
函数重定义的概念在我们的继承章节当中提到过。当我们的派生类存在跟我们基类函数名完全相同的函数的时候,就构成了函数的隐藏。对于函数隐藏来说我们并不要求参数类型一致,不管我们参数相不相同,只要函数分别在基类跟派生类当中,只要不满足我们重写的要求的同名函数都可以构成函数的隐藏。
※重点:分别在基类和派生类的作用域当中,不满足重写的所有同名函数都是重定义
(五)多态的底层原理
之前我们介绍的都是多态的基本的使用方法。比如要求是虚函数,要求构成我们函数的重写,要求我们使用基类的指针或引用进行访问重写的虚函数的等等。但是函数具体是怎么进行实现的呢?这就涉及到了我们多态的底层原理了。
实际上在我们的系统当中,当我们将基类当中的一个函数设置成为虚函数的时候,编译器会自动为这个类生成一张虚函数表,专门用来存储其中的虚函数所存放的地址。当我们的派生类在继承基类之后,这张虚函数表也会同时被继承下来。
当我们在派生类当中对基类当中的虚函数进行重写操作的时候,就会有一个新的函数地址产生。我们将这个新的函数地址,这个新的函数地址就会指向派生类当中已经重写的虚函数。
之后我们将这个地址放到继承下来的虚函数表当中原本虚函数的位置上就产生了一张新的虚函数表。对于这张新的虚函数表进行访问就只能访问到我们重写的虚函数,进而实现我们的多态了。
我们通过调试窗口观察我们函数重写的过程:
我们在基类当中创建两个虚函数,但是只对其中一个虚函数进行重写操作,另一个虚函数进行我们的对照。根据我们之前的分析来说,预期得到的结果应该是没有经过重写的虚函数的地址会保持不变直接复制下来,经过重写的虚函数的地址在虚函数表当中被新的函数覆盖而产生改变。我们会发现一切跟我们的预期完全吻合。
实际上我们在派生类当中还可以重新向虚表当中添加虚函数,使得我们之后的类在继承的时候可以继续进行重写操作。在之后的类当中新添加的虚函数会自动添加到我们的虚函数表当中进行存储。我们进行观察:
这个新添加的虚函数在我们的监视窗口当中可能看不出来,但是我们可以通过调用内存窗口进行观察,输入我们的虚表地址之后就会发现,在虚表当中一次保存着我们基类当中创建的两个虚函数,并且在前两个虚函数之后添加了一个新的地址,我们猜测这个地址就可能是我们在派生类当中新添加的虚函数的地址。接下来我们进行验证操作。