Delphi语法进阶
一,模块化编程
所有的Delphi应用程序都有相同的基本结构。程序模块 = 可选择的说明部分 + 实现语句部分。
工程程序模块=⇒库单元模块==>模块说明部分interface
==>事件处理模块implementation=⇒说明部分
==>实现语句部分
所有的Delphi应用程序都有相同的基本结构。当程序逐渐复杂时,在程序中加入模块即可。例如在库单元模块中加入事件处理模块,向工程中加入库单元模块等。模块化编程使得程序结构良好,并且对数据具有保护作用。delphi模块化的编程有利于针对性的分工和利于维护等,不过用Delphi做模块最简捷的办法就是用 bpl,至于自动发现和注册机制就简单了,统一调用接口,可以找找插件化的代码。另外每个模块编译输出是以CM为后缀名的,然后是通过主程序加载的CM后 缀名的模块,如果在界面中有哪块不要了,直接删掉目录下的CM后缀名的相应的模块即可,那界面就不会显 示出那个模块了。
1,库单元Unit的结构:单元是源程序代码的载体,带有.pas扩展名。库单元也是程序模块。
unit <库单元名称>
interface //程序模块的说明部分。库单元的接口部分。将过程或函数的标题部分放interface部分则可被公有访问,而其函数实体必须在implementation部分出现,可以使用标题简写:procedure或function+过程或函数的名称即可+程序的实现部分
uses <选择性的库单元列表> //uses列出想要包含进来的单元
{全局声明}
implementation //语句部分,事件处理模块。可以且只能被此库单元的事件处理过程使用
uses <选择性的库单元列表>
{局部声明}
{过程和函数的执行部分}
initialization {选择性的} //某程序的每个库单元中的initialization部分都会被编译器优先执行再运行其他代码,且只运行一次
{选择性的初始化程序}
finalization {选择性的} //包含了单元退出时的代码。在程序退出时运行并且只运行一次。参见:Delphi中initialization和finalization
{选择性的退出时程序}
end.
2,事件处理过程的结构
procedure TForm.Button1Click(Sender Tobject);
var {程序模块的说明部分}
Name : string;
begin {程序模块的语句部分}
Name := Edit1.Text;
Edit2.Text := 'Welcome to Delphi'+Name;
end; {程序模块结束}
二,库单元
1,库单元的使用
VCL就是由多个程序库单元组成,当创建窗体时,系统自动建立一个和窗体有关的库单元;当往窗体中加入可视化控件时,系统自动在程序库单元中加入了和控件有关的库单元。Delphi还提供许多其他预定义的程序库单元。要使用非可视化控件则把它先加入uses子句中。
* 库单元A接口中USE单元B,单元B接口USE 单元C,则单元A可以直接访问单元C。注意单元B接口不能再USE单元A,否则会循环单元引用编译错误。
* 库单元A实现部分中USE单元B,单元B实现部分中USE单元C,则单元A仍然不可以直接访问单元C。而单元B实现部分中可以再USE单元A,这种循环单元引用不会有编译错误。
2,建立与窗体无关的新库单元:选用File|New Unit。这时一个新的库单元加入了工程,新库单元的代码如下:
unit Unit2; //系统按次序自动生成单元文件名称,该文件会被编译成具有.DCU后缀的文件,而链接到工程的可执行文件上。
interface
implementation
end.
3,将已有的库单元加入工程:(可用Open Project)先打开该工程|再选用File|Open File|选择该单元文件(.PAS文件)|选OK。
三,包
包是若干个单元集中在一起以类似于DLL的形式存储的BPL文件模块(Borland Package Library)。BPL独立编译(可减小EXE),只是在EXE运行时候链入。
1,类型:
• 运行期包,EXE运行时才被调用。如VCL50.DPL包。
• 设计期包,包包含了组件、属性和组件编辑器等在设计程序时需要的元素,可以用Component|Install Package命令将一个设计期的包安装到组件库中, 如DCL*.BPL包
• 既是运行期又是设计期的包,使用方便但因内容繁杂而低效。
• 既不是运行期又不是设计期的包,该包很少见,通常被其他包引用,而不是直接被应用程序引用。
2,包使用设置:在Project|Options的Packages对话框中选中Build with Runtime Packages复选框,以后当编译和运行应用程序时,应用程序就会包文件分出来。
3,包的语法
DDPK包源文件常用包编辑器(启动方式:File | New | Package)创建。DPK的语法格式如下:
package PackageName;
requires Package1,Package2….; //这个包需要调用的其他包
contains //这个包所包含的单元(单元所引用的单元也会间接地包含到包中,除非已列入requires子句中)。注意此处的单元不能被requires子句中的包所包含
Unit1 in 'Unit1.pas',
Unit2 in 'Unit2.pas',
…;
end.
四,面向对象编程
1,OOP:从D5开始Delphi已经完全脱离Pascal而独立,Object Pascal在语法上与Pascal大致相同,但是编程思想已经完全转向了面向对象的程序设计(OOP)。有利于代码重用和扩展维护。Delphi用户不需了解OOP就可编程。当用户在建立新窗体、添加新组件以及处理事件时,大部分相关代码会由Delphi自动产生。但是了解OOP可以更好地理解Delphi尤其是组件编程。 以下是OOP的3个基本概念:
(1)封装性:隐藏封包内部细节。
(2)继承性:继承以共享代码。能用来建立VCL这样的多层次的对象。Delphi不支持多继承(但可通过包含类或接口来实现)
(3)多态性:相同的表达式,不同的操作,能以灵活的扩展实现最终的代码重用
2,对象的术语:
* 类/对象:如记录般有各种域的封装了数据和方法的抽象结构,经常可以被理解为可视化组件,如按钮、标签、表等。Delphi提供了一系列组件,正是这些组 件构成了Delphi面向对象程序设计的基础。Delphi的对象通过全面支持继承、封装和多态性,提供了面向对象编程的强大功能。
* 对象/类的实例instance:是由类定义的数据类型的变量。当程序运行时实例占用一些内存。实例与对象/类的关系就像变量与类型的关系。
* 域(field):也被称为域定义或实例变量,域是包含在对象中的数据变量。在对象中的一个域就像是在Pascal记录中一个域,在C++中它被称为数据成员。
* 方法( method ):属于一个对象的过程和函数名,在C++中它被称为成员函数。
* 属性( property ):属性是外部代码访问对象中的数据和代码的访问器,属性隐藏了一个对象的具体实现的细节。
3, 面向对象的编程环境:在某些工具中你能操纵对象但不能创建对象, 如VB中的ActiveX 控件(OCX )。能用但不能创建或派生它。这样的环境被称为基于对象的环境。Delphi是完全的面向对象的环境,这表示能用现存组件创建新的可视或不可视的对象,甚 至可以是设计时的窗体。
五,使用对象
1,让我们结合Delphi的实例讨论对象的概念:
当您要建立一个新工程时,Delphi 将显示一个窗体作为设计的基础。在程序编辑器中,Delphi将这个窗体说明为一个新的对象类型,并同时在与窗体相关联的库单元中生成了创建这个新窗体对象的程序代码。
unit Unit1;
interface
uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm) {窗体的类型说明开始} //使用保留字Class声明了一个名叫TForm1的类类型,该类型是从类TForm继承下来的一个对象。注意,类类型的标识符一般以“T”打头,以区别于其他数据类型。另 外,在Delphi中,大量的构造数据类型都是以“T”打头的。如果不指明祖先类,Delphi默认该类继承自TObject类。
Button1: TButton; //现在TForm1对象有了一个名为Button1的域:它是您在窗体中加入的按钮。TButton是一个对象类型,Button1是Tbutton的一个实例。它被TForm1对象所包含,作为它的数据域。每当您在窗体中加入一个部件时,部件的名称就会作为TFom1的域加入到类型说明中来。在Delphi中,您所编写的事件处理过程都是窗体对象的方法。每当您建立一个事件处理过程,就会在窗体的对象类型中说明一个方法。
procedure Button1Click(Sender: TObject); //
private // 对象私有域,其中的数据域和方法只能被本库单元访问
{ Private declarations }
public //对象公有域,其中的数据域和方法可以被其它库单元访问
{ Public declarations }
end; {窗体的类型说明结束}
var
Form1: TForm1; {说明一个窗体变量} //声明TForm1类的一个实例Form1
implementation //对象过程或函数的程序代码可以放在库单元的implementation部分。
{$R *.DFM}
end.
* 当您使用Object Inspector来改变对象(部件)的名称时,这个名称的改变会反映到程序中。例如,在Object Inspector中将Form1的Name属性命名为ColorBox,您会发现在类型说明部分,会将前文的TForm1改为:TColorBox=class(TForm); 并且在变量说明部分,会说明ColorBox为TColorBox类型的变量,由Delphi自动产生的事件处理过程名称会自动改为TColorBox.Button1Click;但您自行编写的实现部分的代码却不会被自动修改。因此,如果您在改变Name属性前编写了程序,则您必须将事件处理过程中的对象名称进行改变。所以,原先的Form1.Color要改为ColorBox.Color。
2,对象变量的赋值
如果两个变量类型相同或兼容,您可以把其中一个对象变量赋给另一个对象变量。例如,对象TForm1和TForm2都是从TForm继承下来的类型,而且Form1和Form2已被说明过,那么您可以把Form1赋给Form2:
祖先 :=后代
Form2 :=Form1;
type
TDataForm = class(TForm)
Button1:TButton;
Edit1:TEdit;
DataGrid1:TDataGrid;
Database1:TDatabase;
TableSet1:TTableSet;
VisibleSession1:TVisibleSession;
private{私有域说明}
public
{公有域说明}
end;
var
AForm:TForm;
DataForm:TDataForm;
AForm :=DataForm; //祖先 :=后代 ,是合法的:
procedure TForm1.Button1Click(Sender:TObject); //Sender是TObject类型即最顶的祖先,所以任何对象都可以赋值给它。此处虽然您没有看见赋值的程序代码,但事实上发生事件的部件或控制部件已经赋给Sender了,这就是说Sender的值是响应发生事件的部件或控制部件的。
begin
end;
procrdure TForm1.Memo1DragOver(Sender,Source:TObject;X,Y:integer; //Source参数也是TObject类型,被赋值为那个被拖曳的对象
State:TDragState;var Accept:Boolean);
begin
Accept :=Source is TLabel; //确保只有标签可以被拖曳,Accept只有在用户拖曳一个标签时才为真,并作为变参输出到函数之外。
end;
procedure TForm1.Memo1DragDrop(Sender,Source:TObject;X,Y:Integer); //把Memo字型赋值成和放入的标签一样的字型
begin
Memo1.Font := (Source as TLabel).Font; //只有当Source是一个标签时,这个事件处理过程才允许这个赋值发生。
end;
3,对象的声明,实例化,构造,析构/撤销,自定义方法,
建立非可视化对象:在Delphi中使用的大部分对象都是您在设计和运行期间可以看见的部件,例如编辑框、按钮等;一些部件,如通用对话框(Common dialog box)等,在设计时看不见,而在运行时可以看见;另外有些部件,例如计时器(Timer)、数据源(Data Source)部件等,在程序的运行期间没有任何可视化的显示,但您却可以在您的应用程序中使用它们。
type
TBoogieNights = class //等效于TBoogieNights = Class(TObject),因为所有的对象都是TObject对象的后代
Dance:Boolean;
procedure DoTheHustle; //声明方法
end;
procedure TBoogieNights.DoTheHustle; //定义方法体时必须用完整的名字。注意对象的Dance域能被直接访问,不提倡。
bigin
Dance:=True;
end;
Var
BoogieNights :TBoogieNights;
BoogieNights := TBoogieNights.Create( ); //调用构造器来实例化对象。编译器将对对象的每一个域进行初始化,所有数字被赋值为0,所有指针为nil,所有字符串为空。等效于 BoogieNights := TBoogieNights.Create;
BoogieNights.Free( ); //析构/撤销 Free( ) = If not Nil then Destroy( ) ,直接调用Destroy( )有危险。Free方法也是从TObject中继承过来的。把注销放在try…finally程序模块的finally部分,而把对象的程序代码放在try部分是编程的好习惯。这样,即使您的程序代码在使用对象时发生了异常事件,也会确保您为这个对象分配的内存会被释放。
* 构造器主要用来为对象创建实例并为对象中的域分配内存并进行初始化使得对象处在可以使用的状态。Object Pascal的对象至少有一个构造器称为Create( ),但一个对象可以有多个构造。根据不同的对象类型, Create( )可以有不同的参数或不带参数。Create方法继承自TObject类型
* 警告:凡是创建的,都需要释放。在Object Pascal中(C++也是),一个静态声明的对象在离开它的作用域时自动调用它的析构方法,但要对隐式动态生成的对象手动调用析构方法。例外:第一条是 当对象被其他对象拥有时,第二种情况是引用计数的对象(像TInterfaceObject和TComObject),当最后一个引用释放时,它将被析 构。
*注意最好不要直接访问对象的域,因为实现对象的细节可能改变。相反用访问器属性来访问对象,它不受对象细节的影响
4,方法的类型
TFoo = class
procedure IAmAStatic;
procedure IAmAVirtual; virtual;
procedure IAmADynamic; dynamic;
procedure IAmAMessage(var M:Tmessage); message wm_SomeMessage;
end;
* 1. 静态方法:是方法的缺省类型,如同过程和函数那样调用。编译时到其地址把运行信息静态地链入EXE文件。静态方法执行的速度最快,但不能被覆盖来支持多态性。
* 2. 虚拟方法:如同过程和函数那样调用。由于虚拟方法能被覆盖所以编译时建立虚拟方法表(VMT)以在运行时找地址。一个对象的VMT表包括自己和祖先的所有虚拟方法,所以比动态方法占更多内存多,但它执行得比较快。
* 3. 动态方法:和虚拟方法基本相似,只是它们的调度系统不同。编译器为每一个动态方法的指定唯一数字和其地址构造一个动态方法表(DMT )。DMT表中仅有自身声明的动态方法,有可能需要祖先的DMT表来访问它其余的动态方法。所以动态方法比虚拟方法用的内存要少,但执行起来有可能较慢。
* 4. 消息处理方法:在关键字message后面的值指明了这个方法要响应的消息。用消息处理方法来响应Windows的消息,这样就不用直接来调用它。
5,方法的覆盖
在Object Pascal覆盖一个方法用来实现O O P的多态性概念。通过覆盖使一方法在不同的派生类间表现出不同的行为。
TFooChild = class(TFoo)
procedure IAmAVirtual; override; //编译器就会用新的方法覆盖替换VMT中原先的方法,原方法仍在
procedure IAmADynamic; override; //编译器就会用新的方法覆盖替换DMT中原先的方法,原方法仍在
procedure IAmAVirtual; virtual; //等效于重新声明,新建方法,而非覆盖
procedure IAmADynamic; dynamic; //等效于重新声明,新建方法,而非覆盖
procedure IAmAStatic; override; //对一个静态方法进行覆盖,新方法将完全覆盖替换在祖先类中的同名方法,原方法不再存在了。
end;
*如果Delphi显示了一个标识符被重复定义的信息,就有可能是一个数据域和其祖先对象(例如TForm)的一个数据域有了相同的名称。可以尝试改变这个标识符的名称。
您可以覆盖(Override)一个方法。通过在后代对象中覆盖说明一个与祖先对象重名的方法,就可以覆盖一个方法。如果想使这个方法在后代对象中作和祖先对象中一样的工作但是使用不同的方式时,您就可以覆盖这个方法。Delphi不推荐您经常覆盖方法,除非您想建立一个新的部件。覆盖一个方法,Delphi编译器不会给出错误或警告提示信息。 6,方法的重载
就像普通的过程和函数,方法也支持重载,使得一个类中有许多同名的方法带着不同的参数表,能重载的方法必须用o v e r l o a d指示符标识出来,可以不对第一个方法用o v e r l o a d。下面的代码演示了一个类中有三个重载的方法:
type
TFoo = class
procedure AMethod(I:Integer);overload;
procedure AMethod(S:String);overload;
procedure AMethod(D:Double);overload;
end;
7,重新引入方法名称
有 时候,需要在派生类中增加一个方法,而这个方法的名称与祖先类中的某个方法名称相同。在这种情况下,没必要覆盖这个方法,只要在派生类中重新声明这个方 法。但在编译时,编译器就会发出一个警告,告诉你派生类的方法将隐藏祖先类的同名方法。要解决这个问题,可以在派生类中使用r e i n t r o d u c e指示符,下面的代码演示了r e i n t r o d u c e指示符的正确用法:
type
TSomeBase = class
procedure Cooper;
end;
TSomeClass = class
procedure Cooper;reintroduce;
end;
*Self:在所有对象的方法中都有一个隐含变量称为S e l f,S e l f是用来调用方法的指向类实例的指针。S e l f由编译器作为一个隐含参数传递给方法。
8,属性
可以把属性看成是能对类中的数据进行修改和执行代码的特殊的辅助域。对于组件来说,属性就是列在Object Inspector窗口的内容。下面的例子定义了一个有属性的简单对象:
T M y O b j e c t是包含下列内容的对象:一个域(被称为S o m e Va l u e的整型数)、一个方法(被称为S e t S o m e Va l u e的过程)和一个被称为v a l u e的属性。S e t S o m e Va l u e过程的功能是对S o m e Va l u e域赋值,Va l u e属性实际上不包含任何数据。Va l u e是S o m e Va l u e域的辅助域,当想得到Va l u e中的值时,它就从S o m e Va l u e读值,当试图对Va l u e属性设置值时,Va l u e就调用S e t S o m e Va l u e对S o m e Va l u e设置值。这样做的好处有两个方面:首先,通过一个简单变量就使得外部代码可以访问对象的数据,而不需要知道对象的实现细节。其次,在派生类中可以覆盖诸 如S e t S o m e Va l u e的方法以实现多态性。
修改对象域的属性或调用方法:对象的名称.属性名称或调用方法
Edit1.Text := 'Welcome to Delphi'; //改变编辑框Text属性
Edit1.ClearSelection; //清除编辑框部件中选中的文本
beginwith (Edit1) do //可以使用With语句进行简化
begin
ClearSelection; //等效于Edit1.ClearSelection;
Text := 'Welcome to Delphi';
end;
9,可见性表示符
Object Pascal能通过在声明域和方法时用p o r t e c t e d、p r i v a t e,p u b l i c,p u b l i s h e d和a u t o m a t e d指示符来对对象提供进一步的控制。使用这些关键字的语法如下:
在每一个指示符下能声明任意多个方法或域。书写时要注意缩进格式。下面是这些指示符的含义:
• p r i v a t e,对象中的这部分只能被相同单元的代码访问。用这个指示符对用户隐藏了对象实现的细节并阻止用户直接修改对象中的敏感部分。
• p r o t e c t e d,对象中的这部分成员能被它的派生类访问,这样不仅能使对象向用户隐藏实现的细节并为对象的派生类提供了最大的灵活性。
• p u b l i c,这部分的域和方法能在程序的任何地方访问,对象的构造器和析构方法通常应该是p u b l i c。
• p u b l i s h e d,对象的这一部分将产生运行期类型信息( RT T I ),并使程序的其他部分能访问这部分。Object Inspector用RT T I来产生属性的列表。
• a u t o m a t e d,这个指示符其实已经不用了,保留这个指示符的目的是为了与Delphi 2.0的代码兼容,
下面的代码是以前介绍过的T M y O b j e c t对象,其中通过增加指示符提高了对象的完整性:
现在,对象的用户不能直接修改S o m e Va l u e的值了,要修改对象的数据就必须通过Va l u e属性来实
现。
10,友类
在C + +语言中有友类的概念(允许在其他类中访问私有数据和私有函数的类)。在C + +中这是通过关键字f r i e n d来实现的,严格地说,在Object Pascal中没有类似的关键字,但有类似的功能。凡是在相同单元声明的对象都认为是友类,都可以访问其他对象的私有成员。
11,对象的范围
procedure TForm1.Button1Click(Sender:Tobject);
begin
Color :=clFuchsia; //为整个窗体Form1着色,等效于Form1.Color :=clFuchsia; 当前对象可以省略对象变量的名称
Edit1.Color :=clLime;end; //当前对象的部件就要加上对象变量的名称,
//如果Edit1是在另一个窗体Form2之中,那么您需要把Unit2加入Unit1的uses子句中,然后将第二句改为:Form2.Edit1.Color := clLime;
12, 对象的秘密
在Object Pascal中的类实例实际上是指向堆中的类实例数据的3 2位指针。当访问对象的域、方法和属性时,编译器会自动产生一些代码来处理这个指针。因此对于新手来说,对象就好像是一个静态变量。这意味着, Object Pascal无法像C++那样在应用程序的数据段中为类分配内存,而只能在堆中分配内存。
13,TObject所有对象的祖先
所有的窗体对象都是TForm的后代。
TForm类型=⇒TForm1类型 (虽只含有域Button1和方法Button1Click,但继承到TForm的全部属性和方法) =⇒Form1 当您在窗体中加入了部件或编写了事件处理过程时,Form1才成为您自己的类型。
TObject类在Delphi的Visual Component Library的顶部,这就意味着所有的Delphi对象都是TObject的后代。TObject==>TComponent=⇒TControl 继承祖先所有功能,并有自己特殊功能
因 为所有对象都是从TO b j e c t继承来的,每一个类都从TO b e j c t继承了一些方法,所以可以对对象的性能进行一些特殊的假定。每一个类都能告诉你它的名字、类型和它是否从某个类派生而来。作为一个程序员,不必关心编译 器的实现细节而只要能利用对象所提供的功能就够了。
TO b j e c t是一个特殊的对象,它在s y s t e m单元中定义,编译器对TO b j e c t是完全清楚的,下面是TO b j e c t的定义:
在D e l p h i的联机帮助中你将看到每一个方法的文档。
在 这里特别要注意那些前面有c l a s s关键字的方法。在一个方法前加上关键字c l a s s,使得方法向其他通常的过程和函数一样调用而不需要生成一个包含这个方法的类的实例,这个功能是从C + +的s t a t i c函数借鉴来的。要小心,不要让一个类方法依赖于任何实例信息,否则编译时将出错。
六,接口
对于Object Pascal语言来说,最近一段时间最有意义的改进就是从Delphi 3开始支持接口(interface),
接口定义了能够与一个对象进行交互操作的一组过程和函数。对一个接口进行定义包含两个方面的内
容,一方面是实现这个接口,另一方面是定义接口的客户。一个类能实现多个接口,即提供多个让客
户用来控制对象的“表现方式”。
正如名字所表现的,一个接口就是对象和客户通信的接口。这个概念像C++中的PURE VIRTUAL
类。实现接口的函数和过程是支持这个接口的类的工作。
在本章你将学到接口的语言元素,要想在应用程序中使用接口,请参考第2 3章“COM和ActiveX;
1. 定义接口
就像所有的Delphi类都派生于TObject一样,所有的接口都派生于一个被称为是IUnknown的接口,IUnknown在system单元中定义如下:
正如你所看到的,接口的定义就像是类的定义,最根本的不同是在接口中有一个全局唯一标识符(GUID),它对于每一个接口来说是不同的。对IUnknown的定义来自于Microsoft的组件对象模型(COM)规范。在第2 3章有详细的介绍。
如果你知道怎样创建Delphi的类,那么定义一个定制的接口是一件简单的事情,下面的代码定义了一个新的接口称为IFoo,它包含一个被称为F1( )的方法:
提示在Delphi的IDE中,按Ctrl+Shift+G键可以为一个接口生成一个新的GUID。
下面的代码声明了一个称为IBar的接口,它是从I F o o接口继承来的:
2. 实现接口
下面的代码演示了在一个类TFooBar中怎样实现IFoo和IBar接口:
注意,一个类可以实现多个接口,只要在声明这个类时依次列出要实现的接口。编译器通过名称来把接口中的方法与实现接口的类中的方法对应起来,如果一个类只是声明要实现某个接口,但并没有具体实现这个接口的方法,编译将出错。
如果一个类要实现多个接口,而这些接口中包含同名的方法,必须把同名的方法另取一个别名,请看下面的程序示例:
3. implements指示符
implements指示符是在Delphi 4中引进的,它的作用是委托另一个类或接口来实现接口的某个方法,这个技术有时又被称为委托实现,关于implements指示符的用法,请看下面的代码:
在上面例子中的implements指示符是要求编译器在Foo属性中寻找实现IFoo接口方法。属性的类型必须是一个类,它包含IFoo方法或类型是IFoo的接口或IFoo派生接口。implements指示符后面可以列出几个接口,彼此用逗号隔开。
implements 指示符在开发中提供了两个好处:首先,它允许以无冲突的方式进行接口聚合。聚合(Aggregation)是COM中的概念。它的作用是把多个类合在一起 共同完成一个任务,详见第2 3章。其次,它能够延后占用实现接口所需的资源,直到确实需要资源。例如,假设实现一个接口需要分配一个1MB的位图,但这个接口很少用到。因此,可能平 时你不想实现这个接口,因为它太耗费资源了,用implements指示符后,可以只在属性被访问时才创建一个类来实现接口。
4. 使用接口
当 在应用程序中使用接口类型的变量时,要用到一些重要的语法规则。最需要记住的是,一个接口是生存期自管理类型的,这意味着,它通常被初始化为n i l,它是引用计数的,当获得一个接口时自动增加一个引用计数;当它离开作用域或赋值为n i l时它被自动释放。下面的代码演示了一个接口变量的生存期自管理机制。
关于接口变量的另一个规则是,一个接口变量与实现这个接口的类是赋值相容的,例如,下面的代码是合法的:
最后,类型强制转换运算符a s可以把一个接口类型的变量强制类型转换为另一种接口(在第2 3章详细介绍)。示例如下:
七,运行期类型信息
运行期类型信息(RTTI)是一种语言特征,能使应用程序在运行时得到关于对象的信息。RTTI是Delphi的组件能够融合到IDE中的关键。它在IDE中不仅仅是一个纯学术的过程。
由于对象都是从TObject继承下来的,因此,对象都包含一个指向它们的RTTI的指针以及几个内建的方法。下面的表列出了TObject的一些方法,用这些方法能获得某个对象实例的信息。
函数 返回类型 返回值
ClassName() string 对象的类名
ClassType() TClass 对象的类型
InheritsFrom() Boolean 判断对象是否继承于一个指定的类
ClassParent() TClass 对象的祖先类型
InstanceSize() word 对象实例的长度(字节数)
ClassInfo() Pointer 指向RTTI的指针
Object Pascal提供了两个运算符as和is,用它们通过RTTI能对对象进行比较和强制类型转换。
关键字as是类型转换的一种新的形式。它能把一个基层的对象强制类型转换成它的派生类,如果转换不合法就产生一个异常。假定有一个过程,想让它能够传递任何类型的对象,它应该这样定义:
Procedure Foo(AnObject:TObject);
在这个过程如果要对AnObject进行操作,要把它转换为一个派生对象。假定把AnObject看成是一个TEdit派生类型,并想要改变它所包含的文本(TEdit是一个Delphi VCL编辑控件),用下列代码:
(FOO as TEdit).Text:= 'Hello World.';
能用比较运算符来判断两个对象是否是相兼容的类型,用is运算符把一个未知的对象和一个已知类型或实例进行比较,确定这个未知对象的属性和行为。例如,在对AnObject进行强制类型转换前,确定AnObject和TEdit是否指针兼容:
If (Foo is TEdit) then
TEdit(Foo).Text:= 'Hello World.';
注意在这个例子中不能用as进行强制类型转换,这是因为它要大量使用RTTI,另外还因为,在第一行已经判断Foo就是TEdit,可以通过在第2行进行指针转换来优化。
参见
深入Delphi 个人认为不错的delphi概要介绍,言简意明,值得一看|
Delphi中initialization和finalization
“多态性”是C++最关键和核心的一个特性,“动态绑定技术”是C++编译器最重要的一个技术!欢迎C++高手进来指正我的粗浅理解、
Delphi中的线程类 TThread