前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Delphi类型和引用

Delphi类型和引用

作者头像
Vaccae
发布2019-07-24 11:29:54
2.4K0
发布2019-07-24 11:29:54
举报
文章被收录于专栏:微卡智享微卡智享

概要介绍: 类类型和下面要讲到的类引用类型是一种特殊的数据类型,是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,表示该变量还没有引用哪个类。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档