1、C++中的多态
因为每个派生类对象中都包含基类部分,所以可将基类类型的引用或者指针绑定到派生类对象中的基类部分,而对象本身并不会改变,仍为派生类对象。
通过该指针或者引用调用虚函数时,编译器将生成代码,在运行时确定调用哪个函数,被调用的是与动态类型相对应的函数,也就是派生类中定义的虚函数实现。
而且,只有通过引用或者指针调用,虚函数才会在运行时确定。如果调用的是非虚函数,则无论实际对象是什么类型,都执行基类类型中定义的函数。
2、 指针的类型
引用和指针的静态类型与动态类型可以不同,这是 C++ 用来支持多态性的基石。
但是不同类型的指针之间到底有什么不同呢?
指向不同类型的各指针,它们之间的差异既不在其指针表示法不同,也不在其内容不同,而是在其所寻址出来的对象类型不同。也就是说,指针类型会指引编译器如何解释某个特定地址中的内存内容及其大小。
而指针本身所需的内存大小是固定的,即等于机器的位数,在 32 位机器上,它将涵盖 4 个字节。
3、 基于指针之多态的实现
在这里,我们仍然引入两个类:
Class Animal { } ;
Class Bear { } : public Animal { } ;
其中各包含一个 virtual void rorate ( ) 函数成员。
Bear b ;
Animal *pz = b ;
在用 Bear 对象初始化 Animal 指针的过程中,对象 b 并没有发生丝毫的变化。 b 和 pz 都指向 Bear 对象的第一个 byte ,其间的差别是, b 所涵盖的地址包含整个 Bear 对象,而 pz 所涵盖的地址只包含 Bear 对象除了Animal中的成员,你不能够使用pz来直接处理Bear中的任何其它成员。唯一例外是通过virtual机制。
Pz->rotate ( ) ;
由指针的性质可知,当指针指向基类对象的时候,我们不能在编译时确定他实际指向的到底是基类对象还是子类对象。等到执行期,pz所指的对象类型(注意,不是pz的定义类型)才决定rotate()所调用的实体。Pz指向的对象是Bear对象(注意,是丝毫没有发生变化的Bear对象),所以通过pz访问得到的自然是Bear类型的rotate()实体。
一个指针或者引用之所以支持多态,是因为它们并不引发内存中任何“与类型有关的内存委托操作”,会受到改变的只是它们所指向的内存的“大小和内容的解释方式”而已。
4、那么,不用指针又会怎么样呢?
Bear b;
Animal za = b;
Za.rotate ( );
运行上面的代码,最后一步执行的是Animal的rotate()实现还是Bear的rotate()实现?答案是Animal版的rotate()。
这又是为什么?基类可以利用已存在的派生类对象初始化吗?其中的过程又是怎样的?
C++语言是一种强类型语言,za并不是指针和引用,而是一个普通的局部变量。为了保证类型安全,za在定义之初就会开辟一段新的内存空间,并且被设定为一个基类Animal对象实体。那么在用b初始化za的过程中,发生了什么呢?
初始化过程只是将b中Animal子对象部分复制给 za ,而其余部分,则被编译器切割掉了。至于 vptr, 在初始化的过程中发生了 virtual table 指针的重新设定, za 中的 vptr 指向的是 Animal 类的 virtual table ,而不再是 b 中的 virtual table 。所以,最终运行的 rotate ()也就变成了 Animal 类中的实现。
联系客服