这个问题困惑了好久还是没想通,为什么说虚函数要运行时才能决定,编译时不是就可以根据建立的虚函数表将虚函数地址确定了么?
解决方案 »
- 我用delphi写了个服务程序,可是关机的时候就这个服务没反映,也关机不了。
- 如何关闭word的工具栏,在线等待!!!!!!!!
- 一个有关数据保存的问题
- 两个模态窗体之间传递变量?
- 如何另程序大小变小啊
- ??? 如题:如何将Excel导入到外网计算机 ???
- 怎样判断ClientdataSet的是否在edit状态(等分涨起来一定多加分)?
- 用CreatOleObject创建ADODB对象进行数据库编程的大虾请进。
- 在做delphi安装盘时,如何象网上有些软件如:cuteftp,最后就一个可执行的安装文件,数据库都自然包括在内,是要什么其它什么软件吗,delphi自带的那个不行吗?
- 有谁知道EXE文件的格式?
- Delphi中这些代表什么啊?这是我在一个获取系统托盘图标的程序中看到的,Shell_TrayWnd, TrayNotifyWnd, SysPager, ToolbarWindow32
- 关于DELPHI父窗口和子窗口的问题
也就是说虚函数不能像静态函数那样直接用call 函数名来调用
调用时打开view-debug-cpu信息,看看汇编在调用函数时用什么方法,是直接用函数名做函数地址还是经过计算后生成的函数地址!
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来决定的)
看看 VCL源代碼剖析就明白了,是通過查表的。
这种情况下,比如在一个类的实现代码里有如下一段: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 的地址,这样也可以免去执行时查表的资源消耗啊,为什么这样不行呢?
咱们换个例子,楼主再琢磨一下下面这个单元(考虑一下编译原理):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;
而把实现留给子类去做。
我给你解释的通俗点。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;
谁有同感?
此贴不错,学习了!只是弱弱的问一句:
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;
//以上是小弟的疑惑,请高手指正.
一个“建筑构件”的虚类,有“屋顶”、“地板”、“墙壁”、“柱子”和“楼梯”等虚函数。然后,建立若干子类如“石头建筑构件”,“屋顶”被override成“石头屋顶”,“地板”被override成“石头地板”……
还有子类如“木头建筑构件”、“金属建筑构件”……再一个类
建设建筑=class
procedure 平房(构件:建筑构件);
procedure 楼房(构件:建筑构件);
procedure 凉亭(构件:建筑构件);
end;
实现
procedure 平房(构件:建筑构件);
begin
构件.地板;
构件.墙壁;
构件.墙壁;
构件.墙壁;
构件.墙壁;
构件.屋顶;
end;
procedure 凉亭(构件:建筑构件);
begin
构件.地板;
构件.柱子;
构件.柱子;
构件.柱子;
构件.柱子;
构件.屋顶;
end;procedure 楼房(构件:建筑构件);
begin
构件.地板;
构件.墙壁;
构件.墙壁;
构件.墙壁;
构件.墙壁;
构件.楼梯;
构件.地板;
构件.墙壁;
构件.墙壁;
构件.墙壁;
构件.墙壁;
构件.屋顶;
end;
假设有一个“建设建筑”的对象“新建筑”
要造“石头楼房”:
begin
新构件=石头建筑构件.create;
新建筑.楼房(构件);
end;
要造“木头平房”:
begin
新构件=木头建筑构件.create;
新建筑.平房(构件);
end;
……