这个问题困惑了好久还是没想通,为什么说虚函数要运行时才能决定,编译时不是就可以根据建立的虚函数表将虚函数地址确定了么?

解决方案 »

  1.   

    虚函数表的作用是生成虚函数的偏移地址,真正的地址需要将对象实例化后的地址+函数的偏移地址!
    也就是说虚函数不能像静态函数那样直接用call 函数名来调用 
      

  2.   

    自己创建二个类,加几个虚函数和成员函数!
    调用时打开view-debug-cpu信息,看看汇编在调用函数时用什么方法,是直接用函数名做函数地址还是经过计算后生成的函数地址!
      

  3.   

    光从编译的角度来理解这个问题可能稍微复杂了一点,我们可以换个角度从类的调用者(Client)的角度来看:比如: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(obj: TBase);
    begin
      fAnimal.SayHello;
    end;看了这段代码,楼主可以想一个问题:TClient怎么可能知道传入animal的是什么?(虚函数的意义在于实现对象的多态,即针对抽象编程时,允许子类的同一个动作有不同的行为。)如果传入的annimal是TDuck的实例,则调用的即是TDuck.SayHello;如果是TBird的示例,则调用TBird.SayHello。所以究竟调用哪个SayHello是运行时决定的(即根据所传入的TAnimal对象示例查找VMT来决定的)
      

  4.   


    看看  VCL源代碼剖析就明白了,是通過查表的。
      

  5.   


    这种情况下,比如在一个类的实现代码里有如下一段:var
      Client:TClient;
      Object:TObject;
    begin
      Object:=TDuck.Create;
      Client:=TClient.Create(Object);
      Client.SayHello;
      ...
    end;那么编译器在编译时不就可以先确定TDuck.SayHello在内存中的绝对位置,再经过跟踪Object的引用类型情况,那么就可以确定在调用TClien.Create(Object)的时候Object是TDuck类对象的引用,这个时侯就直接将 Client.SayHello 编译成调用虚函数 TDuck.SayHello 的地址,这样也可以免去执行时查表的资源消耗啊,为什么这样不行呢?
      

  6.   

    其实大家都从这个阶段走过来的,不明白也没什么,新人可以多写几句代码好好体会一下。一般来说总是经历几个阶段:WHAT -> HOW -> WHY,这是一个循序渐进的过程。
      

  7.   


    咱们换个例子,楼主再琢磨一下下面这个单元(考虑一下编译原理):unit Client;interfaceuses
      Animals;type
    TClient = class
    public
      procedure Test(animal: TAnimal);
    end;procedure TClient.Test(animal: TAnimal);
    begin
      animal.SayHello; // 查找animal对象实例的VMT,找到合适的SayHello方法
    end;
      

  8.   

    虚函数是为了运行时多态,它仅是一个接口。说明此类承诺的特性。
    而把实现留给子类去做。
    我给你解释的通俗点。TAnimal=class
         procedure  eat();virtual; //我们承诺,动物都有吃的动作。
         procedure  doAction(); impletments
         procedure   TAnimal.doAction()
         begin
             eat();//我们可以编写代码调用eat()了,此时不知道eat会具体做什么。
                     //我们可以调未知类的代码,多么激动人心的事情。
                     //此行代码能通过编译,那么它必定是运行时动态确定调用的。
         end;
    end;TDog =class(TAnimal)
       prcedure eat();
    // 不必去实现doAction函数。
     impletments
       procedure TDog.eat()
      begin
       // dogs can eat meat;
    end;end;
    TCow =class(TAnimal)
       prcedure eat();
    // 不必去实现doAction函数。 impletments
       procedure TCow.eat()
      begin
       // cows can eat grass
      end;end;
    //in usecase
      TAnimal   a;//注意,我们声明的是基类TAnimal
      a:= TDog.create();
      a.doAction;//这里将调用到TDog.eat
      a.free;
      a:=TCow.create();
      a.doAction;//这里将调用到TCow.eat
      a.free;
       ...
      //at last free a;
      

  9.   

    学delphi最好的地儿是她的帮助文档。
    谁有同感?
      

  10.   


    此贴不错,学习了!只是弱弱的问一句:
    override与overload之间的区别?
    为何称override就是oop中的特性?(覆盖)而overload不是oop的特性.
    假如:
      TAnimal = class(Tobject)
      public:
        procedure fly();virtual;abstract;
        procedure run();  TBird = class(TAnimal)
      public:
        procedure fly();override;
        procedure run();overload;  var
        mbird:TAnimal;
      begin
        mbird:=TBird.create(); //此时mbird拥有了TBird的fly()方法.我想问的是为何不能直接用重载啊!
                               //反正TBird类肯定会自己定义run()方法,那么我使用TBird.create(),然后直接
                                   //使用mbird.run(),难道不行吗?  end;
      //以上是小弟的疑惑,请高手指正.
         
      

  11.   

    我有一个例子
    一个“建筑构件”的虚类,有“屋顶”、“地板”、“墙壁”、“柱子”和“楼梯”等虚函数。然后,建立若干子类如“石头建筑构件”,“屋顶”被override成“石头屋顶”,“地板”被override成“石头地板”……
    还有子类如“木头建筑构件”、“金属建筑构件”……再一个类
    建设建筑=class
     procedure 平房(构件:建筑构件);
     procedure 楼房(构件:建筑构件);
     procedure 凉亭(构件:建筑构件);
    end;
    实现
    procedure 平房(构件:建筑构件);
    begin
     构件.地板; 
     构件.墙壁;
     构件.墙壁;
     构件.墙壁;
     构件.墙壁;
     构件.屋顶;
    end;
    procedure 凉亭(构件:建筑构件);
    begin
     构件.地板; 
     构件.柱子;
     构件.柱子;
     构件.柱子;
     构件.柱子;
     构件.屋顶;
    end;procedure 楼房(构件:建筑构件);
    begin
     构件.地板; 
     构件.墙壁;
     构件.墙壁;
     构件.墙壁;
     构件.墙壁;
     构件.楼梯; 
     构件.地板; 
     构件.墙壁;
     构件.墙壁;
     构件.墙壁;
     构件.墙壁;
     构件.屋顶;
    end;
    假设有一个“建设建筑”的对象“新建筑”
    要造“石头楼房”:
    begin
     新构件=石头建筑构件.create;
     新建筑.楼房(构件);
    end;
    要造“木头平房”:
    begin
     新构件=木头建筑构件.create;
     新建筑.平房(构件);
    end;
    ……