所以呢,这个讲到这里呢大家可能就会问了,那么这个多态好像非常智能,那么它
究竟是怎么实现的呢?这时候就会用到我们刚刚 以前的部分中提到的这个什么呢
虚拟方法,虚拟方法表。
就是说对于 面向对象这个语言这种编译器
不管是 Java 也好还是 C++ 也好,只要它发现了这个父类和子类之间存在什么呢,存在这种
重写,什么叫重写,也就是说父类和子类具有一些,一个操作,然后都具有一个操作,这- 个操作的
实际上这个,它的这个 signature,它的特征标记是完全一样的
然后我们称,就这个父类和子类都有重写这种情况下,它在编译的时候它就
不能够按照一个简单的,一种方式来进行编译。
而它要麻烦一步,它要建立一个 虚拟表来进行编译。
举一个例子,这里呢我们
实际上呢是,这个父类呢全部都是一些乐器
这些乐器呢还有各种各样,有风琴啊竖琴啊,什么笛子
这个等等,其他的一些乐器来组成。
然后我们发现 这个子类和父类之间,存在一种什么呢,存在一种这个 重写的这种关系。
这是我们在编译器会自动地将它们 编译为一种虚表这种结构,也就是说,它们每一个
对于每一个重写的 overwrite 这些这个操作呢,我们呢不是直接
把它们,这个,直接这个
把它们的操作呢直接静态地绑定到相应的这个,把这个操作名
直接地,直接绑定到,静态地绑定到这个操作
一些这个执行的序列,那个函数里边去,而是我们首先呢
做了这么一个表,然后它呢,当前这是一个,不管是什么乐器
乐器的数组是这么一种结构,然后呢每一种这个数组的首地址
指向当前对象,当前是什么样的一个类型的对象。
而这个 对象有一个虚表的一个指针。
这个指针呢是指向什么呢 指向真正的这个,这个对象的这个虚拟
方法表,就是这个对象的虚拟方法表。
注意它在编排的时候呢 它在编排这个虚拟表这种数据结构的时候呢,它有一个原则,那就是具有相同
特征标记的这个,虚拟方法表呢,它给它排的这个偏移量是一样的。
比如说这个 play 它是排在第一个偏移量,第一个定值上。
然后 what 第二个定值,adjust 这个排在 第三个位置上。
所有的,只要它们是重写父类的方法,它们 的这个排的位置,这个偏移量的位置与它们的这个
特征标记,实际上是,对应关系
这样的话就有什么,一个,就可以解释为什么在实现多态的时候是如此智能
那就是说,一但,现在呢我们找到第一个当前乐器数组中的第一个对象,然后当它这个
风琴,然后它呢,这个找到它之后呢我们 要调用它的一个
play 的操作,然后呢首先就是根据它的一个 虚表指针,然后找到它的虚表首地址。
之后呢,计算一个偏移量,然后呢找到当前的 这个,然后这个就是什么呢,比如它在指向一个什么呢
指向一个真正的那个,风琴演奏的一个
具体的一个方法 如果找到其他的乐器,同样也是一样。
这样的话呢就会,根据它这个 乐器的种类的不同,而找到相应的方法。
实际上我们觉着好像很智能的东西,实际上呢是有一个
在编译的时候是,有额外的一些数据结构,承担了这个 A 组所有的判断。
所以这就是 大概地说呢,这个,虚
我们这个多态是如何实现的,实际上也并不神秘,就是在编译的时候建了一个 虚表,那样就可以完成。
但是呢正是因为它,由于它建立了这么一些复杂的一个结构 所以导致它在效率的时候,受到很大的损失。
我们可以看一个例子 在当前这个例子中呢,我们这个
可以看出,test 5 中,这里头呢
是,由于它这个父类和子类之间是存在一个什么呢 都是,对
action 进行了重写,所以它构成一个多态,并且我们在执行的时候呢,实际上是,我们
是从父类的接口调用这个子类的对象,这就形成了多态的两个条件,因此它是可以 形成多态的一种趋势。
尽管它在写的时候非常简单,但是这个效率是有很大的损失。
而我们执行一下这个 时间,我们可以执行一下这个时间,我们发现
我们执行一下
我们这个当前这个,具有多态的这种 程序。
我们发现呢,如果有多态的话,这将会
而相同的一件事情如果我们不用多态来做呢,在当前这种情况下我们没有多态
为什么呢,我们这个父类上,已经取消了这个 action,我们这两个
action 我们分别是 写成这个 action 1 和
action 2,这样的话你就,已经没有了多态形成的
两个条件,所以呢,它就不可能形成多态 那么,但是它完成相同的事情。
我们看一下 这个执行效率。
我们可以看出,这个只需要 345
秒,而有多态那个需要一千多毫秒,所以它要 有没有多态,实际上对这个效率损失是非常大的
大概是差了 3 倍以上的损失。
所以呢,多态并不是一个 就是说有些情况下呢,效率优先的情况下呢,就不一定
使用多态,并不是说非得用多态。
只有当我们那个 尽可能地分用接口,尽可能地编写更少的程序,源代码的时候,这时候我们才使用多态
所以多态呢,是一个权衡的结果
[空白录音]