值是允许的,或者说是安全的呢?
从语义上来讲,继承所表现的是“是一种”的关系,也就是说,每个派生类对象必定
“是一种”基类对象。所以,任何向基类类型的请求,派生类对象都可以无条件地正常处
理。因为直升机“是一种”飞机,喷气式飞机也“是一种”飞机,所以所有对飞机的操作
请求,它们都应该可以正常处理。
从语言上来讲,由于派生类通常比基类拥有更多的数据成员而绝对不会更少,派生类
对象所占的内存空间必定大于或等于基类对象所占的内存空间。因此,将基类类型的指针
指向派生类类型的对象时,在指针的可视范围中的内存必定是可用的,这一部分内存空间
必定是属于对象的,所以这种赋值行为是合法的、安全的,并且得到编译器认可的。
例如,如下的两个类:
TBase = class
private
FMember1 : Integer;
FMember2 : Integer;
end;
TDerived = class(TBase)
private
FMember3 : Integer;
end;
当TBase类型的指针指向其派生类类型TDerived的对象后:
var
Parent : TBase;
Child : TDerived;
Begin
Child := TDerived.Create();
Parent := Child; //当执行这行代码之后……
// 以上两行代码也可以简化为Parent := TDerived.Create();
…… //之后的代码省略
End;
TBase类型的指针(Parent指针)指向了Child对象实体所在的内存首地址。在2.3节
中说过,每个派生类对象实体中都包含了一个完整的基类对象实体。此处的Parent指针可
以访问的范围,正是这个完整基类(TBase)对象实体的大小。因此,Parent指针始终可以
合法地访问其所指向的内存空间。
Parent指针的可视范围如图2.5所示。
指向VMT的指针
Child对象实体
FMember1
FMember2
FMember3
VMT
Parent指针
的可视范围
图2.5 基类指针可视范围演示
在图2.5中又一次看到了VMT,VMT究竟是何方神圣呢?为什么每个对象都会有一
个指向VMT的指针呢?这些问题可以在了解虚方法的动态绑定实现机制中找到答案。搞
清这些,便会清楚多态是如何实现的了。
当创建一个类的实例之后,编译器会在该对象的内存空间的首4个字节安插一个指针,
该指针所指向的地址称为VMT(Virtual Method Table,虚方法表),这个表中存放了该类
的所有虚方法的入口地址。在Object Pascal中,所有类实例都会有这么一个指向VMT的
指针。如果没有在类中声明虚方法,则该指针为nil。
还是以前面所说的飞机抽象类和直升机类为例:
TPlane = class
protected
FModal : String;
public
procedure fly(); virtual; abstract; // 起飞抽象方法
procedure land(); virtual; abstract; // 着陆抽象方法
function modal() : string; virtual; // 查寻型号虚方法
…… // 其他可能的操作
end;
TCopter = class(TPlane)
public
constructor Create();
destructor Destroy(); override;
procedure fly(); override;
procedure land(); override;
…… // 其他可能的操作,没有覆盖TPlane.modal()
end;
在一个全局函数中用飞机类型来创建直升机实例:
procedure g_CreateACopter(var Plane : TPlane);
begin
Plane := TCopter.Create;
end;
当执行Plane := TCopter.Create之后,一个直升机实例就被创建了,并且Plane指针指
向了它,如图2.6所示。
Plane指针 指向VMT的指针
FModal
直升机对象实例直升机类的VMTTCopter.fly()
TCopter.land()
TPlane.modal()
TCopter没有覆盖
TPlane.modal()
图2.6 plane指针指向直升机对象实例
没有被派生类覆盖的方法,编译