unit Unit2;
interface
uses
  Classes,Dialogs;
type
  TA = class
  public
    a:string;
    procedure Fuc;  end;
  TB =class(TA)
  public
    a:string;
    procedure Fuc;
  end;
implementation
{ TA }
procedure TA.Fuc;
begin
  ShowMessage('a');
end;{ TB }
procedure TB.Fuc;
begin
  ShowMessage('b');
  inherited;
end;
end.
首先,这样的代码能编译通过,运行。TB中的Fuc覆盖了TA中的。
字段也能重名。这两点我真的很不明白,请知道的人给与解释。
还有上面的代码我觉得已经否定了virtual关键字的作用,因为没有这个关键字一样实现重写。
virtual与dynamic的本质区别是什么?动态和虚到底有什么区别?

解决方案 »

  1.   

    你这算怎么个否定法……var
      a1, a2 : TA;
    begin
      a1 := TA.Create;
      a2 := TB.Create;
      a1.Fuc;
      a2.Fuc;
      a1.Free;
      a2.Free;
    end;你试试加上virtual/dynamic、override和不加的运行结果有什么差别dynamic和virtual的区别主要是,前者用时间换空间,后者用空间换时间
    调用virtual函数的时候,首先找到类类型rtti中的虚函数列表,然后call该虚函数对应的指针。也就是说,每一个有虚方法的类的继承类,它的虚方法列表都不小于基类,哪怕没有override,也会把基类的虚方法地址放到虚方法列表中
    而dynamic则需要通过System._CallDynaInst调用。CallDynaInst调用GetDynaMethod寻找动态方法的地址,动态方法放在类类型rtti的vmtDynamicTable中。它的寻找过程比较复杂,首先在调用的时候会传入一个动态方法的索引,然后找到Dynamic Table,在动态列表中寻找是否存在该索引,如果存在的话通过索引找到方法指针并返回;如果不存在的话,则继续跳到父类的动态列表重复上面的过程,直到找到为止。如果一个类没有继承基类的动态方法,那么它的动态方法列表是空的,这样减少了继承类的类型信息的大小
      

  2.   

    楼主要想理解这个问题,需要站在对象的客户(Client即调用者)的角度来想。比如下面两段代码:(你可以先猜下面的运行结果)// 针对具体类操作
    var
      b: TB;
    begin
      b := TB.Create;
      try
        b.Fuc;
      finally
        b.Free;
      end;
    end;// 针对(抽象)基类操作
    var
      a: TA;
    begin
      a := TB.Create;
      try
        a.Fuc;
      finally
        a.Free;
      end;
    end;像第一段代码是直接针对具体类(TB)来操作,此时会直接调用TB的Fuc,和TA的Fuc及声明没有关系。
    而第二段代码则不同,客户想针对抽象基类操作,由于TA.Fuc没有定义为virtual/dynamic,所以调用的仍是TA.Fuc,而不是TB.Fuc。(这是客户想要的吗?)那我们为什么要针对抽象编程?主要是通过*继承*和*多态*(即将基类方法定义为virtual/dynamic,子类再覆盖此方法),让同一个动作在各个子类中可以有不同的行为/实现。(而传统的面向过程的方式是直接在一个函数中用if/case做判断,通过修改而不是继承的方法来实现扩展。)比如: TAnimal = class 
    public 
      procedure SayHello; virtual; 
    end; TDuck = class(TAnimal) 
      procedure SayHello; override; 
    end; TBird = class(TAnimal) 
      procedure SayHello; override; 
    end; 客户的调用代码: TClient = class 
    private 
      fAnimal: TAnimal; 
    public 
      constructor Create(animal: TAnimal);  // 假设通过构造函数的参数传入animal 
      procedure Test; 
    end; procedure TClient.Test; 
    begin 
      fAnimal.SayHello; 
    end; 如果传入的annimal是TDuck的实例,则调用的即是TDuck.SayHello;如果是TBird的示例,则调用TBird.SayHello。
    P.S. 关于virtual和dynamic,其实语义上都是一样的,子类都能通过覆盖(override)来实现多态,区别在于内存布局不同而已。
    如果定义为virtual,编译器会在所有子类的VMT中生成所有的虚拟方法列表(无论该子类中是否override过);
    如果定义为dynamic,编译器仅仅在子类的DMT中生成override过的虚拟方法(这样对于没有override就需要遍历父类来找到虚拟方法的地址)。这也就是Seamour说的用空间换时间(virtual)以及用时间换空间(dynamic)。