zoukankan      html  css  js  c++  java
  • Delphi的程序单元、结构、基础知识(转)

       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
      131)程序首部指定程序名、以及参数等。
      142)Uses语句定义程序用到的所有单元。
      15  标识符为单元的名字,各单元之间用逗好(,)隔开,最后结束用分号(;)。
      16   注意:每个程序总是自动包含System单元,Program单元的Uses部分不能显式指定。System单元用于实现一些低级的运行时间程序的支持,如文件输入输出(I/O)、字符串操作、浮点运算、动态内存分配等。另外,Delphi在发行时提供了许多预先定义的单元,在构造程序时可以直接使用。例如,如果你将一个核对框放进一个窗体,你就自动地使用了StdCtrls单元,因为TCheckBox构件在StdCtrls中定义。
      17   Uses部分列出单元的顺序决定它们初始化的顺序,并影响编译器定位标识符的顺序。如果两个单元定义了一个相同名字的类型,编译器将总是使用前面那个单元的类型。
      183)程序块由保留字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   所谓数组特性,就是说特性是个数组,它是由多个同类型的值组成的,其中每个值都有一个索引号,不过跟一般的数组不同的是,一般的数组是自定义类型,可以把数组作为一个整体参与运算如赋值或传递等,而对数组特性来说,一次只能访问其中的一个元素。声明一个数组特性的程序示例如下:
     281 type
     282 TDemoComponent = class(TComponent) 
     283 private
     284   function GetNumberName(Index: Integer): string; 
     285 public
     286   property NumberName[Index: Integer]: string read GetNumberName; 
     287 end;
     288 ...
     289 function TDemoComponent.GetNumberName(Index: Integer): string;
     290 begin
     291   Result := 'Unknown'; 
     292   case Index of
     293   -MaxInt..-1: Result := 'Negative'; 
     294   0 : Result := 'Zero';
     295   1..100 : Result := 'Small';
     296   101..MaxInt: Result := 'Large';
     297   end;
     298 end;
     299   上例中,声明了一个数组特性NumberName,它的元素类型是String,索引变量是Index,索引变量的类型是Integer。上例中还同时声明了Read子句。从上面的例子中可以看出,声明一个数组特性的索引变量,跟声明一个过程或函数的参数类似,不同的是数组特性用方括号,而过程或函数用圆括号。索引变量可以有多个。
     300   对于数组特性来说,可以使用Read和Write限定符,但Read和Write限定符只能指定方法而不能是字段,并且Object Pascal规定,Read限定符指定的方法必须是一个函数,函数的参数必须在数量和类型上与索引变量一一对应,其返回类型与数组特性的元素类型一致。Write限定符指定的方法必须是一个过程,其参数是索引变量再加上一个常量或数值参数,该参数的类型与数组特性的元素类型一致。
     301   访问数组特性中的元素跟访问一般数组中的元素一样,也是用特性名加索引号。
     302 7.3.4 特性重载
     303   所谓特性重载,就是在祖先类中声明的特性,可以在派生类中重新声明,包括改变特性的可见性(关于类成员的可见性将在后面详细介绍),重新指定访问方法和存贮限定符以及缺省限定符等。
     304   最简单的重载,就是在派生类中这么写:
     305   Property 特性名:
     306   这种重载通常用于只改变特性的可见性,其它什么也不改变,例如特性在祖先类中是在Protected部分声明,现在把它移到Published部分声明。
     307   特性重载的原则是,派生类中只能改变或增加限定符,但不能删除限定符,请看下面的程序示例:
     308 type
     309   TBase = class
     310  ...
     311 protected
     312   property Size: Integer read FSize; 
     313   property Text: string read GetText write SetText; 
     314   property Color: TColor read FColor write SetColor stored False;
     315   ...
     316   end;
     317 type TDerived = class(TBase)
     318 ...
     319   protected
     320    property Size write SetSize; published property Text;
     321    property Color stored True default clBlue;
     322    ...
     323   end;
     324   对于祖先类中的Size特性,增加了Write限定符,对于祖先类中的Text特性,改在Published部分声明,对于祖先类中的Color特性,首先是改在Published部分声明,其次是改变了Stored限定符中的表达式,从False改为True,并且增加了一个Default限定符。
     325 7.4 类成员的可见性
     326   面向对象编程的重要特征之一就是类成员可以具有不同的可见性,在Object Pascal中,是通过这么几个保留字来设置成员的可见性的:Published,Public,Protected,Private,Automated。如
     327 TBASE = class
     328 private
     329   FMinValue: Longint; 
     330   FMaxValue: Longint; 
     331   procedure SetMinValue(Value: Longint);
     332   procedure SetMaxValue(Value: Longint); 
     333   function GetPercentDone: Longint; 
     334 protected
     335   procedure Paint; override; 
     336 public
     337   constructor Create(AOwner: TComponent); override; 
     338   procedure AddProgress(Value: Longint); 
     339   property PercentDone: Longint read GetPercentDone; 
     340 published
     341   property MinValue: Longint read FMinValue write SetMinValue default 0; 
     342   property MaxValue: Longint read FMaxValue write SetMaxValue default 100;   property Progress: Longint read FCurValue write SetProgress
     343 end;
     344   上例中,FMinValue、FMaxValue、FCurValue等字段是在Private部分声明的,表示它们是私有的,Public部分声明的几个方法是公共的。
     345   再请看下面的例子: 
     346 TBASE = class
     347   FMinValue: Longint;
     348   FMaxValue: Longint;�
     349 private
     350   procedure SetMinValue(Value: Longint);
     351   procedure SetMaxValue(Value: Longint);
     352  function GetPercentDone: Longint; 
     353 protected
     354   procedure Paint; override; 
     355 public
     356   constructor Create(AOwner: TComponent); override; 
     357   procedure AddProgress(Value: Longint); 
     358   property PercentDone: Longint read GetPercentDone; 
     359 published
     360   property MinValue: Longint read FMinValue write SetMinValue default 0; 
     361   property MaxValue: Longint read FMaxValue write SetMaxValue default 100; 
     362   property Progress: Longint read FCurValue write SetProgress;
     363 end;
     364  上例中,FminValue,FmaxValue,FCurValue这三个字段紧接着类类型首部,前面没有任何描述可见性的保留字,那么它们属于哪一类的可见性呢? ObjectPascal规定,当类是在{$M+}状态编译或者继承的是用{$M+}状态编译的基类,其可见性为为Published,否则就是Public。
     365 7.4.1 Private
     366   在Private部分声明的成员是私有的,它们只能被同一个类中的方法访问,相当于C语言中的内部变量,对于其它类包括它的派生类,Private部分声明的成员是不可见的,这就是面向对象编程中的数据保护机制,程序员不必知道类实现的细节,只需要关心类的接口部分。
     367 7.4.2 Public
     368   在Public声明的成员是公共的,也就是说,它们虽然在某个类中声明的。但其它类的实例也可以引用,相当于C语言中的外部变量,例如,假设应用程序由两个Form构成,相应的单元是Unit1和Unit2,如果希望Unit2能共享Unit1中的整型变量Count,则可以把Count放在TForm1类中的Public部分声明,然后把Unit1加到Init2的Interface部分就可以了。
     369   注意:面向对象的编程思想其特征之一就是隐藏复杂性,除非必须把某个成员在不同类之间共享,一般来说尽量不要把成员声明在类的Public部分,以防止程序意外地不正确地修改了数据。
     370 7.4.3 Published
     371   在Published部分声明的成员,其可见性与在Public部分声明的成员可见性是一样的,它们都是公共的,即这些成员可以被其它类的实例引用,Published和Public的区别在于成员的运行期类型信息不同。一个Published元素或对象方法不但能在运行时,而且能在设计时使用。事实上,Delphi构件板上的每个构件都有Published接口,该接口被一些Delphi工具使用,例如Object Inspector。
     372   注意:只有当编译开关$N的状态为$M+时或者基类是用$M+编译时,类的声明中才能有Published部分,换句话说,编译开关$M用于控制运行期类型信息的生成。
     373 7.4.4 Protected
     374   Protected与Private有些类似。在Protected部分声明的成员是私有的(受保护的),不同的是在Protected部分声明的成员在它的派生类中可见的,并且成为派生类中的私有成员。
     375   在Protected部分声明的成员通常是方法,这样既可以在派生类中访问这些方法,又不必知道方法实现的细节。
     376 7.4.5 Automated
     377   C++的程序员可能对这个保留字比较陌生,在Automated部分声明的成员类似于在Public部分声明的成员,它们都是公共的,唯一的区别在于在Automated部分声明的方法和特性将生成OLE自动化操作的类型信息。
     378   注意:Automated只适用于基类是TAuto0bject的类声明中,在Automated部分声明的方法,其参数和返回类型(如果是函数的话)必须是可自动操作的。在Automated部分声明的特性其类型包括数组特性的参数类型也必须是可自动操作的,否则将导致错误。可自动操作的类型包括:Byte、Currency、Double、Integer、Single 、SmallInt、String、TDateTime、Variant、WordBool等。
     379   在Automated部分声明的方法只能采用Register调用约定,方法可以是虚拟的但不能是动态的。在Automated部分声明的特性只能带Read和Write限定符,不能有其它限定符如Index、Stored、Default、NoDefault等,Read和Write指定的只能是方法而不能是字段,方法也只能采用Register调用约定,也不允许对特性重载。
     380   在Automated部分声明的方法或特性分配一个识别号(ID),如果不带DispId限定符,编译器自动给方法或特性分配一个相异的Id,如果带DispId限定符,注意Id不能重复。
     381 7.5 类类型的兼容性
     382   一个类类型类与它的任何祖先类型兼容。因此,在程序执行时,一个类类型变量既可以引用那个类型本身的实例,也可以引用任何继承类的实例。例如下面的一段代码:
     383 type
     384 TScreenThing = Class
     385   X,Y:Longint; 
     386   procedure Draw;
     387 end;
     388 T3DScreenThing = Class(TScreenThing) 
     389   Z:Longint;
     390 end;
     391 procedure ResetScreenThing(T:TScreenThing);
     392 begin
     393   T. X:=0;
     394   U. Y:=0;
     395   V. Draw;
     396 end;
     397 procedure Reset3DScreenThing(T:T3DScreenThing);
     398 begin
     399   T. X:=0; 
     400   T.Y:=0;
     401   T.Z:=0;
     402   U.Draw;
     403 end;
     404 var
     405   Q:TScreenThing; R:T3DScreenThin;
     406 begin
     407   {...}
     408   ResetScreenThing(Q); 
     409   ResetScreenThing(R); {this work}
     410   Reset3DScreenThing(Q); { but this does not}
     411   在上面,过程ResetScreenThing定义时使用TScreenThing类型的参数,但可以使用TScreenThing类型和T3DScreenThing类型参数,因为T3DScreenThing类型是TScreenThing类型的继承类。而Reset3DScreenThing使用TScreenThing类型的参数就非法。
     412 7.6 VCL类结构
     413   我们介绍过的Delphi的VCL构件都是使用类类型定义的对象。在Delphi中,所有的类都是从一个共同的类TObject继承下来的,TObject类的声明在System单元中,它定义了一些操纵类的最基本的方法,是Delphi所有类的缺省祖先类。使用View|Browse命令,可以打开Browse Object命令,查看Delphi各对象之间的继承关系。
     414  TObject类是一切构件类和对象的基类,位于继承关系的最顶层。TPersistent类是TObject类的下一级继承者,它是一个抽象类,主要为它的继承者提供对流的读写能力。
     415   TComponent类是TPersistent类的下一级继承者,它是VCL中所有构件的祖先类。TComponent类定义了构件最基本的特性、方法和事件。尽管TComponent类是VCL中所有构件的基类,但直接继承下来的却只有几个非可视的构件,如TTime构件和TDataSource构件等,绝大多数构件是从TComponent类的下级TControl类继承下来的,从TControl类继承下来的都是可视化的构件,这些构件也称为控制。TControl类定义了VCL中所有可视化构件基本的特性、方法和事件等。
     416   TWinControl和TGraphicControl类都是TControl类的子类。TWinControl的子类主要是用于窗口控制(如按钮、对话框、列表框、组合框等控制),它们实际上也是窗口,有自己的句柄,占用Windows资源,并且可以与用户交互。而TGraphicControl的子类没有窗口句柄,也不占用Windows资源类,也能接受键盘的输入,它们的主要优点在于节约资源,如TLabel和TSpeedButton等构件。 
     417   实际上,整数类型可以分为基本整数类型(Fundamental type)和一般整数类型(generic type)。一般整数类型(generic type)包括Integer和Cardinal两种。在实际编程时,请尽量区分这两种,因为底层CPU和操作系统对结果进行了优化。
     418   表6-2列出了Integer和Cardinal的取值范围及存储格式。
     419   表6-2 一般整数类型的取值范围及存储格式
     420 数据类型  取值范围        存储格式
     421 Integer  ?147483648..2147483647  32位有符号
     422 Cardina  l0..4294967295      32位无符号
     423   基本整数类型包括Shortint、Smallint、Longint、Int64、Byte、Word和Longword。表6-3列出了它们的取值范围及存储格式。
     424   表6-3 基本整数类型的取值范围及存储格式
     425 数据类型  取值范围     存储格式
     426 Shortint  -128..127    signed 8-bit
     427 Smallint  -12768..32767  signed 16-bit
     428 Longint   -2147483648..2147483647 signed 32-bit
     429 Int64    -2^63..2^63?   signed 64-bit
     430 Byte    0..255      unsigned 8-bit
     431 Word    0..65535     unsigned 16-bit
     432 Longword  0..4294967295  unsigned 32-bit
     433   一般整数类型的实际范围和存储格式随着Object Pascal的不同实现而不同,但通常根据当前CPU和操作系统来采取最佳的操作方式。
     434   一般整数类型是最常用的整数类型,可以充分利用CPU和操作系统的特性,因此程序中应当尽量使用一般整数类型。基本整数类型独立于操作系统和CPU,只有当应用程序的数据范围或存储格式特别需要时,才使用基本整数类型。
     435   通常情况下,整数的算术运算结果为Integer类型,等价于32位的Longint类型。只有当操作数存在 Int64类型时,才返回Int64类型的值。因此,下面的代码将产生错误的结果:
     436 var I: Integer; J: Int64;
     437  ...
     438 I := High(Integer);
     439 J := I + 1;
     440   在这种情况下,要取得一个Int64的值,必须进行类型转换:
     441 J := Int64(I) + 1;
     442   注意:绝大多数例程在遇到Int64时都把它转换为32位。但例程High,Low,Succ,Pred,Inc,Dec,IntToStr和IntToHex则完全支持Int64参数。Round,Trunc,StrToInt64,和StrToInt64Def函数可以返回Int64类型的结果。
     4432)字符类型(Char)
     444   字符类型中Char类型设计来只存储一个字符。一个字符占一个字节,因此Char数据类型可以存储256个不同的字符,其对应的整数为0到255。
     445   除了Char数据类型外,Delphi还提供了Char类型的扩展,即AnsiChar和WideChar型。表6-4是字符数据类型的列表。
     446   表6-4 字符整数类型
     447 字符类型  占用字节数  存储内容
     448 AnsiChar   1      存储一个Ansi字符。
     449 WideChar   2      存储一个UniCode字符。
     450 Char     1      目前,对应AnsiChar。但Delphi将来的版本可能对应于WideChar。
     451   Ansi字符集是扩展的ASCII字符集,仍然占一个字节。目前,Char对应AnsiChar,但Borland公司在将来的Delphi版本中可能使Char对应WideChar。  
     452  WideChar用来支持泛字符集(Unicode)。Unicode字符占用两个字节,可以有65536种不同的取值,可以表达现代计算机中使用的世界上所有的字符,包括图形符号和用于出版业的特殊符号等。
     453   UniCode字符集的前256个字符对应着ANSI字符。如果你把一个AnsiChar字符放到WideChar字符类型的变量中,WideChar字符类型变量的高字节将全部置为0,AnsiChar字符存放到WideChar字符类型的变量的低字节中。
     454  注意:Windows NT全面支持Unicode字符号集,但Windows 95却不同。如果你希望书写的程序同时能在两种系统上运行,必须使用SizeOf()函数,以确定字符占多少字节。
     4553)布尔类型(Boolean)
     456   Boolean数据类型的变量只存储一个逻辑值,例如True或False。共有4种Boolean数据类型,见表6-5 457 表6-5 布尔类型 
     458 类型  说明 
     459 Boolean 占1个字节 
     460 ByteBool 占1个字节 
     461 WordBool 占2个字节 
     462 LongBool 占4个字节
     463   Delphi提供多种Boolean数据类型的目的是为了兼容,因为在某些情况下,Windows需要用一个字(2个字节)或双字(4个字节)来表示一个布尔值。
     4644)枚举型
     465   所谓枚举类型,就是用一组数量有限的标识符来表示一组连续的整数常数,在类型定义时就列出该类型可能具有的值。枚举类型是一种用户自定义的简单数据类型。在类型定义时就列出该类型可能具有的值。下面是枚举类型定义的一些例子:type
     466   TDays=(Monday,YuesDay,Wednesday,Thursday,Friday,Saturday,Sunday); 
     467 TPrimaryColor=(Red,Yelloow,Blue); TDepartment=(Finance,Personnel,Engineering,Marketing,MIS); TDog=(Poodle,GoldenRetriever,Dachshund,NorwegianElkhound,Beagle);
     468   枚举类型定义中的每个值都对应一个整数,整数值由该值在类型定义表中的位置决定,通常类型定义的第一个数对应的整数值为0。例如,在TDay类型定义中Monday对应值为0、Tuesday值为1,等等。如果你把DayOfWeek定义为Integer,通过赋整数值来代表星期几,也可以得到同样的结果。但是,由于枚举类型表达的意思明确、直观、便于记忆,因此使用枚举类型仍有必要。
     469   下面是声明一个枚举类型的语法(图6.2)。
     470   其中标识符列表中的标识符之间用逗号隔开,它列出该类型可能具有的值。
     471 下面是声明一个枚举类型变量的举例:
     472   var DayOfWeek:TDays; 
     473   Hue:TPrimaryColor; 
     474   Department:TDepartment; 
     475   Dogbreed:TDog;
     476   也可以把类型声明和变量声明合二为一,例如:
     477 var DayOfWeek:(Monday,YuesDay,Wednesday,Thursday,Friday,Saturday,Sunday);
     478   在声明枚举类型和枚举变量时,请注意以下几点:
     479 1)枚举的元素只能是标识符,标识符的命名必须符合 Pascal关于标识符的规定,例如下面的声明就是错误的:
     480 type TDays=(0,1,2,3,4,5,6,7);
     481 2)同一个枚举元素不能出现在多个枚举类型中。例如下面的声明就是错误的: 
     482 type TColors1=(Red,Blue,Green,White,Black); 
     483    TColors2=(Yellow,Lime,Silver,Green);
     484  两个类型变量中都有Green元素,是不允许的。
     485 3)不能直接用枚举类型中的元素参加运算,例如,下面的语句就是错误的:
     486 X:=Red+Blue;
     487   但是,可以用某个枚举类型中的元素对枚举变量赋值,例如,下面的语句: 
     488 DayOfWeek:=Monday;
     4895)子界型
     490   子界类型是Integer,Boolean,Char及枚举型等称为宿主类型数据的一个子集。当你要限制一个变量的数据范围时,使用子界类型就特别有用。子界类型也是一种用户自定义的简单数据类型。要定义子界类型,必须说明区间的最大值和最小值,下面是子界类型定义的一些例子:
     491 type TCompassRange = 0..360; 
     492 TValidLetter ='A'..'F'
     493 TMonthlyIncome=10000..30000;
     494 THours =0..23; TPrecipitation=(Drizzle,Showers,Rain,Downpour,Thunderstorm); {枚举型}
     495 TRain =Drizzle..Downpour; {上面枚举型的子界型}
     496   下面是声明一个子界类型的语法规则(图6.3)。
     497   其中两个常数(称为上界和下界)必须是同一种有序类型,如Integer,Boolean,Char及枚举型等,但不能是Real数据类型。第一个常数必须小于或等于第二个常数。
     498   下面是声明子界类型变量的举例:
     499 var Compass:TCompassRange;
     500 ValidChar:TValidLetter;
     501   在声明子界类型和子界类型变量时,请注意以下几点:
     502 1)上界常数和下界常数必须是同一类型,且都是有序类型。
     503 2)子界类型变量具有宿主类型数据的所有运算特性,但运算的结果必须在范围内。
     504 3)上界常数和下界常数可以是表达式。例如: 
     505 const X = 10; Y = 50; 
     506 type Color = (Red, Green, Blue); 
     507 Scale = X * 2..X * 20;
     508 2.实数类型(Real)
     509   实数类型是带有小数部分的数值,存储实数。有6种不同的Real数据类型,它们在范围、精确度、大小等方面都不相同。见表6-6 510   实数类型
     511 数据类型 取值范围           有效位 存储字节
     512 Real48  2.9 x 10^-39 .. 1.7 x 10^38  11..12 6
     513 Single  1.5 x 10^-35 .. 3.4 x 10^38  7..8  4
     514 Double  5.0 x 10^-324 .. 1.7 x 10^308 15..16 8
     515 Extended 3.6 x 10^-4951 .. 1.1 x 10^4932 19..20 10
     516 Comp   -2^63+1 .. 2^63 ?        19..20 8
     517 Currency 22337203685477.5808..922337203685477.5807 19..20 8
     518 当前通常使用的Real等价与Double。
     519 6.3.2 字符串类型
     520   Delphi在处理字符串时,提供了多种方式,表6-7是Delphi使用的字符串类型。
     521 表6-7 字符串类型
     522 类型    最大长度  存储的字符  是否以Null结尾
     523 ShortString 255个字符 AnsiChar   否
     524 AnsiString~ 2^31个字符 AnsiChar   是
     525 String   或者255或者~2^31个字符 ANSIChar都可能
     526 WideString ~2^30个字符 WideChar   是
     527   从上表可知,Delphi主要支持两种类型的字符串: ShortString和AnsiString。WideString类似于AnsiString,只是其存储的字符为WideChar。
     528   ShortString数据类型定义的是长度在1到255之间动态长度字符串。像数组一样,单个字符可以通过引用它们的索引进行存取。位于0的字节存储了代表字符串当前所赋值长度的数值(只能通过关闭范围检查才能访问)。ShortString数据类型主要是为了能和Delphi 1.0和Borland Pascal的早期版本兼容。
     529   AnsiString(又称为long String或huge String)数据类型的定义是动态分配的,长度几乎可以无限的(仅受可用内存限制)以NULL结尾的字符串。AnsiString中的字符由AnsiChar数据类型的字符组成。
     530   建议最好使用AnsiString数据类型。这是因为AnsiString数据类型的变量是动态分配的,当把一个更长的字符串放入AnsiString数据类型的变量时,Delphi会从新为该变量申请空间。如果要显式地改变字符串的长度,可以使用SetLength() 函数来分配一块恰当的内存;使用AnsiString数据类型的另外一个优点是,AnsiString字符串是以NULL结尾,即在最后一个字符之后自动加上一个NULL字符表示字符串结束,与操作系统的大多数函数例程兼容,例如Win32 API,从而在调用操作系统函数例程时更加方便,不需要使用StrPCopy()来将以Pascal风格的字符串转换为以NULL结尾的字符串。Delphi VCL构件的所有特性、事件使用AnsiString来传递参数,以简化、统一VCL和API之间的接口。
     531   String既可以是SHortString类型也可以是AnsiString类型,缺省是AnsiString类型。例如,如果你像下面那样定义字符串:
     532 var S: String;// S is an AnsiString
     533   则编译器假定你要创建一个AnsiString数据类型变量。
     534   使用$H编译命令可以改变缺省定义。当在程序中把编译开关$H的状态改为{H-}时,String缺省是ShortString类型;当在程序中把编译开关$H的状态改为{H+}时,String缺省是AnsiString类型。例如:
     535 var {$H-} S1: String; // S1 is a ShortString
     536 {$H+} 
     537 S2: String; // S2 is an AnsiString
     538   如果定义中指明了长度(最大为25 5),则String为ShortString。例如:
     539 var S: String[63]; // S是一个 长度为63的ShortString变量。
     540 6.3.3 结构数据类型
     541   结构类型在内存中存储一组相关的数据项,而不是像简单数据类型那样单一的数值。Object Pascal结构类型包括集合类型、数组类型、记录类型、文件类型、类类型、类引用类型、接口类型等。这里,我们只介绍集合类型、数组类型、记录类型和文件类型。类类型、类引用类型和接口类型放在下一章介绍。
     542 1.数组(Array)
     543   数组是一种数据类型数据的有序集合,是代表一定数量具有相同类型变量的一种数据类型。Object Pascal数组可与任何简单数据类型或字符串类型等一起使用。数组可用于声明一个简单变量或作为一个记录类型定义的组成部分。
     544   (1)数组的定义
     545   下面是声明一个数组类型的语法规则(图6.4)。
     546  要声明一个数组变量,要求你提供一个标识符,使用array保留词,在方括号中指定数组的界限,并指定编译器数组将用于存储什么类型,例如:
     547 Var Check:array[1..100] of Integer;
     548  范围标点‘..’用于表示Check是一个有100个整数的数组,这些整数从1到100编号。范围编号是一个子界类型,可以是0,也可以是正数或负数,或者字符,或其它有序类型。
     549   下面是声明数组类型及数组类型变量的举例:
     550 Type TCheck = array [1..100] of Integer;
     551 Var CheckingAccount:TCheck;
     552   上面是先定义数组类型,然后定义数组变量。其实上,也可以同时定义类型、变量,例如:
     553 var Kelvin:array[0..1000] of Temperatures; 
     554 TwentiethCentury: array[1901..2000] of Events; 
     555 LessThanzeroo: array[-999..-400] of Shortint; 
     556 DigiTValues:array ['0'..'9' of Byte; 
     557 SecretCode:array[''A'..'Z' of char;
     558  访问数组中的元素很简单,只要在标识符后面的方括号中给出指定的元素的索引号即可。例如:
     559 Check[5]:=12;
     560 J:=9;
     561 Check[J]:=24;
     562   要访问数组中的所有元素,可以使用循环语句。例如 :
     563 For J:=1 to 10 do Check[J]:=0;
     5642)多维数组
     565   上面介绍的是一维数组。实际上,数组可以是多维的。例如,如果你想编写一个数组来容纳一张电子表格中的值,那么就可以使用2维数组。下面的例子说明如何使用2维数组定义一个有20行和20列的表格:
     566 Type Ttable = array[1..20,1..20] of Double;
     567 Var BigTable:Ttable;
     568   要将2维数组中的所有数据初始化,可以使用如下语句:
     569 Var Col,Row:Intger;
     570 .
     571 .
     572 .
     573 for Col:=1 to 20 do
     574   for Row:=1 to 20 do
     575    BigTable[Col,Row]:=0.0;
     576   使用多维数组时,要记住的一件事是数组为每维所占据的RAM数都呈幂级数增加。例如:
     577 Aline:Array[1..10] of byte;占用10个字节
     578 AnArea:Array[1..10,1..10] of byte;占用10*10=100个字节
     579 Avloume:Array[1..10,1..10,1..10] of byte;占用10*10*10=1000个字节
     5803)字符数组
     581   前面介绍的字符串,实际上就是一个1维字符数组,只不过Pascal对字符串类型作了特殊的准许,你可以把它看作一个整体。字符串类型本质上等同于下列类型:type StringType:array[0..255] of char;但是,虽然你可以把一个字符串看待,但它仍然保持其数组的特性。例如在定义一个字符串类型变量时,你可以说明字符串的大小,就像你定义字符数组的大小一样。下面是几个字符串类型定义:
     582 type MyString:string[15]; 
     583 BigString:string;
     584 LittleString:string[1];
     585  上面语句定义MyString类型包含15个字符,LittleString包含1个字符,BigString没有说明大小,就取字符串包含字符的最大个数255。然后你可以定义这些类型的变量,就像使用其它类型一样:
     586 Var MyName:MyString; 
     587 Letter,Digit:LittleString;
     588   你可以对字符串变量进行赋值: MyName:='Frank P.BorLand';
     589  ?因为MyName长度为15,因此只能容纳15个字符。如果执行下面语句: MyName:=Frank P.Borland?则MyName变量中只存有FranK.P.Borlan其余部分被舍弃。
     590  为了取得字符串中的一个字符,可以按如下方法进行:AChar:=MyName[2];
     591  但是,如果索引大于字符串变量的长度,则结果不可知。例如: AChar:=MyName[16];则AChar将被设置为某个不确定的字符,换句话说,就是废字符。
     592   在字符串类型的数组中,字符串的第一个位置[0]包含有字符串的长度,因此数组的实际长度比该字符串长度大1个字节。你可以使用Length函数或下面的代码来得到一个字符串的长度:L:=Ord(String[0]); 
     5934)数组类型常量
     594   数组类型常量的每个字段都是类型常量,下面是声明数组类型常量的语法规则(图6.5)。
     595   从图中可以知道,一个数组类型常量由括号括起来的类型常量组成,不同类型常量用逗号隔开。
     596   像简单类型常量一样,数组类型常量用来定义一个数组常量,下面是一个例子。
     597 type TStatus = (Active, Passive, Waiting); 
     598 TStatusMap = array[TStatus] of string;
     599 const StatStr: TStatusMap = ('Active', 'Passive', 'Waiting');
     600   上面的例子首先定义一个数组TStatusMAp,然后定义一个数组常量StatStr。该数组常量的目的是把TStatus类型的值转化为对应的字符串。下面是数组常量StatStr元素的值:
     601 StatStr[Active] = 'Active'StatStr[Passive] = 'Passive'
     602 StatStr[Waiting] = 'Waiting'
     603   数组常量的元素类型可以是除文件类型以外的任何类型。字符数组类型常量既可以是字符也可以是字符串,例如:
     604  const Digits: array[0..9] of Char = ('0', '1', '2', '3', '4', '5','6', '7', '8', '9');
     605  该数组常量也可以表示为:const Digits: array[0..9] of Char = '0123456789';
     606   初始化字符数组类型常量的字符串长度可以小于数组类型的定义长度,例如:var FileName: array[0..79] of Char = 'TEST.PAS';这时数组余下的字符空间自定置NULL(#0),因此数组也变成了一个以NULL结尾的字符串。
     607   多维数组类型常量的定义采用括号的形式,每一维用括号括起,不同维及不同元素常量之间用逗号隔开。最里面的常量对应最右面的维数。
     608   例如:type TCube = array[0..1, 0..1, 0..1] of Integer;const Maze: TCube = (((0, 1), (2, 3)), ((4, 5), (6, 7)));
     609  Maze常量数组各元素的值为:
     610 Maze[0, 0, 0] = 0
     611 Maze[0, 0, 1] = 1
     612 Maze[0, 1, 0] = 2
     613 Maze[0, 1, 1] = 3
     614 Maze[1, 0, 0] = 4
     615 Maze[1, 0, 1] = 5
     616 Maze[1, 1, 0] = 6
     617 Maze[1, 1, 1] = 7
     6185)开放式数组
     619   所谓开放式数组,是指数组作为形参传递给过程或函数时其长度是可变的,这样在调用过程或函数时,可以传递不同长度的数组作为实际参数。
     620   开放式数组在过程或函数中作为形参可以定义为: array of T这里T是数组的元素类型标识符,实际参数必须是T类型的变量或元素类型为T的数组变量。在过程或函数内形参的作用可看作为下面的数组: array[0..N - 1] of T
     621   这里N是实参中元素的个数。实际上实参的上下界被映射到0到 N-1。如果实参是类型T的简单变量,则它被看成为只有类型T元素的数组。
     622    开放数组只能以开放数组参数或一个未定义变量参数的的形式传递到过程或函数。开放数组可以作为数值参数、常数参数或变量参数,并与这些参数具有同样的语法规则。作为形式参数的开放数组不允许整体赋值,只能访问它的元素。并且对元素的赋值不影响实参。
     623  当开放式数组作为数值参数时,编译器将在内存中开辟一块区域存放实参的拷贝,等过程或函数退出后再释放这块区域,这样当实参是个很大的数组时,可能会发生栈溢出的问题。在使用开放数组参数时,可以使用Low函数获得当前最小下标(不过总是为0),使用High函数获得当前最大下标,使用SizeOF函数获得当前数组大小。下面是一个例子,演示了开放式数组的使用。
     624 {定义两个长度不同的数组变量}
     625 Var X1:array[1..10] of Double;
     626 X2:array[1..30] of Double;
     627   {Clear过程对一个Double数组的各元素清0,SUM过程计算一个Double数组的各元素之和。两个过程的参数都是开放式数组。}
     628 procedure Clear(var A: array of Double);
     629   var I: Integer;
     630 begin
     631   for I := 0 to High(A) do
     632    A[I] := 0;
     633 end;
     634 function Sum(const A: array of Double): Double;
     635   var I: Integer; S: Double;
     636 begin
     637  S := 0;
     638  for I := 0 to High(A) do S := S + A[I];
     639  Sum := S;
     640 end;
     641 begin
     642  Clear(X1);
     643  Clear(X2);
     644  Sum(X1);
     645  Sum(X2); 
     646 end;
     647   当开放式数组的元素类型为Char时,实参可以是一个字符串常数。例如:
     648 procedure PrintStr(const S: array of Char);
     649   var I: Integer;
     650 begin
     651   for I := 0 to High(S) do
     652    if S[I] <> #0 then Write(S[I]) 
     653    else Break;
     654 end;
     655   下面是合法的过程调用语句:
     656   PrintStr('Hello world');
     657   PrintStr('A');
     6586)动态数组
     659   在Delphi中,除了定义静态数组外,还可以定义动态数组。动态数组只需说明数组的类型信息(包括数组的维数和数组元数的类型),但不需要定义元素的个数。例如:
     660 A: array[1..100] of string;//静态数组
     661 B: array of integer//动态数组 
     662 C: array of array of string;//动态数组
     663   这里A是静态数组,B是一维的整数动态数组,C是二维的字符串动态数组。
     664   动态数组没有固定的长度。相反,当为动态数组赋值或使用SetLength过程时,动态数组的内存空间将重新分配。动态数组的定义形式是:
     665   array of baseType
     666   例如: var MyFlexibleArray: array of Real;
     667   定义了一个类型为实数型的一维动态数组。注意,声明语句并没有为MyFlexibleArray分配内存。要为动态数组分配内存,需要调用SetLength过程。例如:
     668   SetLength(MyFlexibleArray, 20);上面语句分配20个实数,标号从0到19。
     669   动态数组的标号是整数类型,标号总是从0开始。使用Length,High和Low函数可以取得有关动态数组的特性。Length函数返回数组中元素的个数。High函数返回数组的最大标号,Low返回0。
     670 2.集合类型
     671   集合类型是Integer,Boolean,Char,枚举型,子界型等类型数据的一个子集。在应用程序中,当要检测一个数是否属于一个特定的集合时,就可以使用集合类型。(1)集合类型的定义下面是声明一个集合类型的语法规则(图6.6)。
     672  其中Set of是保留字,ordinal Type是集合的基类型,可以是任何有序类型如整数型,布尔型,字符型,枚举型和子界型,但不能是实型或其它自定义类型。下面是一些集合类型的例子:
     673 type VoterDataSet= Set Of (Democrat,Republican,Male,Female, LowOpinion,HighOption,Confused);
     674 Chars = Set of Char;
     675 Letters = Set of 'a'..'z';
     676 VIBGYOR= (Violet,Indigo,Blue,Green,Yellow,Orange,Red); {这是枚举型}
     677 ColorSet = set of VOBGYOR;{上面枚举型的集合类型}
     678   一个集合类型的变量的值实际上是它的基类型的一个子集,可以为空集。一个集合最多可有256个元素。因此下面的集合定义是错误的:
     679  type SET1= Set Of Integer;
     680   这是因为Integer集合的元素个数远远大于256。 
     681  下面是集合类型变量的一些例子:
     682 var Voter: VoterDataSet; 
     683 Color: ColorSet;
     684 Lets:Letters;
     685   Pascal使用方括号来表示集合,例如:
     686   [Democrat];表示只含Democrat的集合。
     687   一个集合可以拥有0个元素,这时称之为空集,用两个方括号表示,其中什么也没有。对于集合类型变量,你可以进行+,-,=,*(并),IN等运算。见下表6-8 688 表6-8 集合类型运算
     689 操作符 描述            举例
     690 +   往一个集合中添加元素    Aset:=Aset+AnotherSet;
     691 -   从一个集合中去除元素    Aset:=Aset-AnotherSet;
     692 *   去除两个集合中都没有的元素 Aset:=Aset*AnotherSet;
     693 In   测试元素          Bool:=AnElement in Aset
     694  下面是集合运算的例子:
     695 Voter:=[HighOption];
     696 Voter:=Voter+[Democrat]; 
     697 Voter:=Voter+{male};
     698 Voter:=Voter-[HighOption]; 
     699  If HighOption in Voter then SendtheVoterFlowers;
     7002)集合类型
     701   常量像简单类型常量一样,集合类型常量用来定义一组常量的集合。例如:
     702 type TDigits = set of 0..9;
     703 TLetters = set of 'A'..'Z';
     704 const EvenDigits: TDigits = [0, 2, 4, 6, 8];
     705 Vowels: TLetters = ['A', 'E', 'I', 'O', 'U', 'Y'];
     706   上面的例子首先定义两个集合类型TDigits和Tletters,然后定义了两个集合常量,其中EvenDigits的值域是[0, 2, 4, 6, 8],它为TDigits的一个子集;Vowels的值域是 ['A', 'E', 'I', 'O', 'U', 'Y'],它为TLetters的一个子集。
     707 3.记录类型
     708   记录是一系列相关的变量,这些变量被称为域,它们放在一起,作为一个整体使用。例如,一个雇员可能包含姓名、雇用时间、薪金等数据,这时你可以像下面那样定义一个雇员记录类型:
     709 type TEmployee = record LastName: String[20];
     710 FirstName:String[15];
     711 YearHired:1990..2000; 
     712 Salary:Double;
     713 Position:string[20]; 
     714 end;
     715   Pascal的记录类型跟数据库中的记录很相似,记录类型中的元素可以理解为数据库中的字段,事实上Pascal正是借用了数据库中的记录和字段的概念。
     7161)记录类型的定义
     717   下面是声明记录类型的语法规则 
     718  记录可以一个字段也没有,即为空记录;一个记录可以有一个固定部分(fixed part),在固定部分,每个字段都有其确定的标识符和数据类型,它们在内存中分别占用不同的区域;一个记录也可以加入一个可变部分(variantpart)。声明记录变量与声明其它类型变量一样,下面是两个记录变量的说明:
     719  var NewEmployee,PromotedEmployee:TEmployee; 
     720  记录类型中的每个域都有一种数据类型,你既可以单独访问这些域,也可以把记录作为一个整体来使用。例如,你可以像下面那样访问NewEmployee记录中的Salary域或整个记录:
     721 NewEmployee.Salary:=43211.00; 
     722 PromotedEmployee:=NewEmployee;
     723   当你要访问记录内的域时,需要指定记录名,并在记录名后加(.),然后跟上域名。例如:
     724 PromotedEmployee.Position
     725   如果要对多个域赋值,则每个域前都必须加记录名。例如: 
     726 PromotedEmployee.LastName :='Gates'
     727 PromotedEmployee.FirstName:='Bill'
     728 PromotedEmployee.YearHired:=1990;
     729 PromotedEmployee.Salary:=92339.00;
     730 PromotedEmployee.Position:='Manager'
     731  ?Pascal提供了With语句,使你可以减少重复书写记录名的烦恼。With语句的语法是: With记录变量名Do ...
     732   每个可变部分由至少由一个常量(Constant)标识,所有常量必须是唯一的,并且类型为与tag field type指定类型相容的类型。Identifier用于记录可变部分的可选部分,称为识别字段标识符。如果定义了识别字段标识符,程序可以使用该标识符决定在给定的时间内哪个可变部分是活动的,如果没有定义识别字段标识符,程序必须根据其它规则选定记录可变部分。
     733   下面是带有可变部分的记录类型的例子:
     734 type TPolygon = record X, Y: Real; 
     735 case Kind: Figure of
     736   TRectangle: (Height, Width: Real);
     737   TTriangle: (Side1, Side2, Angle: Real);
     738   TCircle: (Radius: Real); 
     739 End;
     740  注意:记录可变部分的字段不能是长字符串类型和变体类型,也不能含有长字符串类型和变体类型分量的构造类型。 
     7412)记录类型常量
     742   记录常量的每个字段都是类型常量,
     743   一个记录类型常量每个字段由一个标识符和类型常量组成,不同字段用分号隔开,字段部分用括号括起。像简单类型常量一样,记录类型常量用来定义一个记录常量,下面是一些例子。
     744 type TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec); 
     745 TDate = record D: 1..31; 
     746 M: Month; 
     747 Y: 1900..1999;
     748 end;
     749 const
     750   SomeDay: TDate = (D: 2; M: Dec; Y: 1960);
     751   上面的例子首先定义一个记录类型TDate,然后定义了一个记录常量SomeDay。注意:记录类型常量中个字段的出现顺序必须与记录类型定义中的顺序一致。如果记录类型包括文件类型字段,则不能定义该记录的记录常量。如果记录类型包括可变部分,则只有被选择的可变部分可以定义常量。
     752 4.文件类型
     753   文件是指相同类型元素的有序集合。Delphi处理文件有三种方式,一种是使用Object Pascal标准的文件处理技术;一种是使用Windows的文件处理函数;还有一种是使用文件流对象。
     754   Object Pascal标准的文件处理技术,有专门的数据类型和文件处理例程,并且与Windows的标准API不兼容,但对于熟悉Pascal的用户来说,仍然是操作文件的好选择。下面我们就对此进行介绍。
     755   声明一个文件类型的语法如下: type fileTypeName = file of type
     756   这里,fileTypeName是任何有效的标识符, type是一种大小固定的数据类型,称之为基类型。 基类型不能使用指针类型,不能包括动态数组、长字符串、类、对象、指针、可变类型以及其它文件类型。但可以是其它结构类型,例如:
     757 type
     758 PhoneEntry = record FirstName, LastName: string[20];
     759 PhoneNumber: string[15];
     760 Listed: Boolean; 
     761 end; 
     762 PhoneList = file of PhoneEntry;
     763   这里,PhoneEntry是自定义的结构类型,PhoneList是以PhoneEntry为基类型的文件类型。在定义了文件类型后,就可以直接定义文件类型的变量了。例如:
     764   var List1: PhoneList;
     765   有时侯,我们也可以使用file of基类型的方式直接定义文件类型变量。例如下面的定义与上面的形式有同样的效果: 
     766   var List1: file of PhoneEntry;
     767   如果我们在声明文件类型时不指明其基类型。则这样的文件我们称之为无类型文件,如: 
     768   var DataFile: file;
     769   无类型文件主要用于直接访问磁盘文件的多层I/O操作。6.3.4 指针类型指针类型对程序员来说可能是最复杂和最灵活的数据类型。当你在Delphi中创建一个数据结构时,首先要分配内存空间。分配的内存空间用于存储数据结构中的数据。而指针就是指向分配空间的内存地址。使用指针,可以使程序不必每次需要时都去分配,只要申请一次即可,其它过程或函数使用同一块内存空间时,只要使用该内存空间的地址。例如,假设你的一个邻居问你怎样去百货店,你并不需要把整个百货店搬到邻居家里,只需要告诉它去百货店的路径即可,这个路径类似于一个指针。
     770 1.指针类型的声明
     771   声明指针类型的语法规则见图6.10 772   其中基类型可以是简单类型,也可以是前面介绍的结构,或只是一个标识符。如果基类型是一个未定义的类型标识符的话,则该类型标识符必须在同一块内声明。
     773   下面是带有指针类型声明的例子:
     774 type WordPtr =^Word;
     775 RealPtr =^Real;
     776 PersonType=Record LAstNAme:String; 
     777 FirstNAme:String; Age:Integer;
     778 end; 
     779 PersonPointer = ^PersonType;
     780   上例中,声明了三个指针类型,一个是WordPtr,指向^Word,一个是RealPtr,指向^Real,还有一个是PersonPointer,指向一个标识符,而该标识符标识一个记录类型。
     781   声明了指针类型之后,就可以声明指针类型变量,
     782   例如:Var WP:WordPtr; 
     783       RP:RealPtr;
     784       Person:PersonPointer;
     785 2.指针的使用
     786   Delphi提供专门的过程和函数操作指针,这些过程和函数是:New过程,@操作符,PTR函数,GetMem过程。下面分别介绍。
     7871)New过程
     788   New过程是 Pascal中的标准例程(在System单元声明),用于在应用程序堆栈中为动态变量申请一块区域,并把该区域的地址赋予指针变量。New过程的语法为: 
     789   procedure New(var P: Pointer);
     790   其中P是一个指针变量。所分配区域的大小由指针变量P的基类型决定。如果在应用程序堆栈中没有足够的内存空间供分配,将触发EOutOfMemory异常。
     791   新分配的内存空间由P指向,P^即为类型的动态变量。应用程序不再需要该动态变量时,可以调用Dispose标准例程释放为该变量分配的内存空间。 
     792  下面是使用New过程的举例: 
     793 type PListEntry = ^TListEntry;
     794 TListEntry = record
     795   Next: PListEntry;
     796   Text: string; 
     797  Count: Integer;
     798 end;
     799 var List, P: PListEntry;
     800 begin
     801 ... 
     802 New(P);
     803 P^.Next := List; 
     804 P^.Text := 'Hello world';
     805 P^.Count := 1; 
     806 List := P; 
     807 Dispose(P);
     808 ... 
     809 end;
     810   上例中,声明了一个指针类型 PListEntry,指向标识符 TListEntry,而该标识符标识一个记录类型 TlistEntry,然后定义了两个指针变量List和P。程序首先用New过程在应用程序堆栈中为动态变量申请一块区域,并把该区域的地址赋予指针变量P。P^即为记录类型TListEntry的动态变量。不再需要该动态变量后时,调用Dispose释放为该变量分配的内存空间。
     8112)@操作符
     812   @操作符是个一元操作符,用于获得操作数的地址,其使用语法见图6.11 813   从图中可以知道,@后面的操作数可以是变量、过程、函数或类类型中的方法。
     814   程序示例如下:
     815 procedure ChangeValue(X:Integer)
     816   Var IntPtr:^Integer;
     817 begin
     818  IntPtr:=@X; 
     819   Writeln(IntPtr^); 
     820   IntPtr^:=20;
     821 end;
     822   假设主程序如下:
     823 begin
     824 Param:=10; 
     825 ChangeValue(param); 
     826 Writeln(Param); {10}
     827 end;
     828   上例中,ChangeVAlue过程首先声明了一个指向整型数的指针Ptr,然后用@操作符取出X的地址赋予IntPtr指针,并显示Ptr指针指向的数,最后改变这个数。
     8293)PTR函数
     830   Ptr函数是 Pascal中的标准例程(在System单元声明),用于把一个指定的地址转换为指针。Ptr函数的语法为: 
     831   function Ptr(Address: Integer): Pointer;
     832   其中Address是一个整数,用于表示一个32位地址,函数执行的结果是把32位地址转化为指针。
     8334)GetMem过程
     834   GetMem过程也是Pascal中的标准例程(在System单元声明),类似于New,用于在应用程序堆栈中为动态变量申请一块指定大小的区域,并把该区域的地址赋予指针变量。GetMem函数的语法为:
     835   procedure GetMem(var P: Pointer; Size: Integer);
     836   其中P是一个指针变量,Size指定区域的字节数。
     837   所分配区域的大小由指针变量P的基类型决定。如果在应用程序堆栈中没有足够的内存空间供分配,将触发EOutOfMemory异常。如果程序不再需要该动态变量时,可以调用FreeMem标准例程释放为该变量分配的内存空间。
     838  程序示例如下:
     839 Var F: file; 
     840 Size: Integer;
     841 Buffer: PChar;
     842 begin
     843 AssignFile(F, 'test.txt');
     844 Reset(F, 1); 
     845 try
     846   Size := FileSize(F);
     847   GetMem(Buffer, Size);
     848   try
     849    BlockRead(F, Buffer^, Size);
     850    ProcessFile(Buffer, Size);
     851   finally
     852    FreeMem(Buffer);
     853   end; 
     854 finally
     855 CloseFile(F);
     856 end;
     857 end;
     858   上例打开一个名字为test.txt的文件,并把文件读入动态分配的缓冲区,缓冲区大小为文件的大小,然后对文件进行处理,最后释放动态分配的缓冲区,并关闭文件。
     859   Pascal中有一个特殊的保留字nil,这是一个空指针常量,当指针的值为nil时,表示指针当前没有指向任何动态变量。值为nil的指针变量不能访问动态变量。
     860 3.无类型指针
     861   无类型的指针是指指针变量在声明时没有指明基类型。无类型指针在声明中只使用Pointer。例如:
     862   var pAnyPoint:Pointer;
     863   指针pAnyPoint可以指向任何变量类型。无类型的指针的作用是它可以指向任何类型,但是,不能用指针变量符后加^的形式来引用它的动态变量。
     864 4.字符指针类型
     865   字符指针类型即PChar数据类型,是一个指向以NULL(不是零)字符结尾的字符(Char)串的指针。这种类型主要用于与外部函数如在Windows API中所用的函数兼容。与Pascal字符串不同,Windows和C字符串没有一个长度字节。取而代之的是它们从0字节索引开始,以一个NULL(#0)结束。Pascal RTL字符函数根据长度决定存储在字符串变量中的字符数目。C函数实际上一次搜索字符数组的一个字符,直到碰到NULL,表示字符串结尾。在Windows API中所用的许多函数以指向NULL结束字符串或用NULL结束填入缓冲区的字符。在Pascal中使用这些函数就需要PChar类型变量。内存将分配给变量并被所需函数使用。
     866   除了PChar外,Delphi还包含PAnsiChar和PWideChar数据类型。
     867   PAnsiChar数据类型是一个指向以NULL(不是零)字符结尾的AnsiChar字符串的指针,在Delphi中,PCHAR等同于PAnsiChar。
     868  ?PWideChar数据类型是一个指向以NULL(不是零)字符结尾的WideChar字符串的指针,用于UniCode字符集。实际上,PAnsiChar和PWideChar数据类型的定义为:
     869 type
     870   PAnsiChar = ^AnsiChar;
     871   PWideChar = ^WideChar;
     872   PChar = PAnsiChar;
     873   字符串类型与PCHAR类型赋值兼容,即一个字符串可以直接赋给一个PCHAR类型的变量,例如:
     874   var P: PChar;
     875    ...
     876   begin
     877   P := 'Hello world...';
     878   end;
     879   上面赋值语句首先申请一块区域,该区域包含字符串 'Hello world...',并在最后加上NULL,然后P指向这块内存区。上述例子等价于下列形式:
     880   const TempString: array[0..14] of Char = 'Hello world...'#0;
     881   var P: PChar; 
     882   ...
     883  begin
     884   P := @TempString;
     885  end;
     886 6.3.4 过程类型
     887   Object Pascal允许把过程和函数作为一个整体赋给变量和作为参数传递。实现这一功能的途径是使用Object Pascal的过程类型。
     888  声明一个过程类型的语法与声明过程或函数的首部的语法相似,不同的是声明一个过程类型时不需要过程或函数保留字后面的标识符。声明过程类型时可以指定一种调用约定方式,缺省的调用方式是Register。下面是声明过程类型的举例:
     889 type
     890 TProcedure = procedure; 
     891 TStrProc = procedure(const S: string);
     892 TMathFunc = function(X: Double): Double;
     893   上例声明的三个过程类型中,第一个是不带任何参数的过程,第二个是带一个参数S的过程,第三个是带一个参数X的函数,函数返回值为Double。
     894   过程类型根据其是否运用于对象分为两类:全局过程指针和方法指针。
     895   声明过程类型时不带of Object的是全局过程指针。全局过程指针指向的是全局的过程或函数。例如上面的过程类型Tprocedure,TstrProc,TMathFunc都是全局过程指针。
     896   声明过程类型时带有of Object的是方法指针。方法指针指向的是一个对象的过程或函数方法。例如下面的过程类型是方法指针。
     897 type TMethod = procedure of object;
     898 TNotifyEvent = procedure(Sender: TObject) of object;
     899   声明过程类型变量的方法与声明其它类型变量的方法相同,下面例子声明两个过程类型变量:
     900 var Proc:TProcedure; 
     901   StrProc:TStrProc;
     902   过程类型变量的值可以取下列四种之一:
     903  nil一个过程类型变量
     904  一个全局过程或函数标识符一个方法指示符下面举例说明过程类型的用法。
     905 type TMainForm = class(TForm) 
     906   procedure ButtonClick(Sender: TObject);
     907   ...
     908   end;
     909 var MainForm: TMainForm; 
     910   MathFunc: TMathFunc; 
     911   OnClick: TNotifyEvent;
     912 function Tan(Angle: Double): Double;
     913   begin
     914    Result := Sin(Angle) / Cos(Angle);
     915   end;
     916   上例的TMainForm是一个类类型,TMathFunc是前面定义的全局过程指针,TnotifyEvent是前面定义的方法指针。其中MathFunc和OnClick是两个过程类型变量。变量MathFunc和OnClick的赋值方式为:
     917   MathFunc := Tan;OnClick := MainForm.ButtonClick;
     918   调用结果为:
     919 X := MathFunc(X);{等价于 X := Tan(X) }
     920 OnClick(Self);{等价于 MainForm.ButtonClick(Self) }
     921   过程类型变量值等于NIL表示该过程类型变量没有赋值,因此在过程语句或函数调用中使用值等于NIL的过程类型变量将发生错误。防止的办法是使用Assigned()函数。例如:
     922   if Assigned(OnClick) then OnClick(Self);
     923   如果给定的过程类型变量已经赋值,Assigned函数返回TRUE,如果给定的过程类型变量值为NIL,Assigned函数返回FALSE。在把一个过程或函数赋给一个过程类型变量时要注意赋值兼容,必须满足下列条件:调用约定方式必须相同。?参数个数必须相同,相应的数据类型必须相同。?函数返回的值类型必须相同。
     924 6.3.5 Variant数据类型
     925   Variant主要用于表达需要动态改变类型的数据。例如,当一个数据的实际类型在编译时不知道或运行时需要改变类型时,就可以使用Variant类型。
     926   Variant类型变量可以包含integer, real, string, boolean, 日期和时间等类型值或以及 OLE自动化对象等,还可以表示长度和维数可变的数组。
     927   Variant变量在首次创建时,总是被初始化为Unassigned。Unassigned是Variant变量的一个特殊值,表明Variant变量还未赋值,Variant变量的另一个特殊值是NULL,指示Variant变量未知或丢失数据。
     928 6.4 数据类型的转换
     929   Object Pascal是一种类型严谨的程序设计语言,不是所有类型的数据都可以互相赋值的。只有赋值两边的数据类型一致或兼容才可以进行赋值操作。下面就有关数据类型兼容和强制数据类型转换等概念进行介绍。
     930 6.4.1 类型兼容
     931   所闻类型兼容,是指一种类型的数据可以与另一种类型的数据进行关系运算。类型兼容是赋值兼容的前提条件,也是Object Pascal数据运算的基本前提。
     932 ?Object Pascal规定,只有满足下列条件才是类型兼容:
     933 ?两种类型都一致。
     934 ?两种类型都是实型。
     935 ?两种类型都是整型。
     936 ?一种类型是另一种类型的子界。
     937 ?两种类型都是另一种宿主类型的子界。
     938 ?两种类型都是另一种兼容基类型的集合类型。
     939 ?两种类型都是紧凑字符串类型,并具有相同的元素个数。
     940 ?一种类型是字符串类型,另一种类型是字符串类型、紧凑字符串类型或字符类型。
     941 ?一种类型是Pointer类型,另一种类型是任意的指针类型。
     942 ?两种类型都是类类型或,类引用类型,并且一种类型继承了另一种类型。
     943 ?一种类型是PChar类型,另一种类型是形式为array[0..X] of Char的字符数组。
     944 ?两种类型都是基类型相同的指针类型(编译开关$T设置为{$T+})。
     945 ?两种类型都是结果类型相同、参数个数相同、参数类型一致的过程类型。
     946 ?一种类型是Variant类型,另一种类型是整型、实型、字符串类型或布尔类型。
     947   当两个类型要进行关系运算操作而又不满足类型兼容时,将产生编译错误。 
     948 6.4.2 赋值兼容
     949   类型兼容仅仅可以进行关系运算,只有赋值兼容的变量才可以赋值或进行参数传递。
     950   类型T2的值与类型T1的值赋值兼容是指T1和T2允许赋值操作,即:
     951   T1:=T2;
     952   Object Pascal规定,类型T1的值与类型T2的值赋值兼容必须有满足下列条件:
     953 ?T1和 T2类型相同,并且都不是文件类型或包含文件类型的自定义类型。
     954 ?T1是T2是兼容的有序类型,类型T2的值在类型类型T1的取值范围内。
     955 ?T1和 T2都是实型, 类型T2的值在类型T1的取值范围内。
     956 ?T1是实型,T2是整数型。
     957 ?T1和 T2都是字符串类型。
     958 ?T1是字符串类型,T2是字符类型。
     959 ?T1是字符串类型,T2是紧凑的字符串类型。
     960 ?T1是长字符串类型,T2是PChar类型。
     961 ?T1和T2是兼容的、紧凑的字符串类型。
     962 ?T1和T2是兼容的、集合类型。 类型T2的所有成员在类型T1的取值范围内。
     963 ?类型T2在类型T1的取值范围内。
     964 ?T1和T2是兼容的指针类型。
     965 ?T1是类类型,T2是T1的继承类类型。
     966 ?T1是类引用类型,T2是T1的继承类引用类型。
     967 ?T1是PChar类型,T2是字符串常量。
     968 ?T1是PChar类型,T2是形式为array[0..X] of Char的字符数组。
     969 ?T1和T2是兼容的过程类型。
     970 ?T1是过程类型,T2是具有与T1嗤�峁�嘈拖嗤�⒉问�鍪�嗤�⒉问�嘈鸵恢碌墓�袒蚝��?br> ?T1是Variant类型,T2是Integer,real,string或boolean类型。
     971 ?T1是Integer,real,string或boolean类型,T2是Variant类型。当两个类型要进行赋值操作而又不满足赋值兼容时,将产生编译错误。
     972 6.4.3 变量强制类型转换
     973   变量强制类型转换就是强制将一种类型变量转换为另一种类型的变量。程序员自己确定强制类型转换的合法性。
     974   图6.13是变量强制类型转换的语法规则。
     975   当变量强制类型转换应用于一个变量时,该变量就被视为由类型标识符说明的类型。变量的大小必须与类型标识符说明的类型的大小相同。变量之前可以放置一个或多个类型允许的限定符。
     976   Word类型的变量W转换为TByteRec, TWordRec(L)将一个LongInt类型的变量L转换为TWordRec类型,而PByte(L)则将LongInt类型变量L转换为指针类型Pbyte。
     977 6.4.4 数值强制类型转换
     978   数值强制类型转换就是强制将数值(或表达式)从一种类型转换为另一种类型。 
     979   其中表达式类型必须是有序类型或指针类型。 在转换中,如果结果类型的大小不同于表达式类型的大小,则有可能造成数据的截止或扩展。下面举例说明数值强制类型转换的用法。
     980 Integer('A')//把字符A转换为一个整数。
     981 Char(48)//把数字48转换为一个字符。
     982 Boolean(0)//把数字0转换为一个布尔值。
     983 Longint(@Buffer)//把指针转换为一长整数。
     984 Int64(I)//把一个整数转换为64位整数
     985 6.5 数据类型运算符 
     986 操作符 操作  操作数据类型 结果数据类型
     987 DIV  整数除  integer   integer
     988 mod  余数除  integer   integer
     989 mod运算符的结果是两个操作数相除后的余数的整数部分。
     990 shl  按位左移 integer   Boolean
     991 shr  按位右移 
     992 in  属于
     993 6.5.4 运算符的优先级
     994 运算符              优先级  分类
     995 @, not              1 (最高) 一元运算符
     996 *, /, div, mod, and, shl, shr, as 2     乘法运算符
     997 +,-, or, xor           3     加法运算符
     998 =, <>, <, >, <=, >=, in, is    4 (最低) 关系运算符
     999 6.6 语 句
    1000   这些例子都是DOS窗口方式的,而不是通常的Windows应用程序。如果读者要调试这些程序,需要修改Delphi的一些缺省设置。其步骤是:
    10011)开始Delphi。
    10022)如果当前不是自动打开一个新项目 ,选择File|New命令开始一个新项目 。
    10033)选择Project|Options|Linker命令,使能Generate Console Application核对框,从而是Delphi创建的程序是DOS窗口方式的,而不是通常的Windows应用程序。
    10044)选择View|Project Source命令,进入代码编辑器编辑项目 文件代码,键入本书提供的例子。
    10055)运行程序,程序将在它自己的DOS窗口运行。要关闭DOS窗口,选择Alt+F4或单击窗口的右上角单击X。
    1006 6.6.1 赋值语句
    1007 6.6.2 块语句
    1008 1.ASM/END块语句
    1009   ASM块语句在Pascal中嵌入汇编语言代码。由于Delphi Pascal对计算机资源提供了很好的支持,因此,除非特别需要,一般不需要使用汇编语句。 
    1010 2.BEGIN/END块语句
    1011 6.6.3 Delphi控制语句
    1012  Object Pascal使用控制语句来控制程序的执行顺序。7个是分支语句,3个是循环语句:
    1013 .分支语句
    1014 . if 语句
    1015 . case语句 
    1016  CASE Choice of
    1017   '1': EvaluateChoice;
    1018   '2': Initialize
    1019   ELSE Writeln('Bad Choice,Try Again.');
    1020  END;
    1021   请注意,CASE语句的常量范围不能重叠。Else要放在所有判断语句之后.
    1022 . GOTO语句 
    1023   GOTO语句强行将程序转向一个指定的点执行。该指定点用一个标号标识。 
    1024 . Break语句
    1025 . Continue语句
    1026 . Exit语句 
    1027   EXIT语句的功能是退出当前的代码块。如果代码块是主程序,EXIT语句导致程序的终止;如果当前块是嵌套的,EXIT语句跳到外一层嵌套继续执行。如果当前块是过程或函数,EXIT语句导致过程或函数执行终止,跳到调用过程或函数的语句的下一条语句执行。 
    1028 . Halt语句
    1029   HALT语句导致程序的非正常结束,并返回到操作系统。通常是在程序遇到致命错误时才使用HALT语句。HALT语句后可跟一个整数代码HALT(1),以指定错误的原因。
    1030 . 循环语句
    1031 . Repeat/Until语句
    1032   REPEAT
    1033    Key:=GetChar; 
    1034    Writeln('Key IS',key);
    1035    UNTIL Key=$D; 
    1036 . While语句
    1037   WHILE key<>$D DO key:=GetChar; 
    1038 . for语句
    1039   for V := Expr1 to Expr2 do Body; 可使用DOWNTO;在循环体中,如果不想执行循环下面的语句,而直接进入下一次循环,可以使用Continue语句.
    1040   要退出循环,跳到FOR/DO语句下面的语句执行,可以使用break语句;如果不想执行循环下面的语句,而要求直接进入下一个循环,可以使Continue语句。
    1041 6.7 过程与函数 
    1042 6.7.1 过程的定义和调用
    1043   要定义和调用一个过程,首先要在程序的TYPE区声明一个过程.
    1044  下面是一个过程声明的例子:
    1045 procedure NumString(N: Integer; var S: string);
    1046   过程声明之后,就应当在Implementation区定义这个过程,定义的规则如下。过程:
    1047 Procedure <name>(<Parameters>)
    1048 <declarations> 
    1049 BEGIN
    1050 <statements> 
    1051 END;
    1052 6.7.2 函数的定义和调用
    1053   函数的定义和调用与过程的定义和调用类似,不同的是函数的首部,函数的首部多了一个返回结果类型。
    1054  function Max(A: Vector; N: Integer): Extended;
    1055   Max函数返回类型为Extended。函数声明之后,就应当在Implementation区定义这个函数。
    1056 6.7.3 返回值
    10571)返回值直接送给函数名。
    10582)返回值送给Delphi的一个内置变量Result。
    1059  如果你写的函数有可能移植到其它Pascal编译器中使用,最好使用第一种方式。
    1060 6.7.4 调用约定
    1061   从前面说明的过程和函数的语法规则我们知道,在声明过程或函数时,可以在附属块指定过程或函数的参数的传递方式。Pascal共提供了五种传递方式,分别为Register,Pascal,Cdecl,Stdcall,SafeCall。缺省的调用方式是Register方式。如果一个过程或函数没有指定过程或函数的调用方式,就采用Register调用方式。
    1062   调用方式的语法示例如下:
    1063   function Max(A: Vector; N: Integer): Extended;Stdcall;
    1064   Object Pascal调用方式的区别于以下几点:
    10651)传递参数的顺序
    1066   Register和 Pascal调用方式传递参数是从左到右,即最左边的参数先产生并首先传递,最右边的参数最后产生并最后传递。而Cdecl, Stdcall和 Safecall 调用方式传递参数则是从右到左。
    10672)堆栈中删除参数
    1068   使用Pascal、Stdcall和Safecall调用方式的过程或函数在返回时程序自动删除堆栈中的参数,而Cdecl调用方式必须在程序返回时调用者自己删除堆栈中的参数。
    10693)使用寄存器传递参数
    1070   Register调用方式使用三个CPU寄存器来传递参数,而其它调用方式使用堆栈来传递参数。
    1071   Register调用方式通常是最快的参数传递方式,因为它不需要创建栈帧。Pascal和 Cdecl调用方式通常用于调用用C,C++或其它语言书写的动态链接库程序。Stdcall 调用方式通常用于Windows API程序。而Safecall调用方式通常用于实现OLE自动化编程的双接口(Dual interfaces)。
    1072 6.7.5 指示字
    1073   在声明过程或函数时,可以在附属块使用指示字以进一步指定过程或函数的产生方式。Delphi过程或函数分别提供了Block,External,Asm,Forward。指定调用方式的语法示例如下:
    1074   procedure MoveWord(var Source, Dest; Count: Integer); external;
    1075   其中Block是缺省方式,表示过程或函数的语句部分是 Pascal程序快,下面对External,Assembler,Forward进行介绍。
    1076 1.External
    1077   该指示字表示过程或函数是外部的,通常用于从动态链接库中引入过程或函数。External后可以动态链接库名或表示动态链接库的有序数,也可以指定引入的过程或函数名。例如:
    1078   function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';
    1079   上例中,user32.dll指定用于引入过程或函数的动态链接库名(也可以是一个有序数),MessageBox指定从动态链接库中引入过程或函数名。
    10802)Assembler
    1081   该指示字表示过程或函数是使用嵌入式汇编语言编写的。例如函数声明:
    1082  function LongMul(X, Y: Integer): Longint;Assembler
    1083   其定义为:
    1084   function LongMul(X, Y: Integer): Longint;
    10853)Forward
    1086   该指示字表示一个过程或函数是向前查找的。在声明了一个过程或函数是向前查找的之后,该过程或函数的定义必须在后面的某个地方定义。 
    1087 procedure Walter(M, N: Integer); forward;
    1088 procedure Clara(X, Y: Real);
    1089 begin
    1090 ... 
    1091 Walter(4, 5);
    1092 end;
    1093 procedure Walter;
    1094 begin
    1095 ...
    1096 MessageBeep(0);
    1097 end;
    1098  注意:不能在单元的interface部分声明向前查找过程。在使用向前查找过程时,要注意相互递归。
    1099 6.7.6 参数
    1100   当调用过程或函数时,常常需要使用参数传递数据给被调用的过程或函数。在某种程度上,使过程、函数更有用更灵活的方法就是使用参数。
    1101   在Pascal中,调用过程或函数使用的参数称为实参,被调用过程或函数使用的参数称为形参,例如,下面语句中,Edit1是实参:
    1102   ColorIt(Edit1);
    1103   下面的AnEditBox是形参:
    1104   Procedure ColorIt(AnEditBox:Tedit);
    1105   Delphi传递参数的方式有四种:
    11061)传值(Passing By Value)。
    1107   变量和结构被完整地拷贝到堆栈中,而不是通过机器的寄存器。通过值传递参数可以防止调用的函数修改原来的参数,因为调用的函数接收到的只不过是参数的一个副本。例如:
    1108 procedure Tform1.Button1Click(Sender:Tobject);
    1109 var Number:Integer;
    1110 begin
    1111   Number:=StrToInt(Edit1.text); 
    1112   Calculate(Number); 
    1113   Edit2.Text:=IntToStr(Number);
    1114 end;
    1115 Procedure Calculate(CalcNo:Integer);
    1116 begin
    1117  CalcNo:=CalcNo*10;
    1118 end;
    1119   在Calculate过程中,CalcNo参数按值传递,执行该过程后,CalcNo的值扩大了十倍。但是,调用过程Tform1.Button1Click中Number并没有改变,因此Edit1编辑框与Edit2编辑框的值一样。
    11202)传引用(Passing By Reference)。
    1121   传递一个指向参数的引用(指针),按规则引用可用作指针和值。改变引用传递的参数要影响调用源参数的拷贝。
    1122   使用传引用必须在参数前加上Var保留字。例如,把Calculate改写如下:
    1123   procedure Calculate(Var CalcNo:Integer);
    1124   begin
    1125    CalcNo:=CalcNo*10;
    1126   end;
    1127   修改后,Calculate过程的CalcNo参数为按引用传递,执行该过程后,CalcNo的值扩大了十倍,同时,调用过程Tform1.Button1Click中Number也作了改变,因此Edit2编辑框的值是Edit1编辑框值的10倍。
    11283)常量传递(Constant Parameters)。
    1129   如果过程或函数运行时,形参的值永远都不会改变,就可以考虑使用常数参数。要使一个参数为常数参数,只要在参数前加上Const保留字。例如:
    1130 function TDirectoryOutline.ForceCase(const AString: string): string;
    1131 begin
    1132   if Assigned(FCaseFunction) then
    1133    Result := FCaseFunction(AString) 
    1134   else
    1135    Result := AString;
    1136 end;
    1137   当你不需要参数改变时,可以使用常数参数防止偶然对该参数的修改。如果程序某个地方对常数参数进行了修改,你将会得到一个非法变量引用错误信息。
    11384)默认参数
    1139   在Delphi中,可以为过程和函数定义默认参数。默认参数仅仅显示在参数列表的尾部,其形式是:
    1140   参数名: 类型 =1141   当调用包括默认参数的过程或函数时,默认参数的值可以省去。例如下面是一个函数的定义:
    1142 procedure FillArray(A: array of Integer; Value: Integer = 0);
    1143   下面是两个合法的调用语句:
    1144 FillArray(MyArray, 1);//直接传递值 
    1145 FillArray(MyArray);//使用默认参数 
    1146  
    1147  
    1148 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lcq419/archive/2006/09/29/1305538.aspx
  • 相关阅读:
    mysql u root p 等这些常用的参数
    关于类对象大小的 sizeof 计算问题
    sql like用法
    链表队列
    Zend Studio安装教程
    #define和inline 的区别
    ERROR 1045 (28000): Access denied for user root@localhost (using password: NO)
    Ubuntu 10.04下LAMP环境的配置与使用教程
    win7保护视力的电脑设置
    sql between and 用法
  • 原文地址:https://www.cnblogs.com/Thenext/p/9410320.html
Copyright © 2011-2022 走看看