Delphi类型和引用

2019-07-24 11:29:54 浏览数 (1)

概要介绍: 类类型和下面要讲到的类引用类型是一种特殊的数据类型,是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时将产生异常导致程序中止。 九:怎样调用方法 调用方法跟调用普通的过程或函数相似,也是用方法名加实参来调用。不同的是方法必须由类或对 象来引用,也就是说必须加类型限定符。如果您使用了With语句,则这个就相对简化了。 十:类方法 Object pascal中还有一种称为类方法的特殊方法,类方法跟构造有些相似,其相似之处在于它们 都能由类来引用,而不必先创建一个对象实例,也就是说类方法不依赖于任何类的具体实例。 类方法可以是过程,也可以是函数,在声明时必须在Procedure 或fucntion 前加Class保留字, 例如: Type TClass=Class Class Function GetClassName:String; End; 上例中,声明了一个类方法,它是一个返回类型为字符串的函数。 在程序中,您可以直接由类来引用类方法,例如: Var MyString:String; MyString:=TClass.GetClassName; 由于类方法不依赖于对象实例,那么在类方法的定义中,也就不能出现任何对象字段的访问。 类方法通常用于返回诸如类名等住处,因为这类信息独立于对象实例,是相对固定的。 当用类来引用类方法时,除了一般的参数外,实际上还隐含传递了Self 参数,这个参数总是表示该类方 法声明所在的类。注意Self表示的是类而不是对象,因此不能用self来引用类中的字段和一般的方法以 及属性。不过您可以使用Self引用类的构造和其它类方法。这些是不依赖于实际的对象实例的。 类方法也可以由对象实例引用,这种情况下,self传递的是对象实例的类。 十一:类类型中的属性 属性有点类似于字段,因为属性也是类的数据,不过跟字段不同的是,属性还封装了读写属性的方 法。属性可能是Delphi的程序员接触最多的名词之一。因为操纵Delphi的元件主要是通过读取和修改元 件的属性来实现的,例如要改变窗口的标题是修改Form的Caption属性,要改变窗口文件的字体就是修 改Form的Font属性。 Delphi的属性还有个显著的特点就是,属性本身还可以是类类型,例如Font属性就是TFont类型的类。 十二:声明属性的语法 Object Pascal使用保留字Property声明属性,其语法如下: property 标识符 属性接口 属性子句; 属性的声明由保留字Property,标识符,属性的数据类型以及可选的属性接口和可选的属性子句构 成。属性的数据类型可以是除了文件类型外的任意类型,包括构造类型。 通常是把属性的值放在一个字段中,然后用Read和Write指定的方法去读或写字段的值。 示例如下: Type TClass=Class Private FMyProperty:AClassType; procedure SetProperty(Const Value:AClassType); Published property Myproperty :AClassType Read FMyProperty Write SetProperty; End; 上例中声明了一个TClass类型的类,声明了一个字段FMyProperty(将私有字段标识符以F打头是 DELPHI程序员遵循的一个习惯,在很多源代码中可以看到这一点),它的数据类型是某种数据类型, 还声明了一个方法,最后声明了一个属性MyProperty。它的数据类型和字段是一样,并且指定,访问 该属性即是取出FMyProperty 的值,同时还用SetProperty方法来修改这个值。这是因为私有字段在 类外部是不可见的,则用户修改属性时,即可内部处理一些私有字段达到完美封装的目的。在Delphi4 中,有一个新特性称为类补全。其意义是指当您在书写代码时,只需要输入属性保留字,标识符和数 据类型,打上分号,然后按下CTRL+SHIFT+C键(如果是中文环境,这可能会调用出中文输入法,所 以,可以在该行点击鼠标右键,从快捷菜单中选择类补全),这样, Delphi会为您自动加上适当的 Read和Write字句,一般情况下您根本不必修改它加入的任何代码。除此之外,你还可以使用一个返回 类型与属性数据类型一样的函数来读取它的值。这和设置值是一样的。 在程序中访问属性是很简单的,例如假设创建TClass类型的对象MyObject,一个与属性同类型的 变量MyVarible,程序可以这么写: MyVarible:=MyObject.MyProperty; Myobject.MyProperty:=NewValue 跟访问字段和方法一样,要访问属性也需要加对象限定符,当然如果With语句则可以简化。 和字段不同的是,属性不能作为变量参数来传递,也不能用@来引用属性的地址。 十三:属性子句 属性子句可以有四类,分别的Read,Write,Store,Default。其中Read和Write子句用于指定访问属 性的方法或字段。 Read和Write子句中指定的方法或字段只能在类的private部分声明,也就是说它们是私有的。这样 能保证对属性的访问不会干扰到这些方法的实现,也能防止程序员不小心破坏了数据结构。 Read子句用于指定读取属性的方法或字段,通常可以是以F打头,与属性同名的私有字段,也可以 是一个不带参数的函数,返回的类型就是属性的类型。并且函数名通常以Get加属性名组成。从语法上 讲,可以没有Read子句,这时我们称这个属性是只写的,不过这种情况极少。 Write子句用于定修改属性的方法,通常是一个只带一个与属性同类型的参数的过程,这个参数用于传 递属性新的值,并且过程名通常以Set加属性名组成。 在Write子句指定的方法的定义中,通常首先是把传递过来的值跟原先的值比较,如果两者不同, 就把传递过来的属性值保存在一个字段中,然后再对属性的修改作出相应的反应。这样当下次读取属性 值时,读取的总是最新的值。如果两者相同,那就什么也不需要干。 从语法上,可以没有Write子句,这时候属性就是"只读"的。只读的属性在Delphi中是常见的,只读的 属性不能被修改。 Store子句用于指定一个布尔表达式,通过这个布尔表达式的值来控制属性的存贮行为。注意,这 个子句只适用于非数组的属性。 Stored子句指定的布尔表达式可以是一个布尔常量,或布尔类型的字段,也可以是返回布尔值的函 数。当表达式的值为False时,不把属性当前的值存到Form文件中(扩展名.DFM),如果表达式的值为 True,就首先把属性的当前值跟Default子句指定的缺省(如果有的话)比较,如果相等,就不存贮, 如果不等或者没有指定缺省值,就把属性的当前值存在Form文件中。 Default子句用于指定属性的缺省,在Delphi的对象观察器中,您可能已经注意到大多的属性都有一 个缺省值,这些缺省值就是通过Default子句指定的。 Default子句只适用于数据类型为有序类型或集合类型的属性,并且Default后必须跟一个常量,常 量的类型必须与属性的类型一致。 十四:数组属性 所谓数组属性,就是说属性是个数组。它是由多个类型的值组成的,其中每个值都有一个索引号, 不过跟一般的数组不同的是,一般的数组是个构造类型,您可以把数组作为一个整体参与运算如赋值或 传递等,而对数组属性来说,只能访问其中的每一个元素。 声明一个数组属性的程序示例如下: Property MyStr[Index:integer]:String Read GetMyStr Write SetMyStr; 上例中,声明了一个数组属性MyStr,它的元素类型是String,索引变量是Index,索引变量的类型 是integer,上例中还同时声明了read子句和Write子句。 从上面的例子可以看出来,声明一个数组属性的索引变量,跟声明一个过程或函数的参数类型,不 同的是数组属性用方括号,而过程或函数用圆括号。索引变量可以有多个。 对于数组属性来说,可以使用Read和Write子句,但Read和Write子句只能指定方法而不是字段,并 且object Pascal规定,Read 子句指定的方法必须是一个函数,函数的参数必须在数量和类型上与索引 变量一一对应,其返回类型与数组属性的元素类型一致。Write子句指定的方法必须是一个过程,其参 数是索引变量再加上一个常量或数值参数,该参数的类型与数组属性的元素类型一致。例如上例声明的 MyStr属性,Read和Write子句的声明如下: Function GetMyStr(Index:Integer):String; Procedure SetMyStr(Index:Integer;const NewElement:String); 十五:访问数组属性 访问数组属性中的元素跟访问一般数组中的元素一样,也是用属性名加索引号,例如: MyStr[1]:='This is a Sample'; if StrLen(MyStr[2])=10 then ... 十六:多重索引的数组属性 数组索引允许使用多重索引,相当于多维数组一样,相应地由Read和write子句指定的方法的参数也 应当一一对应。 Delphi的TStringGrid元件的Cells属性就是一个典型的多重索引的数组属性,其声明如下: property Cells[ACol,ARow:Integer]:String Read GetCells Write SetCells; Function GetCells(ACol,ARow:Integer):String; Procedure SetCells(ACol,ARow:Integer;const Value:String); 上例中,声明了一个数组属性Cells,它有两个索引ACol和ARow,元素的类型是String,同时还声明 了Read和Write子句。注意GetCells和SetCells的参数。 访问一个多重索引的数组属性中的某个元素,就象访问一个多维数组中某个元素一样,例如: Cells[1,2]:='This is Sample'; Caption:=Cells[2,8]; 十七:缺省的数组属性 如果声明一个数组属性时加上Default指示字,表示这个属性是缺省的数组属性,对于缺省的数组 属性,有一个很有趣的功能,就是您不必通过属性名加索引号来访问其中的某个值,而只要用对象名 加索引号来访问。典型的例子是TMainMenu元件的Items属性,其声明如下: Property items[Index:Integer]:TMenuitem Read Getitem;Default; 上例中声明了一个数组属性Items,其元素类型是TMenuitem,没有Write子句表示这个属性是只读 的,加上Default指示字表示这个属性是缺省的数组特性,当您要访问Items属性的某个值时,您可以这 么写(假设mainmenu1 是TMainMenu的实例对象): MainMenu1[1].caption:='&File'; 这种写法相当于这么写: MainMenu1.items[1].Caption:='&File'; 缺省属性通常是这个类最重要也是最常用的属性,把它设为缺省属性可以简化对它的访问。 一个类只能有一个缺省属性,如果多于一个,那么怎样确定是那一个呢?^_* 十八:索引子句 从声明数组属性的语法可以看出,数组属性可以带索引子句,索引子句由指示字Index加一个整数常量 构成,整数常量的值只能在-32767和32767之间,程序示例如下: property picturePlus:Tbitmap index 0 read GetPicture Write SetPicture; property PictureMinus:Tbitmap index 1 read GetPicture Write SetPicture; Property PictureOpen:TBitmap index 2 read GetPicture Write Setpicture; 上例声明了3个带索引子句的属性,其索引号分别是0、1、2。 索引子句主要用于使多个属性共享同一个访问方法,上例中,3个属性的Read 子句指定的都是Get- Picture,3个 属性的Write子句指定的都是SetPicture。 多个属性共享相同的访问方法,访问方法即根据索引子句来区别不同的属性。索引子句给属性分配 一个相异的索引号,因此,共享的方法必定要有一个Index参数,用于传递当前的索引号,请看GetPic- ture和SetPicture的声明: Function GetPicture(Index:Integer):TBitmap; Procedure SetPicture(Index:Integer;Value:TBitmap); 当程序访问带有索引子句的属性时,程序自动将索引号传递给方法。 如果访问方法本身还有其它参数时,对于Read子句来说,Index 参数必须是最后一个参数,对于 Write子句指定的过程来说,Index参数必须是倒数第二个参数,因为最后一个参数总是属性的最新值。 带索引子句的属性,其Read和write子句指定的只能是方法而不能是字段。 十九:特性重载 所谓属性重载,就是在基类中声明的属性,可以在派生类中重新声明,包括改变属性可见性。重新指定 访问方法和存贮子句以及缺省子句等。 最简单的重载,就是在派生类中这么写; property 属性名; 这种重载通常用于只改变属性的可见性,其它什么也不改变,例如属性在基类中是在Protected部 分声明,现在把它移到Published部分声明。 属性重载的原则是,派生类中只能改变或增加子句,但不能删除子句,请看下面的程序示例: type TBase=Class protected property Size:Integer read FSize; property Text:String read GetText Write SetText; property Color:TColor read FColor Write SetColor Stored False; end; 上面是基类的声明: Type TDerived=class(TBase) protected property Size Write SetSize; published property Text; property Color Stored True Default clBlue; end; 上面是派生类的声明,对于基类中的Size属性,增加了Write子句,对于基类中的Text属性,改在 Published部分声明,对于基类中的color属性,首先是改在Published部分声明,其次是改变了Stored 子句中的表达式,从False改为True,并且为它增加了一个Default子句。 二十:类成员的可见性 面对对象编程的重要特征之一就是类成员可以具有不同的可见性,在object pascal中,是通过这么几 个保留字来设置成员的可见性的:published, public, protected ,private,Automated。例如: Type TClass=Class private X,Y,Z:integer S:String[128]; public constructor Create(X,Y,Z:integerS:String); Destructor Destroy;override; Procedure Display;virtual; function GetStr:string;virtual; property Caption:String read GetStr Write Buffer; End; 上例中,X,Y,Z,S等字段是在Private部分声明的,表示它们是私有的,Public部分声明的几个 方法是公共的。 再请看下面的例子: Type TClass=Clas X,Y,Z:integer; Public S:String[80]; End; 上例中,X,Y,Z这三个字段的前面没有任何描述可见性的保留字,那么它们属于哪一类的可见性 呢? Object pascal规定,当类是在{$M }状态编译或者继承的是用{$M-}状态编译的基类,上述例子中的 X,Y,Z字段属于Published,否则就是Public。 Published 在Published部分声明的成员,其可见性与在Public部分声明的成员的可见性是一样的,它们都是 公共的,所谓公共的就是说这些成员可以被其它类的实例引用,Published和Public的区别在于成员的 运行期类型信息不同,delphi的元件库VCL正是通过运行期类型信息来访问元件的属性值的,此外Delphi .的IDE还通过运行期类型信息决定Object Inspector中的属性列表。 注意:只有当编译开关$M的状态为{$M }时或者基类是用{$M }编译的时,类的声明中才能有Publish- ed部分,换句话说,编译开关$M用于控制运行期类型信息的生成。 在Published部分声明的成员一般是属性,不过也可以声明字段,Object Pascal规定, 在Published 部分声明的字段只能是类类型的,如果是其它类型的字段中只能在Public、Protected或Private部分声 明。在Published部分声明的属性不能是数组属性,另外属性的数据类型只能是有序类型、部分实型 (single,Double,Extended,Comp)、字符串类型、小集合类型、类类型或者方法指针类型,其中小集合 类型是指集合的基类型的上下界序号在0到15之间的集合。 Public 在public声明的成员是公共的,也就是说,它们虽然是在某个类中声明的。但类的实例也可以引用, 相当于C语言中的外部变量,例如,假设应用程序由两个Form构成,相应的单元是Unit1和Unit2,您希望 Unit2能共享Unit1的整型变量count,你可以把count在TForm1类中的Public部分声明,然后把Unit1加到 unit2的interface部分就可以了。 注意:面向对象的编程思想其特征之一就是隐藏复杂性,除非您必须把某个成员在不同类之间共享, 一般来说尽量不要把成员声明在类的Public部分,以防止程序意外地不正确地修改了数据。 Private 在Private部分声明的成员是私有的,它们只能被同一个类中的方法访问,相当于C语言中的内部变 量,对于其它类包括它的派生类,Private部分声明的成员是不可见的,这就是面向对象编程的数据保护 机制,程序员不必知道类实现的细节,只需要关心类的接口部分。 实际上Object Pascal对私有成员的限制比C 要宽松一些,如果把两个类放在同一个单元声明,那么就可 以通过对象名来引用对方的私有成员,例如: type TForm1=class(TForm) private X:Integer; End; Tmy=class procedure GetMy; End; 以上声明了两个类,其中TForm1还声明了一个私有的字段X. Var Form1:TForm1; procedure TMy.GetMy; Begin form1.X:=1; End; 上例中首先声明了一个TForm1类型的变量Form1 ,您可能发现在TMy类的方法GetMy类的定义中,通 过Form1引用了Tform1中的私有字段X。 protected Protected与private有些类似,在protected部分声明的成员是私有的(受保护的),不同的是在 protected部分声明的成员在它的派生类中是可见的,并且成为派生类中的私有成员。 在Protected部分声明的成员通常是方法,这样既可以在派生类中访问这些方法,又不必知道方法实现 的细节。 Automated C 的程序员可能对这个保留字比较陌生,在Automated部分声明的成员类似于在Public部分声明的 成员,它们都是公共的,唯一的区别在于在automated部分声明的方法和特性将生成OLE自动化操作的类 型信息。 注意:automated只适用于基类是TAutoObject的类声明中,在automated部分声明的方法,其参数 和返回类型(如果是函数的话)必须是可自动操作的,在automated部分声明的特性其类型包括数组属 性的参数类型也必须是可自动操作的,否则将导致错误,可自动操作的类型包括: Byte,Currency,Double,Integer,Single,Smallint,String,TDateTime,Variant, WordBool等。 在Automated部分声明的方法只能采用Register调用约定,方法可以是虚拟的但不能是动态的。 在Automated部分声明的特性只能带Read和Write子句,不能有其它子句如Index , Stored,Default, NoDefault等,Read和Write指定的只能是方法而不能是字段,方法也只能采用Register调用约定,也不 允许对属性重载。 在Automated部分声明的方法或属性可以带一个可选的DispID子句,dispID指示字后面跟一个整数 常量,用于给方法或属性分配一个识别号(ID),如果不带DispID子句,编译器自动给方法或属性分 配一个相异的ID。如果带DispId子句,注意ID不能重复。 二十一:类引用类型声明 以上讲到的类类型,一般不能直接对类类型操作,而只能对类的实例即对象操作。只有类方法以及类的 构造和析构可以直接作用于类本身。那么类引用类型是一种什么样的数据类型呢? 类引用类型实际上就是指向某种类类型的指针,用这个指针可以引用任何同类型的类。 类引用类型的声明语法如下: Type TClassRef=Class of Tobject; 上例中,声明了一个类引用类型TClassRef,它指向TObject类。 注意:声明类类型时,如果基类是Tobject,则基类可以省略不写,但在声明类引用类型时不能省略。 二十二:类引用类型的使用 声明了类引用类型以及类引用类型的变量后,您就可以给这个变量赋值,赋值号的右边必须是与类引用 类型指向的类型赋值相容的类类型,例如: Type Tcontrol=Class(Tcomponent) ... End; TComponentref=class of Tcomponent; TControlRef=Class of TControl; Var Componentref:TcomponentRef; controlRef:TControlRef; 上例中首先声明了一个类类型Tcontrol,声明了两个类引用类型,分别是TcomponentRef和TcontrolRef, 然后分别声明了上述两种类引用类型的变量。 ComponentRef:=Tform; controlRef:=TButton; 上例中,把TForm类型赋给componentRef变量,因为Tform是与Tcomponent赋值相容的类型,把 Tbutton赋给ControlRef,是因为Tbutton是与Tcontrol赋值相容的类型。 以后,凡是TForm和Tbutton能出现的地方,就分别能用componentref和controlref替代。例如: Var MyButton:Tcontrol; MyButton:=ControlRef.Create; 上例中,用Tbutton类型的引用创建了一个Tbutton对象. 注意:给类引用类型的变量赋值时要注意是否赋值兼容,一个类引用类型,只与它指向的类型或该 类型的派生类型是赋值相容的。例如上面的例子中,controlRef引用的是Tcontrol类型,您只能把Tcon- trol类型以及Tcontrol类型的派生类型赋给controlref,如果您把TTimer类型赋给controlRef,编译器 将认为出错,因为TTimer不是Tcontrol类型的兼容类型。 如果类引用类型变量的值为nil,表示该变量还没有引用哪个类。

0 人点赞