关于delphi中对象的疑惑
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TMan = class
private
public
Fname:string;
FAge:integer;
constructor create();
procedure SayHello();
end;
TChinese = Class(TMan)
public
procedure SayHello();
end;
{ TMan }
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
AMan: TMan ;
AChinese:TChinese;
s:string;
i:integer;
begin
AMan.SayHello;
AMan:=TMan.create;
s:=AMan.Fname;
i:=AMan.FAge;
ShowMessage(s);
//为什么不用Create 上面的代码也能运行呢?Create()到底在做什么呢?
end;
{ TMan }
constructor TMan.create;
begin
inherited;
end;
procedure TMan.SayHello;
begin
ShowMessage('TMan.SayHello');
end;
{ TChinese }
procedure TChinese.SayHello;
begin
ShowMessage('TChinese.SayHello');
end;
end.
问题点数:100、回复次数:5Top
1 楼net_morning(矿泉水)回复于 2006-03-07 19:31:18 得分 0
在option里面那个TMan是自动建立的缘故吧?Top
2 楼linzhengqun(风。我回来了)回复于 2006-03-08 09:10:11 得分 0
如果在你的SayHello中访问你的类中的成员,就会出错的。
这个也不知是为何。Top
3 楼GARNETT2183(KingWolves (http://kevin-lu.blogspot.com))回复于 2006-03-08 09:23:13 得分 0
呵呵...这个有意思。。。第一次知道...关注中...
Top
4 楼l0f(凌风)回复于 2006-03-08 09:51:31 得分 0
神奇!Top
5 楼chijingde(AD)回复于 2006-03-08 11:11:22 得分 100
《DELPHI的原子世界》中如是说:
我们知道,TObject是所有对象的基本类,那么,一个对象到底是什么? DELPHI中的任何对象都是一个指针,这个指针指明该对象在内存中所占据的一块空间!虽然,对象是一个指针,可是我们引用对象的成员时却不用写成这样的代码MyObject^.GetName,而只能写成MyObject.GetName,这是Object Pascal语言扩充的语法,是由编译器支持的。使用C++ Builder的朋友就很清楚对象与指针的关系,因为在C++ Builder的对象都要定义为指针。对象指针指向的地方就是对象存储数据的对象空间,我们来分析一下对象指针指向的内存空间的数据结构。
对象空间的头4个字节是指向该对象类的虚方法地址表(VMT - Vritual Method Table)。接下来的空间就是存储对象本身成员数据的空间,并按从该对象最原始祖先类的数据成员到该对象类的数据成员的总顺序,和每一级类中数据成员的定义顺序存储。
类的虚方法地址表(VMT)保存从该类的原始祖先类派生到该类的所有类的虚方法的过程地址。类的虚方法,就是用保留字vritual声明的方法,虚方法是实现对象多态性的基本机制。虽然,用保留字dynamic声明的动态方法也可实现对象的多态性,但这样的方法不保存在虚方法地址表(VMT)中,它只是Object Pascal提供的另一种可节约类存储空间的多态实现机制,但却是以牺牲调用速度为代价的。
即使,我们自己并未定义任何类的虚方法,但该类的对象仍然存在指向虚方法地址表的指针,只是地址项的长度为零。可是,在TObject中定义的那些虚方法,如Destroy、FreeInstance等等,又存储在什么地方呢?原来,他们的方法地址存储在相对VMT指针负方向偏移的空间中。其实,在VMT表的负方向偏移76个字节的数据空间是对象类的系统数据结构,这些数据结构是与编译器相关的,并且在将来的DELPHI版本中有可能被改变。
因此,你可以认为,VMT是一个从负偏移地址空间开始的数据结构,负偏移数据区是VMT的系统数据区,VMT的正偏移数据是用户数据区(自定义的虚方法地址表)。TObject中定义的有关类信息或对象运行时刻信息的函数和过程,一般都与VMT的系统数据有关。
一个VMT数据就代表一个类,其实VMT就是类!在Object Pascal中我们用TObject、TComponent等等标识符表示类,它们在DELPHI的内部实现为各自的VMT数据。而用class of保留字定义的类的类型,实际就是指向相关VMT数据的指针。
对我们的应用程序来说,VMT数据是静态的数据,当编译器编译完成我们的应用程序之后,这些数据信息已经确定并已初始化。我们编写的程序语句可访问VMT相关的信息,获得诸如对象的尺寸、类名或运行时刻的属性资料等等信息,或者调用虚方法或读取方法的名称与地址等等操作。
当一个对象产生时,系统会为该对象分配一块内存空间,并将该对象与相关的类联系起来,于是,在为对象分配的数据空间中的头4个字节,就成为指向类VMT数据的指针。
我们再来看看对象是怎样诞生和灭亡的。看着我三岁的儿子在草地上活蹦乱跳,正是由于亲眼目睹过生命的诞生过程,我才能真真体会到生命的意义和伟大。也只有那些经历过死别的人,才会更加理解和珍惜生命。那么,就让我们理解一下对象的产生和消亡的过程吧!
我们都知道,用下面的语句可以构造一个最简单对象:
AnObject := TObject.Create;
编译器将其编译实现为:
用TObject对应的VMT为依据,调用TObject的Create构造函数。而在Create构造函数调用了系统的ClassCreate过程,系统的ClassCreate过程又通过存储在类VMT调用NewInstance虚方法。调用NewInstance方法的目的是要建立对象的实例空间,因为我们没有重载该方法,所以,它就是TObject类的NewInstance。TObjec类的NewInstance方法将根据编译器在VMT表中初始化的对象实例尺寸(InstanceSize),调用GetMem过程为该对象分配内存,然后调用InitInstance方法将分配的空间初始化。InitInstance方法首先将对象空间的头4个字节初始化为指向对象类对应VMT的指针,然后将其余的空间清零。建立对象实例之后,还调用了一个虚方法AfterConstruction。最后,将对象实例数据的地址指针保存到AnObject变量中,这样,AnObject对象就诞生了。
同样,用下面的语句可以消灭一个对象:
AnObject.Destroy;
TObject的析构函数Destroy被声明为虚方法,它也是系统固有的虚方法之一。Destory方法首先调用了BeforeDestruction虚方法,然后调用系统的ClassDestroy过程。ClassDestory过程又通过类VMT调用FreeInstance虚方法,由FreeInstance方法调用FreeMem过程释放对象的内存空间。就这样,一个对象就在系统中消失。
对象的析构过程比对象的构造过程简单,就好像生命的诞生是一个漫长的孕育过程,而死亡却相对的短暂,这似乎是一种必然的规律。
在对象的构造和析构过程中,调用了NewInstance和FreeInstance两个虚函数,来创建和释放对象实例的内存空间。之所以将这两个函数声明为虚函数,是为了能让用户在编写需要用户自己管理内存的特殊对象类时(如在一些特殊的工业控制程序中),有扩展的空间。
而将AfterConstruction和BeforeDestruction声明为虚函数,也是为了将来派生的类在产生对象之后,有机会让新诞生的对象呼吸第一口新鲜空气,而在对象消亡之前可以允许对象完成善后事宜,这都是合情合理的事。其实,TForm对象和TDataModule对象的OnCreate事件和OnDestroy事件,就是在TForm和TDataModule重载的这两个虚函数过程分别触发的。
此外,TObjec还提供了一个Free方法,它不是虚方法,它是为了那些搞不清对象是否为空(nil)的情况下能安全释放对象而专门提供的。其实,搞不清对象是否为空,本身就有程序逻辑不清晰的问题。不过,任何人都不是完美的,都可能犯错,使用Free能避免偶然的错误也是件好事。然而,编写正确的程序不能一味依靠这样的解决方法,还是应该以保证程序的逻辑正确性为编程的第一目标!
有兴趣的朋友可以读一读System单元的原代码,其中,大量的代码是用汇编语言书写的。细心的朋友可以发现,TObject的构造函数Create和析构函数Destory竟然没有写任何代码,其实,在调试状态下通过Debug的CPU窗口,可清楚地反映出Create和Destory的汇编代码。因为,缔造DELPHI的大师门不想将过多复杂的东西提供给用户,他们希望用户在简单的概念上编写应用程序,将复杂的工作隐藏在系统的内部由他们承担。所以,在发布System.pas单元时特别将这两个函数的代码去掉,让用户认为TObject是万物之源,用户派生的类完全从虚无中开始,这本身并没有错。虽然,阅读DELPHI的这些最本质的代码需要少量的汇编语言知识,但阅读这样的代码,可以让我们更深刻认识DELPHI世界的起源和发展的基本规律。即使看不太懂,能起码了解一些基本东西,对我们编写DELPHI程序也是大有帮助。
Top




