概要介绍:
类类型和下面要讲到的类引用类型是一种特殊的数据类型,是Object Pascal面向对象编程的基础。
一:类类型概述
和以前介绍的几种数据类型相比,类类型具有如下特点:
类类型的成员可以是不同的数据类型,这一点跟记录类型相似,因此,类类型首先是由不同的字段
组成的。
类类型除了包含数据以外,还包含了操纵数据的方法及特性。类类型把数据和方法封装在一起。
类类型具有可继承性,所谓继承就是一个新的类类型,不必什么都从新定义,只需要继承一个已有
的类型再加上自己的成员就构成一个新的类类型。事实上Delphi中所有的元件都是从一些共同的祖先类
继承下来的,利用类的可继承性,您可以编写您自己的类元件,并把它加到Delphi的环境中去。
被继承的类我们称为基类,继承下来的类我们称为派生类,基类的成员自动成为派生类的成员。类
的继承具有传递性,例如假设T3继承了T2,而T2又是继承了T1,可以认为T3也继承T1。在Delphi中,所
有的类都是从一个共同的类TObject继承下来的,TObject类的声明在System单元中,它定义了一些操纵
类的最基本的方法,因此,Tobject也被称为缺省祖先类。
TObject是一个抽象类,它的派生类可以对TObject中的方法重载,包括对它的构造 Create 和析构
Destory的重载。
二:类类型的声明
类类型的声明比较复杂,其语法如下:
Type 类=class(基类)
[成员列表]
End;
从以上语法可以看出,类类型可以指定一个祖先类型,表示该类型是从这个基类继承下来,如:
Type TClass=Class(TObject)
这个例子,声明了一个名叫TClass的类类型,它是从类TObject继承下来的。注意,在Delphi中,类
名一般都是以T打头,以区别于其它数据类型。如果省略了指定基类,则表明直接从TObject继承下来。
成员列表的定义是这样
字段定义
方法定义
属性定义
类类型可以有三类成员分别是字段、方法、特性。字段的声明类型于记录类型中字段的声明,类类
型中的方法又可以分为4类,分别是构造、析构、过程和函数。分别用 Constructor、 Destructor、
Procedure、Function这4个保留字声明,类类型中的特性用保留字Property来声明,一个典型的类类型
示例如下:
Type TClass=Class
Private
FX,FY,FZ:Integer;
FS:String[128];
Public
Constructor Create(X,Y,Z:Integer;S:string);
Destrutor Destroy;override;
Procedure Display;Virtual;
Function SetStr(const Value:string);
Publish
Property Caption:String read FS Write SetStr;
End;
上例中,声明了4个字段,数据类型分别是Integer和String。在Delphi中,一般私有变量字段都以
F打头。并且声明了一个构造CREATE,一个析构Destroy,一个过程Display,一个函数SetStr。另外还声
明了一个属性Caption。其它的语法元素如Private、Public等将在后面介绍。
注意:跟其它数据类型不同的是,类类型的声明只能出现在程序的Type区,而其它数据类型则可以
在Var区或过程或函数或方法的Begin语句之前声明。因此类类型的作用域总是全局的。
顺便提一下,类类型包括包含类类型分量的构造类型不能作为文件类型的基类型。
三:类类型的字段
类类型中的字段也就是类的数据部分,其声明方法同记录中字段的声明语法相似:
标识符:类型
其中字段的类型可以是各种数据类型,甚至是另一个类类型。
要访问对象的某个字段,跟访问记录变量中的字段类似,是用对象名加小圆点和字段名。
四:类类型的方法
类类型中的方法是个特定的名称,从形式上看也不过是一些过程或函数,不同的是方法是在类类型内部
声明的并只操纵类本身,因此在Object Pascal中方法有其特定含义。我们姑且都称它为方法。
方法的声明和定义
方法定义 方法首部;方法指示字
方法的声明跟变通的过程或函数的声明既相似也有不同的地方,相似的是声明时只需写出方法的首
部,不同的是声明方法时可以加上方法指示字。
方法分为4种类型,分别是构造、析构、过程和函数。它们分别用 Constructor、 Destructor、
Procedure、Function这4个保留字声明。
声明了方法以后,还需对方法加以定义。注意:方法的声明是在类类型的声明内部进行的,而方法
的定义则是在类类型的声明之外即程序的Implementation区进行,并且方法名前必须加类型限定符。
在定义方法时,可以直接使用类中已声明的字段,不需要作为参数来传递,访问这些字段时也不需
要用引用限定符,例如:
程序的Type区:
Type TClass=Class
X,Y,Z:integer;
Procedure Method(Param1:integer;Param2:Real);
End;
程序的Implementation区:
Procedure TClass.Method(Param1:integer;param2:Real);
Begin
X:=1;
Y:=Param1;
Z:=floattoint(Param2);
end;
上例中,首先声明了一个类类型TClass,其中声明了一方法Method,然后就是方法Mehod的定义,方
法本身有两个参数,在方法的执行体中对类的字段的引用是直接的,不需要加引用限字符。
跟普通的过程或函数一样,调用方法时要注意形参和实参以及返回类型的匹配。不过在调用方法时
Object Pascal还隐含传递了一个参数Self,这个参数可能不大好理解,因为这涉及到虚拟与多态的概念。
我们可以初步把它理解为一个指向输出该方法的对象实例的指针。举例说明,我们在Delphi的可视环境下
建立一个表单窗口时,它实际上是创建了一个从TForm类中继承下的类类型。如果仔细看Delphi为您生成
的代码,你可以完全看到上面的各个规则是如何被实现的。同时,当我们在表单上布置各种控件时,也是
在增加这个类类型的特殊成员和方法等。然后如果您可以看一看工程文件,可以看到APPLICATION对象首
先要创建一个类类型的实例。你可能已经注意到,如果您需要在你的表单上动态创建一个对象时,往往可
以看到创建时需要一个OWNER,而你可能看到的很多代码中,这个OWNER是用self来指定的。这个时候,这
个Self指的就是这个表类型的实例。(表单的构造和析构是一个特殊的过程,所以在您的单元里看不到)
五:方法指示字
方法指示字的声明如下:
方法定义;virtural|dynamic|message;register|pascal|cdecl|stdcall;abstract;
其中,|的意思是指从这些指示字中任选一个。
方法指示字是可以不加的,这种情况下声明的方法是静态的(除了构造),静态的方法在调用时,在
编译期就已指定了输出该方法的对象实例。您还可以把一个方法声明为虚拟的(Virtual)或动态的(Dynamic)
或消息处理的(Message)。
虚拟方法
如果一个方法通常是一个基类的某个方法声明为虚拟的,那么它的派生类就可以重新定义这个方法,
例如:
Type
TDraw=Class
Procedure Draw(X,Y:integer);Virtual;
End;
Type
TRectangel=Class(TDraw)
Procedure Draw;override;
End;
Typpe
TEllipse=Class(TDraw)
Procedure Draw;override;
End;
Var
MyDraw:TDraw;
Begin
MyDraw:=TRectangle.Create;
MyDraw.Draw;
MyDraw.Destroy;
MyDraw:=TEllipse.Create;
MyDraw.Draw;
MyDraw.Destory;
End;
上例中,首先声明了一个基类TDraw,其中方法Draw声明为虚拟的,然后声明了两个派生类,分别重
载了Draw方法(方法的定义略),然后依次建立了TRectangle类型的对象和TEllipse类型的对象。
关键的问题是,当程序调用Draw时,究竟调用的是哪个Draw,是基类的Draw还是的派生类的Draw
呢?很显然,这个问题在编译期是无法决定的,而需要编译器在运行期根据调用这个虚拟方法的对象实
例来决定(有的资料把这称为迟后联编或滞后联编)。上例中,当调用Draw的对象是TRectangle类型的
对象时,实际调用的就是TRectangle类的Draw,如果调用Draw的对象是TEllipse类型的对象,实际调用
的就是TEllipse类的Draw。如果TRectangle类或TEllipse类中没有声明Draw,那调用的就是基类TDraw
中的Draw。
注意:重载的方法必须与基类中被继承的方法在参数个数、参数顺序,数据类型上完全匹配,如果
是函数的话,还要求函数的返回类型一致。
上面的例子中,声明派生类的Draw时,后面加了一个Override指示字,表示被声明的方法是重载基类中
的同名的虚拟或动态方法。
加了Override指示字后,这个方法自动成为虚拟方法,也就是说不需要重复写Virtaul指示字。
注意:要重载基类中的方法,必须使用override指示字,如果不加这个指示字,而在派生类中声明
了与基类同名的方法,则新声明的方法将隐藏被继承的方法。
动态方法
所谓动态方法,非常类似于虚拟方法,当把一个基类中的某个方法声明为动态方法时,派生类可以
重载它。
不同的是,被声明为动态的方法不是放在类的虚拟方法表中,而是由编译器给它一个索引号(一般
不直接用到这个索引),当调用动态方法时,由索引号决定调用方法的哪个具体实现。
从功能上讲,虚拟方法和动态方法几乎完全相同,只不过虚拟方法在调用速度上比较愉,但代码长度稍
长,而动态方法在调用速度上稍慢而在代码长度上短一此.一般来说,在虚拟和动态之间还是选择使用
虚拟为好。
消息处理方法
除了可以把方法声明为虚拟的和动态的之外,您还可以把方法声明为用于处理消息的(也称消息句
柄)。消息句柄主要用于响应并处理某个特定的消息。
把一个方法声明为消息句柄的示例如下:
Type
TMyControl=Class(TWinControl)
Procedure WMPaint(var Message:TWMPaint);Message WM_PAINT;
End;
上例声明了一个名叫TMyControl的类类型,其中还声明了一个过程WMPAINT,只有一个变量参Message,
过程的首部后用保留字Message表示这是个消息句柄,后跟一个常量WM_PAINT表示消息句柄要响应的消
息。
Object pascal规定,作为消息句柄的方法只能是过程,并且只能有一个参数,这个参数还必须是
变量参数,用于传递消息的详细住处。
注意:消息句柄不能使用Cdecl调用约定,也不能用Virtual, Dynamic, Overide或Abstract等指
示字。
在消息句柄中,您还可以调用缺省的消息句柄,例如上例中,您声明了一个处理WM_PAINT消息的
方法,事实上Delphi提供了处理这个消息的缺省的句丙,不过句柄的名称可能与您声明的方法名称不一
样,也就是说您未必知道缺省句柄的名称,那怎么调用呢?实际上,你只要使用一个保留字Inherited
就可以了,
例如:
Procedure TMyControl.WMPaint(var message:TWMPaint);
Begin
Inherited;
End;
上例中,消息句柄首先调用WM_PAINT消息的缺省句柄,然后再执行自己的代码。使用Inherited保留
字总是能自动找到对应于指定消息的缺省句柄(如果有的话)。
使用inherited保留字还有个好处,就是如果Delphi没有提供处理该消息的缺省句柄,程序就会自己调用
TObject的DefaultHandler方法,这是个能对所有消息进行基本处理的缺省句柄。
六:调用约定
所谓调用约定,就是参数传递的方式,Object Pascal规定,缺省的方式是寄存器方式(Register),这是
种最有效的方式,除了Register方式之外,您还可以指定采用Pascal方式,Cdecl方式和StdCall方式。
七:抽象方法
所谓抽象,首先必须是虚拟的或动态的,其次它只有声明而没有定义,只能在派生类中定义它(重载)。
抽象方法在C++中称为虚函数,至少含有一个虚函数的类称为抽象类,抽象类不能建立对象实例。
声明一个抽象方法是用Abstract指示字,例如:
Type TClass=Class
Procedure method;Virtual;Abstract;
End;
上例中声明了一个抽象方法,注意;Virtual或Dynamic指示字必须写在Abstract指示字之前。
在派生类中重载抽象方法,跟重载普通的虚拟或动态方法相似,不同的是在重载的方法定义中不能使
用inherited保留字,因为基类中抽象方法本来就没有定义。同样的道理,如果抽象方法没有被重载,程序
不能调用这个抽象方法,否则会引起运行期异常。
八:构造和析构
构造和析构是类类型中两种特殊的方法,用于控制类的对象如何创建和初始化,如何删除等行为。一个类
可以没有也可以有多个构造和析构,构造和析构也可以继承。
从形式上讲,构造和析构也是过程或函数,不同的是普通的过程和函数是用Procedure或Function声
明的,而构造和析构分别是用Constructor和Destructor声明的,例如:
Type
TShape=Class(TGraphicControl)
Private
FPen:TPen;
FBrush:TBrush;
Procedure PenChanged(Sender:TObject);
Procedure BrushChanged(Sender:TObject);
Public
Constructor Create(Owner:Tcomponent);override;
Destructor Destroy;override;
End;
上例中,声明了一个构造Create和一个析构Destroy,实际上它们是分别继承了基类TGraphicControl
中的构造和析构,并重载的。
构造主要用于控制如何创建类的对象以及如何初始化等行为,跟一般的方法不同的是,一般的方法
只能由类的对象实例引用,而构造可以不依赖于某个特定的对象实例,直接由类来引用,这一点跟后面
要介绍的类方法相似。例如,在创建一个新的对象时,尽管还没有对象实例存在,您仍然可以调用类的
构造。程序示例如下:
Var MyShape:TShape;
MyShape:=TShape.Create(Self);
上例中,首先声明了一个TShape类型的常量,然后调用TShape类的构造Create创建了一个TShape类
型的对象。
注意:尤其是熟悉C++的程序员要注意,在C++中,当您用一个类类型声明一个对象时,将自动调
用类的构造函数(这也是C++中一般不需要显式调用构造函数的原因),而在object Pascal中,当您声
明了一个类类型的变量,实际上这个变量还不能称为类的对象,您必须调用类的构造才算真正创建的对
象。
当您用类来引用类的构造时,实际上程序做了这么一些工作:
首先是在堆中开辟一块区域用于存贮对象,然后把这块区域初始化,包括把有序类型的字段清零,
指针类型和类类型的字段设为nil,字符串类型的字段清为空,上述工作称为缺省初始化。
缺省初始化完毕以后,就是执行构造中指定的动作。
新创建的对象由构造返回,返回值的类型必须就是类的类型。
上面介绍的是构造由类来引用,事实上构造还可以由对象实例引用。不过这时候不会再在堆中分配一块
区域,也不执行缺省初始化工作,更不返回一个新的对象实例,它只是执行构造中指定的动作。
跟普通的方法一样,在构造中要访问类的字段,也不需要加类型限定符,例如,上面声明的构造
Create的定义如下 :
Constructor TShape.Create(Owner:TComponent);
Begin
inherited Create(Owner);
Width:=65;
Height:=65;
FPen:=TPen.Create;
FPen.Onchange:=PenChanged;
FBrush:=TBrush.Create;
FBrush.Onchange:=BrushChanged;
End;
上例中,对TShape类型中的几个字段的访问都不需要加类型限定符,包括对它基类中字段的访问,
例如Width和height。
您可能已发现,构造的第一行是Inherited Create(Owner),其中Inherited是保留字,Create是基
类的构造名,事实上大多数构造都是这么写的。这句话的意思是首先调用基类的构造来初始化基类的字
段,接下来的代码才是初始化派生类的字段,当然也可以重新对基类的字段赋值。前面已讲到,用类来
引用构造时,程序将自动做一些缺省的初始化工作,也就是说,对象在被创建时,其字段已经有了缺省
的值,除非您在创建对象时赋给这些字段其他值,否则在构造中除了inherited Create(Owner)这一句
外,您不需要写任何代码。
如果在类来引用构造中的过程中发生了异常,程序将自动调用析构来删除还没有完全创建好的对象实例。
构造也可以声明为虚拟的,当构造由类来引用时,虚拟的构造跟静态的构造没有什么区别,当构造
由对象实例来引用时,构造就具有多态性,您可以使用不同的构造来初始化对象实例。
析构的作用跟构造正相反,它用于删除对象并指定删除对象时的动作,通常是释放对像所战胜的堆和先
前占用的其他资源。
构造的定义中,第一句通常是调用基类的构造,而析构正相反,通常是最后一句调用基类的析构,
程序示例如下:
Destructor Tshape.Destroy;
Begin
FBrush.Free;
FPen.Free;
Inherited Destroy;
End;
上例中,析构首先释放了刷子和笔的句柄,然后调用基类的析构。
析构可以被声明为虚拟的,这样派生类就可以重载它的定义,甚至由多个析构的版本存在。事实上,
Delphi中的所有类都是从Tobject继承下来的,TObject的析构名叫Destroy,它就是一个虚拟的无参数的
析构,这样,所有的类都可能重载Destroy。
前面提到,当用类来引用构造时,如果发生运行期异常,程序将自动调用析构来删除还没有完全创建
好的对象。由于构造将执行缺省的初始化动作,可能把指针类型和类类型的字段清为空,这就要求析构在
对这些字段操作以前要判断这些字段是否为nil。有一个比较稳妥的办法是:用Free来释放占用的资源而
不是调用Destroy,例如上例中的FBrush.Free和FPen.Free,如果改用FBrush.Destroy和FPen.Destroy,当
这些指针为nil时将产生异常导致程序中止。
Delphi类型和引用
最新推荐文章于 2023-01-30 11:27:54 发布