C++学习第13篇-虚拟函数

来源:互联网 发布:如何搭建数据库 编辑:程序博客网 时间:2024/06/08 16:37

1. 衍生类中基类的指针和引用

上一篇中,已经介绍了类的继承;这篇中,将介绍继承中另一个重要和实用的方面-虚拟函数

在讨论虚拟函数之前,我们应明确为什么需要虚拟函数。之前我们知道,衍生类包含了基类的一部分和本身的一部分。


输出:


因为一个Derive的对象也是Base类型,如老师是一个人。


输出:


因为rBase和pBase是Base类型,只能看到Base类的成员或者是Base类继承其他类的成员,对于Derived类的非继承成员是不可见的。

另一个例子:



输出:


2)在基类中使用指针和引用

如果没有使用指针,对于多个动物的呼叫:


当然也可以这样写:


但是,Animal只会执行其对应的呼叫函数。

测试:


而且,当动物种类超出30种时,需要30个数组来保存不同的动物种类。


这两种方法都有个明显的缺点:基类指针指向衍生类,执行的函数是基类的函数,而不是衍生类的重定义函数。

2. 虚拟函数

在第1节中,使用基类指针,可以简化代码;但是基类指针只能执行基类的对应函数,而不是衍生类的重定义函数。

例如:


输出是:rBase is a Base。

这个问题可以通过虚拟函数来解决:

虚拟函数-特殊类型的函数,执行相同签名函数的最高级衍生版本。

只需将virtual关键字放在函数声明的前面。

修改上面的例子:


输出是:rBase is a Derived。

因为使用virtual关键字,GetName执行力衍生类版本的函数;

复杂一点的例子:


输出是:rBase is a C。

流程:首先rBase是一个A引用,因为A中的GetName是一个虚拟函数;然后执行遍历A到C的相同签名的函数,最后执行最高级衍生的C类,然后执行C的重定义函数。

因为rBase指向的是一个C对象,所以不会遍历D的函数。

更复杂的例子:



输出:


再次测试:


输出:


注意:衍生类函数的签名必须和基类的虚拟函数的签名一致,才能达到执行衍生类函数的效果。

2)virtual关键字的使用

A)事实上,virtual关键字在衍生类中不必要使用的。

B)通常,最原始的基类使用virtual关键字,这样所有继承的衍生类都可以虚拟执行;

C)通常,推荐在衍生类中也使用virtual关键字,尽管语法上不需要;

3)虚拟函数的返回值

在正规情况下,虚拟函数和衍生类的重定义函数必须有一致的返回类型;

但是,例外:如果虚拟函数返回的是类的指针或引用,那么重定义函数可以返回衍生类的指针或引用。

3. 虚拟析构函数、虚拟赋值、重写虚拟列表

1)虚拟析构函数

C++提供了一个默认的析构函数,有时需自定义析构函数,特别是在动态分配内存。

例如:


这时,打印:Calling ~Base()。

如果采用虚拟析构函数:


此时,输出是:

Calling ~Drived

Calling ~Base()

2)虚拟赋值

将赋值虚拟是可能的。不像虚拟析构,虚拟赋值是麻烦之源。

3)重写虚拟化

使用全局运算符::来显示执行基类函数:


这种做法是比较少使用,但只是说明是可能的。

虚拟函数的缺点:低效的,执行一个虚拟函数需要的时间更长;还要另外开辟内存空间。

4. 提前绑定和延迟绑定

当CPU执行编译程序时,每行语句被编译成一行或多行的计算机代码,每一行指定了唯一的顺序地址;函数也一样,每个函数结尾都有一个唯一的顺序地址。

所谓绑定,就是将标志符转换为机器语言地址的过程;本章节主要讲述函数的绑定。

1)提前绑定

当执行直接调用一个函数时,这个进程成为提前绑定(静态绑定);记住,每个函数都有唯一机器地址,当编译器遇到函数调用,编译器将函数调用转换为机器语言,CPU跳转到函数的地址。


因为Add、Subtract和Multiply是直接函数调用,即提前绑定。

2)延迟绑定

在一些程序中,不可能知道那些函数直到运行时才加载,即延迟绑定(动态绑定);

在C++中实现延迟加载主要是采用函数指针;

修改上面的main函数:


因为pFcn知道运行时才确定执行的调用函数,所以称作延迟绑定。

延迟绑定效率相对低一点,因为要涉及更多的间接调用级数;但延迟更灵活,因为函数调用知道运行时才确定。

5. 虚拟表

在应用虚拟函数,C++使用了延迟绑定的特殊形式-虚拟表。

虚拟表是延迟管理器管理的一张函数调用列表。名字如:“vtable”, “virtual functiontable”, “virtual method table”, or “dispatch table”。

虚拟表是编译器在编译时创建的一个静态数组;每个函数调用都有一个入口供对象调用。

编译器为基类添加类一个隐藏的指针: *__vptr;这是个真实的指针,衍生类可以继承。

例如:


编译器创建3张虚拟表,记录每个类的虚拟函数;

每个虚拟表的*_vptr负责管理函数调用的遍历;

通过虚拟表,编译器和程序更准确地找到所需的虚拟函数。

6. 纯粹虚拟函数、抽象基类和接口类

这是虚拟函数的最后一节,恭喜各位通过了C++语言最难懂的部分了。

1)纯粹虚拟(抽象)函数和抽象基类

目前为止,所看到的虚拟函数都有函数体(定义体),C++允许创建一类特殊的虚拟函数-纯粹虚拟函数(抽象函数),没有函数体。


使用一个纯粹的虚拟函数有2个主要的结果:

A)任何类使用了一个或多个的纯粹虚拟函数,成为抽象基类;抽象类-即不可以实例化;

B)任何继承于抽象基类的衍生类必须实现该函数;

一个纯粹的虚拟函数是非常有用的,我们想将一个函数放到基类,衍生类只需知道其返回类型即可;这样就预防了衍生类忘记实现该函数;

2)接口类

一个接口类-不包含任何成员变量,所有函数都是纯粹虚拟函数。

接口是非常有用的,当你想定义衍生类必须实现的功能;但功能的实现细节依赖于具体的衍生类。


接口类越来越流行,因为其易用、易扩展和易维护。


【免责特此声明:
1)本内容可能是来自互联网的,或经过本人整理的,仅仅代表了互联网和个人的意见和看法!
2)本内容仅仅提供参考,任何参考该内容造成任何的后果,均与原创作者和本博客作者无关!】

原创粉丝点击