在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1 Object Passal的程序结构很特殊,与其它语言如C++,Object Windows等结构都不同。一个Delphi程序由多个称为单元的源代码模块组成。使用单元可以把一个大型程序分成多个逻辑相关的模块,并用来创建在不同程序中使用的程序库。 2 8.1 Program单元 3 Program单元就是Delphi中的项目文件。 4 Program单元是一个特殊的单元,类似于C语言中的Main程序,即为应用程序的主程序。一个程序可以有多个单元组成,也可以只有一个Program单元组成,例如前面我们介绍过的DOS窗口程序就只有一个Program单元组成。下面是一个典型的Program单元:program Project1; 5 uses Forms, 6 Unit1 in 'Unit1.pas' {Form1}; 7 {$R *.RES} 8 begin 9 Application.Initialize; 10 Application.CreateForm(TForm1, Form1); 11 Application.Run; 12 end 13 (1)程序首部指定程序名、以及参数等。 14 (2)Uses语句定义程序用到的所有单元。 15 标识符为单元的名字,各单元之间用逗好(,)隔开,最后结束用分号(;)。 16 注意:每个程序总是自动包含System单元,Program单元的Uses部分不能显式指定。System单元用于实现一些低级的运行时间程序的支持,如文件输入输出(I/O)、字符串操作、浮点运算、动态内存分配等。另外,Delphi在发行时提供了许多预先定义的单元,在构造程序时可以直接使用。例如,如果你将一个核对框放进一个窗体,你就自动地使用了StdCtrls单元,因为TCheckBox构件在StdCtrls中定义。 17 Uses部分列出单元的顺序决定它们初始化的顺序,并影响编译器定位标识符的顺序。如果两个单元定义了一个相同名字的类型,编译器将总是使用前面那个单元的类型。 18 (3)程序块由保留字Begin和End括起来的一段代码组成,用于对程序的初始化。 19 8.2 UNIT单元 20 UNIT单元相当于C语言的子程序。基本上Delphi每个窗体都一个对应的单元。当你为应用程序创建窗体时,你就创建了一个与该窗体相联系的新单元。然而,单元也可以独立于窗体而存在。例如,一个单元可以只包含数学运算程序,而不需要有窗体。 21 一个单元可以由多个程序共享。单元的磁盘文件名后缀为.pas。 22 8.2.1 单元结构 23 不管单元是否与窗体相关,单元的基本结构都是一样的 。UNIT单元由单元首部、接口部分(interface part)、实现部分(implementation part)、可选择的初始化部分(initialization part)、结束部分(finalization part)、end.组成。 24 8.2.2 单元首部 25 单元的首部用保留字Unit开始,后跟单元名。单元名必须遵循标识符的所有一般原则(不能以数字开头等)。下面的单元名将是有效的: 26 Unit Rsgl; 27 8.2.3 接口部分(Interface) 28 在单元名之后是接口部分。接口部分用于声明变量、类型、过程和函数等。在接口部分声明的变量、类型以及过程、函数等是其它使用该单元的程序或单元等都可见的。接口部分用保留字Interface标明开始,用implemention标明结束。接口部分只能含有声明部分。 29 一个单元的接口部分还作为该单元说明文件的起点。虽然接口部分没有告诉你子程序做什么或变量如何使用,但它正确告诉了你的变量、类型、过程、函数等的名字及其调用方法。 30 接口部分本身又可以由几个可选的部分组成,分别是单元的USES语句、常量声明部分、类型声明部分、变量声明部分、过程和函数声明部分。其中常量声明、类型声明、变量声明、过程和函数声明部分用于声明其它使用该单元的单元可以访问的变量、类型、过程和函数等。 31 而USES语句列出该单元要用到的标准单元和其它单元,用于把外部的已定义的常量、类型、变量、过程或函数引入到本单元中使用。USES语句紧跟在Interface之后。 32 8.2.4 实现部分 33 单元的第二部分,称为实现部分(Implementation),主要用于定义接口部分声明过的过程、函数等的代码。实现部分用保留字implementation标明,总是紧随接口部分之后。 34 实现部分也可以用USES语句列出该单元要用到的标准单元和其它单元等。如上面的uses MDIEdit;语句。实际上,实现部分也可以声明变量、数据类型、过程及函数等。 35 但是,在实现部分定义的变量、类型、过程、函数等只能由本单元自己使用(private declarations),使用该单元的其它单元或程序不可见的。私有定义可以隐藏单元的细节。 36 8.2.5 USES子句 37 USES子句用于访问其它单元。例如,如果你要让程序来效仿一个电传打字机,可以在USES包含WinCRT,因为WinCrt含有进行这个仿效所需要的程序。 38 USES WinCRT; 39 Delphi提供了许多预先定义的单元,你可以在程序中直接使用。实际上,当你将一个新构件放入设计的窗体时,DElphi会自动将该构件的单元放入USES子句中。例如,如果你将Color Grid放入窗体,则单元ColorGrd就附加在窗体单元的USES子句末尾,从而ColorGRd单元中接口部分所有定义都是窗体单元可以访问的。 40 要使用USES子句包含单元中的程序,只要在单元名后加上程序名即可。例如,如果要在Unit2中访问Unit1中的ComputePayMent函数。 41 USES子句可以放在接口部分(保留字Interface之后),也可放在实现部分(保留字Implementation之后),但是USES子句必须出现在它所有子程序、数据类型或变量被使用之前 。 42 USES子句放在实现部分可以隐藏单元的内部细节,同时可以避免循环引用发生。 43 8.2.6 初始化部分(Initialization) 44 初始化部分是单元的可选部分,主要用于对单元数据的初始化,一般很少用到。 45 该部分总是在其它程序代码之前运行。如果一个程序包含多个单元,则在程序的其它部分运行之前,每个单元的初始化代码都会先调用,其顺序是单元显示在Uses语句的顺序。 46 8.2.7 完成部分(Finalization) 47 完成部分(Finalization)是初始化过程的反过程,只要单元有初始化部分,才会有完成部分。完成部分对应在Delphi1.0中ExitProc和AddEXitProc函数。 48 完成部分在程序关闭时运行。任何在单元初始化时获得的资源如分配内存、打开文件等通常都在完成部分释放。单元完成部分的执行顺序与初始化部分相反。例如假如程序以A、B、C的顺序初始化单元,则完成过程的顺序是C、B、A。 49 一旦单元的初始化部分开始执行,就必须保证程序关闭时对应的完成部分执行。完成部分必须能够处理不完全初始的数据,因为如果产生异常,初始化代码可能不能完全执行。 50 1.构造 51 构造用建立对象,并对对象进行初始化。通常,当调用构造时,构造类似一个函数,返回一个新分配的并初始化了的类类型实例。 52 构造跟一般的方法不同的是,一般的方法只能在对象实例中引用,而构造既可以由一个对象实例引用,也可以直接由类来引用。当用类来引用类的构造时,实际上程序做了以下工作: 53 (1)首先在堆中开辟一块区域用于存贮对象。 54 (2)然后对这块区域缺省初始化。初始化,包括有序类型的字段清零,指针类型和类类型的字段设置为Nil,字符串类型的字段清为空等。 55 (3)执行构造中用户指定的动作。 56 (4)返回一个新分配的并初始化了的类类型实例。返回值的类型必须就是类的类型。 57 当你用在对象实例中引用类的构造时,构造类似一个普通的过程方法。这意味着一个新对象还没有被分配和初始化,调用构造不返回一个对象实例。相反,构造只对一个指定的对象实例操作,只执行用户在构造语句中指定的操作。 58 例如,在创建一个新的对象时,尽管还没有对象实例存在,仍然可以调用类的构造,程序示例如下: 59 type 60 TShape = class(TGraphicControl) 61 private 62 FPen: TPen; 63 FBrush: TBrush; 64 procedure PenChanged(Sender: TObject); 65 procedure BrushChanged(Sender: TObject); 66 public 67 constructor Create(Owner: TComponent); 68 override; 69 destructor Destroy; 70 override; ... 71 end; 72 constructor TShape.Create(Owner: TComponent); 73 begin 74 inherited Create(Owner);{ Initialize inherited parts } 75 Width := 65;{ Change inherited properties } 76 Height := 65; 77 FPen := TPen.Create;{ Initialize new fields } 78 FPen.OnChange := PenChanged; 79 FBrush := TBrush.Create; 80 FBrush.OnChange := BrushChanged; 81 end; 82 构造的第一行是Inherited Create(Owner),其中Inherited是保留字,Create是祖先类的构造名,事实上大多数构造都是这么写的。这句话的意思是首先调用祖先类的构造来初始化祖先类的字段,接下来的代码才是初始化派生类的字段,当然也可以重新对祖先类的字段赋值。用类来引用构造时,程序将自动做一些缺省的初始化工作,也就是说,对象在被创建时,其字段已经有了缺省的值。所有的字段都被缺省置为0(对于有序类型)、nil(指针或类类型)、空(字符串)、或者 Unassigned (变体类型)。除非想在创建对象时赋给这些字段其它值,否则在构造中除了Inherited Create(Owner)这句外,不需要写任何代码。 83 如果在用类来引用构造的过程中发生了异常,程序将自动调用析构来删除还没有完全创建好的对象实例。效果类似在构造中嵌入了一个try協inally语句,例如: 84 try 85 ...{ User defined actions } 86 except{ On any exception } 87 Destroy;{ Destroy unfinished object } 88 raise;{ Re-raise exception } 89 end; 90 构造也可以声明为虚拟的,当构造由类来引用时,虚拟的构造跟静态的构造没有什么区别。当构造由对象实例来引用时,构造就具有多态性。可以使用不同的构造来初始化对象实例。 91 2.析构 92 析构的作用跟构造正相反,它用于删除对象并指定删除对象时的动作,通常是释放对象所占用的堆和先前占用的其它资源。构造的定义中,第一句通常是调用祖先类的构造,而析构正相反,通常是最后一句调用祖先类的析构,程序示例如下: 93 destructor TShape.Destroy; 94 begin 95 FBrush.Free; 96 FPen.Free; 97 inherited Destroy; 98 end; 99 上例中,析构首先释放了刷子和笔的句然后调用祖先类的析构。 100 析构可以被声明为虚拟的,这样派生类就可以重载它的定义,甚至由多个析构的版本存在。事实上,Delphi中的所有类都是从TObject继承下来的,TObject的析构名为Destroy,它就是一个虚拟的无参数的析构,这样,所有的类都可以重载Destroy。 101 前面提到,当用类来引用构造时,如果发生运行期异常,程序将自动调用析构来删除还没有完全创建好的对象。由于构造将执行缺省的初始化动作,可能把指针类型和类类型的字段清为空,这就要求析构在对这样字段操作以前要判断这些字段释放为Nil。有一个比较稳妥的办法是,用Free来释放占用的资源而不是调用Destroy,例如上例中的FBrush.Free和FPen.Free,Free方法的实现是: 102 procedure TObject.Free; 103 begin 104 if Self <> nil then Destroy; 105 end; 106 也即Free方法在调用Destroy前会自动判断指针是否为Nil。如果改用FBrush.Destroy和FPen.Destroy,当这些指针为Nil时将产生异常导致程序中止。 107 7.2.3 方法指令字 108 声明方法的语法规则中,method directives为方法的指令字。 109 从语法示意图中可以看出,方法按指令字分又可分为三种,分别是虚拟、动态、消息方法,它们分别是方法名后用Virtual,Dynamic,Message保留字指定。也可以不加方法指令字,这种情况下声明的方法是静态的(static)。 110 另外,从语法示意图中可以看出,一个方法也可以像函数那样,指定参数的传递的方式,也即方法的调用约定。一个方法调用约定与通常的过程和函数相同,请参看本书关于过程和函数的部分。 111 1.静态方法 112 缺省情况,所有的方法都是静态的,除非你为方法提供了其它指令字。静态方法类似于通常的过程和函数,编译器在编译时就已指定了输出该方法的对象实例。静态方法的主要优点是调用的速度快。 113 当从一个类派生一个类时,静态方法不会改变。如果你定义一个包含静态方法的类,然后派生一个新类,则被派生的类在同一地址共享基类的静态方法,也即你不能重载静态方法。如果你在派生类定义一个与祖先类相同名的静态方法,派生类的静态方法只是替换祖先类的静态方法。例如: 114 type TFirstComponent = class(TComponent) 115 procedure Move; procedure Flash; 116 end; 117 TSecondComponent = class(TFirstComponent) 118 procedure Move;{该MOVE不同于祖先类的MOVE} 119 function Flash(HowOften: Integer): Integer;{该Flash不同于祖先类的Flash} 120 end; 121 上面代码中,第一个类定义了两个静态方法,第二个类定义了于祖先类同名的两个静态方法,第二个类的两个静态方法将替换第一个类的两个静态方法。 122 2.虚拟方法 123 虚拟方法比静态方法更灵活、更复杂。虚拟方法的地址不是在编译时确定的,而是程序在运行期根据调用这个虚拟方法的对象实例来决定的,这种方法又为滞后联编。 虚拟方法在对象虚拟方法表(VMT表)中占有一个索引号。 124 VMT表保存类类型的所有虚拟方法的地址。当你从一个类派生一个新类时,派生类创建它自己的VMT,该VMT包括了祖先类的VMT,同时加上自己定义的虚拟方法的地址虚拟方法可以在派生类中重新被定义,但祖先类中仍然可以被调用。例如: 125 type TFirstComponent = class(TCustomControl) 126 procedure Move;{ static method } 127 procedure Flash; virtual;{ virtual method } 128 procedure Beep; dynamic;{ dynamic virtual method } 129 end; 130 TSecondComponent = class(TFirstComponent) 131 procedure Move;{ declares new method } 132 procedure Flash; 133 override;{ overrides inherited method } 134 procedure Beep; override;{ overrides inherited method } 135 end; 136 上例中,祖先类TFirstComponentw中方法Flash声明为虚拟的,派生类TSecondComponent重载了方法Flash。声明派生类的Flash 时,后面加了一个Override指令字,表示被声明的方法是重载基类中的同名的虚拟或动态方法。 137 注意:重载的方法必须与祖先类中被继承的方法在参数个数,参数和顺序,数据类型上完全匹配,如果是函数的话,还要求函数的返回类型一致。 138 要重载祖先类中的方法,必须使用Override批示字,如果不加这个指令字,而在派生类中声明了于祖先类同名的方法,则新声明的方法将隐藏被继承的方法。 139 3.动态方法 140 所谓动态方法,非常类似于虚拟方法,当把一个基类中的某个方法声明为动态方法时,派生类可以重载它,如上例的Beep。不同的是,被声明为动态的方法不是放在类的虚拟方法表中,而是由编译器给它一个索引号(一般不直接用到这个索引),当调用动态方法时,由索引号决定调用方法的哪个来具体实现。 141 从功能上讲,虚拟方法和动态方法几乎完全相同,只不过虚拟方法在调用速度上较快,但类型对象占用空间大,而动态方法在调用速度上稍慢而对象占用空间小。如果一个方法经常需要调用,或该方法的执行时间要求短,则在虚拟和动态之间还是选择使用虚拟为好。 142 4.消息句柄方法 143 在方法定义时加上一个message指令字,就可以定义一个消息句柄方法。消息句柄方法主要用于响应并处理某个特定的事件。 144 把一个方法声明为消息句柄的示例如下: 145 type 146 TTextBox = class(TCustomControl) 147 private 148 procedure WMChar(var Message: TWMChar); message WM_CHAR; 149 ... 150 end; 151 上例中声明了一个名叫TTextBox的类类型,其中还声明了一个过程WMPaint,只有一个变量参数Message,过程的首部后用保留字Message表示这是个消息句柄,后跟一个常量WM_PAINT表示消息句柄要响应的事件。 152 Object Pascal规定消息句柄方法必须是一个过程,并且带有一个唯一的变量参数。message保留字后必须跟随一个范围在1到49151的整型常量,以指定消息的ID号。注意,当为一个VCL控制定义一个消息句柄方法时,整型常量必须是Windows的消息ID。(Delphi的Messages单元列出了所有Windows的消息ID。 153 注意:消息句柄不能使用Cdecl调用约定,也不能用Virtual,Dynamic,Override或Abstract等指令字。 154 在消息句柄中,你还可以调用缺省的消息句柄,例如上例中,你声明了一个处理WM_PAINT消息的方法,事实上Delphi提供了处理这个消息的缺省的句柄,不过句柄的名称可能与你声明的方法名称不一样,也就是说你未必知道缺省句柄,那怎么调用呢?没关系,Object Pascal只要你使用一个保留字Inherited就可以了,例如: 155 procedure TTextBox.WMChar(var Message: TWMChar); message WM_CHAR; 156 begin 157 Inherited 158 ... 159 end; 160 上例中,消息句柄首先调用WM_PAINT消息的缺省句柄,然后再执行自己的代码。使用Inherited保留字总是能自动找到对应于指定消息的缺省句柄(如果有的话)。 161 使用Inherited保留字还有个好处,就是如果Delphi没有提供处理该消息的缺省句柄,程序就会自动调用TObject的DefaultHandler方法,这是个能对所有消息进行基本处理的缺省句柄。 162 7.2.4 抽象方法 163 从图7.7的方法指令字语法规则可知,可以在方法的调用约定之后加一个Abstract,以进一步指明该方法是否是抽象的。所谓抽象方法,首先必须是虚拟的或动态的,其次它只有声明而没有定义,只能在派生类中定义它(重载)。因此定义一个抽象方法,只是定义它的接口,而不定义底层的操作。 164 抽象方法在C++中称为纯虚函数,至少含有一个纯虚函数的类称为抽象类,抽象类不能建立对象实例。 165 声明一个抽象方法是用Abstract指令字,例如: 166 type 167 TFigure = class 168 procedure Draw; virtual; abstract; 169 ... 170 end; 171 上例中声明了一个抽象方法,注意,Virtual或Dynamic指令字必须写在Abstract指令字之前。在派生类中重载抽象方法,跟重载普通的虚拟或动态方法相似,不同的是在重载的方法定义中不能使用Inherited保留字,因为基类中抽象方法本来就没有定义。同样的道理,如果抽象方法没有被重载,程序不能调用这个抽象方法,否则会引起运行期异常。 172 7.2.5 重载方法与重定义方法 173 在子类中重载一个滞后联编的对象方法,需要使用保留字override。然而,值得注意的是,只有在祖先类中定义对象方法为虚拟后,才能进行重载。否则,对于静态对象方法,没有办法激活滞后联编,只有改变祖先类的代码。 174 规则非常简单:定义为静态的对象方法会在每个子类中保持静态,除非用一个同名的新虚拟方法隐藏它,被定义为虚拟的方法在每个子类中保持滞后联编。这是无法改变的,因为编译器会为滞后联编方法建立不同的代码。 175 为重新定义静态对象方法,用户只需向子类添加该对象方法,它的参数可以与原来方法的参数相同或不同,而不需要其它特殊的标志。重载虚拟方法,必须指定相同的参数并使用保留字override。例如: 176 type 177 AClass=Class 178 procedure One;virtual; 179 procedure Two;{static method} 180 end; 181 BClass=Clas(Aclass) 182 procedure One;override; 183 procedure Two; 184 end; 185 重载对象方法有两种典型的方法。一种是用新版本替代祖先类的方法,另一种是向现有方法添加代码。这可以通过使用保留字inherited(继承)调用祖先类中相同的方法来实现。例如: 186 procedure Bclass.One; 187 begin //new code 188 ...? 189 //call inherited procedure Bclass 190 inherited One; 191 end; 192 在Delphi,对象可以有多个同名的方法,这些方法被称为重新定义的方法(overload), 并用保留字Overload标识。各同名的方法必须能够根据参数中不同的类型信息予以区分。例如: 193 constructor Create(AOwner: TComponent); overload; override; 194 constructor Create(AOwner: TComponent; Text: string); overload; 195 如果要重新定义一个虚拟的方法,在继承类中必须使用reintroduce指令字。例如: 196 type 197 T1 = class(TObject) 198 procedure Test(I: Integer); overload; virtual; 199 end; 200 T2 = class(T1) 201 procedure Test(S: string); reintroduce; overload; 202 end; 203 ... 204 SomeObject := T2.Create; 205 SomeObject.Test('Hello!'); // calls T2.Test 206 SomeObject.Test(7); // calls T1.Test 207 在同一个类里,不同同时公布(publish)具有同名的重定义方法。例如: 208 type 209 TSomeClass = class 210 published 211 function Func(P: Integer): Integer; 212 function Func(P: Boolean): Integer // error 213 ... 214 7.3 类 的 特 性 215 特性有点类似于字段,因为特性也是类的数据,不过跟字段不同的是,特性还封装了读写特性的方法。特性可能是Delphi程序员接触得最多的名词之一,因为操纵Delphi的构件主要是通过读写和修改构件的特性来实现的,例如要改变窗口的标题则修改Form的Caption特性,要改变窗口文字的字体则修改Form的Font特性。 216 Delphi的特性还有个显著特点就是,特性本身还可以是类类型,例如Font特性就是TFont类型的类。 217 7.3.1 声明特性 218 要声明特性,必须说明三件事情:特性名、特性的数据类型、读写特性值的方法。Object Pascal使用保留字Property声明特性。 219 特性的声明由保留字Property,特性标识符,可选的特性接口(Property Interface)和特性限定符(Property Specifier)构成。 220 特性接口指定特性的数据类型,参数和索引号。一个特性可以是除文件类型外的任何数据类型。 221 在声明特性时,必须指定特性的名字、特性的数据类型以及读写特性的方法。通常是把特性的值放在一个字段中,然后用Read和Write指定的方法去读或写字段的值。程序示例如下: 222 type TYourComponent = class(TComponent) 223 private 224 FCount: Integer; { used for internal storage } 225 procedure SetCount (Value: Integer); { write method } 226 public 227 property Count: Integer read FCount write SetCount; 228 end; 229 上例中声明了一个TYourComponent类型的类,声明了一个字段FCount,它的数据类型是Integer,还声明了方法过程SetCount,最后声明了一个特性Count,它的数据类型跟字段FCount的数据类型相同,并且指定特性的值从字段Fcountt中读取,用方法SetCount修改特性的值。 230 特性的声明似乎比较复杂,但要在程序中要访问特性却是很简单的,例如假设创建了 TYourComponent类型的对象AObject,一个Integer型变量AInteger,程序可以这么写: 231 AInteger:=Aobject.Count; 232 Aobject.Count:=5; 233 实际上,编译器根据声明中的Read子句和Write子句自动把上述语句分别转换成: 234 Ainteger:=Aobject.Fcount; 235 Aobject.SetCount(5); 236 顺便说一下,跟访问字段和方法一样,要访问特性也需要加对象限定符,当然如果使用With语句则可简化。 237 跟字段不同的是,特性不能作为变量参数来传递,也不能用@来引用特性的地址。 238 7.3.2 特性限定符 239 特性限定符可以有四类,分别是Read,Write,Stored和Default。其中Read和Write限定符用于指定访问特性的方法或字段。 240 注意:Read和Write限定符指定的方法或字段只能在类的Private部分声明,也就是说它们是私有的(关于Private的概念将在后面介绍),这样能保证对特性的访问不会干扰到这些方法的实现,也能防止不小心破坏数据结构。熟悉C++的程序员可能已非常理解Private的含义,因为这正是面向对象的精髓之一。 241 1.Read限定符 242 Read限定符用于指定读取特性的方法或字段,通常是一个不带参数的函数,返回的类型就是特性的类型,并且函数名通常以“Get”加特性名组成,例如一个读取Caption特性的方法通常命名为GetCaption。 243 从语法上讲,可以没有Read限定符,这时候我们称特性是“只写”的,不过这种情况较为少见。 244 2.Write限定符 245 Write限定符用于指定修改特性的方法,通常是一个与特性同类型的过程,这个参数用于传递特性新的值,并且过程名通常以“Set”加特性名组成,例如修改Caption特性的方法通常命名为SetCaption。 246 在Write限定符指定的方法的定义中,通常首先是把传递过来的值跟原先的值比较,如果两者不同,就把传递过来的特性值保存在一个字段中,然后再对特性的修改作出相应的反应。这样当下次读取特性值时,读取的总是最新的值。如果两者相同,那就什么也不需要干。 247 从语法上讲,可以没有Write限定符,这时候特性就是“只读”的。只读的特性在Delphi中是常见的,只读的特性不能被修改。 248 3.Stored限定符 249 Stored限定符用于指定一个布尔表达式,通过这个布尔表达式的值来控制特性的存贮行为,注意,这个限定符只适用于非数组的特性(关于数组特性将在后面介绍)。 250 Stored限定符指定的布尔表达式可以是一个布尔常量,或布尔类型的字段,也可以是返回布尔值的函数。当表达式的值为False时,不把特性当前的值存到Form文件中(扩展名为DFM),如果表达式的值为True,就首先把特性的当前值跟Default限定符指定的缺省值(如果有的话)比较,如果相等,就不存贮,如果不等或者没有指定缺省值,就把特性的当前值存到Form文件中。 251 含有Stored限定符的特性声明示例如下: 252 TSampleComponent = class(TComponent) 253 protected 254 function StoreIt: Boolean; 255 public { normally not stored } 256 property Important: Integer stored True;{ always stored } 257 published { normally stored always } 258 property Unimportant: Integer stored False;{ never stored } 259 property Sometimes: Integer stored StoreIt;{ storage depends on function value } 260 end; 261 上例中,TSampleComponent类类型包括三个特性,一个总是Stored,一个总是不Stored,第三个的Stored取决于布尔类型方法StoreIt的值。 262 4.Default和NoDefult限定符 263 Default限定符用于指定特性的缺省值,在Delphi的Object Inspector中,可能已发现所有特性都有一个缺省值,例如把一个TButton元件放到Form上时,它的AllowAllUp特性缺省是False,Down特性的缺省值是False,这些缺省值都是通过Default限定符设定的,程序示例如下 : 264 TStatusBar = class(TPanel) 265 public 266 constructor Create(AOwner: TComponent); override; { override to set new default } 267 published 268 property Align default alBottom; { redeclare with new default value } 269 end; 270 ... 271 constructor TStatusBar.Create(AOwner: TComponent); 272 begin 273 inherited Create(AOwner); { perform inherited initialization } 274 Align := alBottom; { assign new default value for Align } 275 end; 276 上例中,TStatusBar类类型包括Align特性,指定了缺省值为alBottom,TStatusBar类类型在实现部分构造定义中,也设置了缺省值。 277 注意:Default限定符只适用于数据类型为有序类型或集合类型的特性,Default后必须跟一个常量,常量的类型必须与特性的类型一致。 278 如果特性声明时没有Default限定符(也可能是不能有Default限定符),表示特性没有缺省值,相当于用NoDefault限定符(NoDefault限定符只是强调一下特性没有缺省值,其效果跟什么也不写是一样的)。 279 7.3.3 数组特性 280 所谓数组特性,就是说特性是个数组,它是由多个同类型的值组成的,其中每个值都有一个 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论