DELPHI基础教程
第二章 Delphi面向对象的编程方法(三) 2.1.8.4 过程和函数的语句部分 过程或函数的语句部分由 begin 开始, end 结束。函数需要一个返回值。可以将返回值赋给函数名称,也可以将返回值赋给 Result 变量。下面的例程将返回值赋给函数名称: function CalculateInterest(Principal,InterestRate: Double):Double; begin CalculateInterest := Principal * InterestRate; end; 将返回值赋给 Result 变量也是可以的,则上面的程序改为: Result := Principal*InterestRate; 下面是这个函数的调用方法: InterestEarned :=CalculateInterest(2000,0.012); 在 Implementation 后面的过程和函数,可以且只能被此库单元的事件处理过程使用。要让过程和函数可以被其他的程序库单元使用,则需要将过程或函数的标题部分放在库单元中的 interface 部分,而把含标题的整个过程或函数放在库单元的 inplementation 部分,并在要访问这个过程或函数的库单元的 uses 子句中加入说明这个过程或函数的库单元名称。 2.1.8.5 函数的递归调用 在 Object Pascal 中,过程或函数必须先说明再调用。上文的 NoValue 函数必须在使用它的事件处理过程之前说明和执行,否则程序会报告一个未知标识符的错误。 以上规则在递归调用时是例外情况。所谓递归调用,是指函数 A 调用函数 B ,而函数 B 又调用函数 A 的情况。在递归调用中,函数要进行前置,即在函数或过程的标题部分最后加上保留字 forword 。下文的例程是一个递归调用的典型例子: … implementation var alpha:Integer; procedure Test2(var A:Integer):forword; {Test2 被说明为前置过程} procedure Test1(var A:Integer); begin A :=A-1; if A>0 then test2(A); { 经前置说明,调用未执行的过程 Test2} writeln(A); end; procedure Test2(var A:Integer);{ 经前置说明的 Test2 的执行部分} begin A :=A div 2; if A>0 rhen test1(A); { 在 Test2 中调用已执行的过程 Test1} end; procedure TForm1.Button1Click(Sender:TObject); begin Alpha := 15; { 给 Alpha 赋初值} Test1(Alpha); { 第一次调用 Test1, 递归开始} end; 按钮的 OnClick 事件处理过程给 Alpha 赋初值,并实现先减 1 再除 2 的循环递归调用,直到 Alpha 小于 0 为止。 2.1.8.6 过程和函数的参数 当您的程序代码在调用一个过程或函数时,通常用参数传递数据到被调用的过程或函数中。最常用的参数有数值参数、变量参数和常量参数三种。 由被调用过程或函数定义的参数为形参,而由调用过程或函数指明的参数叫实参。在 NoValue 函数中,说明函数体中的 AnEditBox 是形参,而调用时在 if NoValue(Edit1) …中, Edit1 是实参。 数值参数在运行过程中只改变其形参的值,不改变其实参的值,即参数的值不能传递到过程的外面。试看下面的例程: procedure Calculate(CalNo:Integer); begin CalNo := CalNo*10; end; 用以下例程调用 Calculate 函数: … Number := StrToInt(Edit1.Text); Calculate(Number); Edit2.Text := IntToStr(Number); … Number 接受由编辑框 1 输入的数值,经 Calculate 过程运算。它是一个数值型实参。在进入 Calculate 函数后,会把 Number 实参拷贝给形参 CalNo ,在过程中 CalNo 增大十倍,但并未传递出来,因此 Number 值并未改变,在编辑框 2 中显示仍然是编辑框 1 中的输入值。形参和实参占用不同的内存地址,在过程或函数被调用时,将实参的值复制到形参占用的内存中。因此出了过程或函数后,形参和实参的数值是不同的,但实参的值并不发生变化。 如果您想改变传入的参数值,就需要使用变量参数,即在被调用程序的参数表中的形参前加上保留字 var 。例如: procedure Calculate(var CalNo : Integer); 则 CalNo 并不在内存中占据一个位置,而是指向实参 Number 。当一个变参被传递时,任何对形参所作的改变会反映到实参中。这是因为两个参数指向同一个地址。将上一个例程中过程头的形参 CalNo 前面加上 var ,再以同样的程序调用它,则在第二个编辑框中会显示计算的结果,把第一个编辑框中的数值放大十倍。这时形参 CalNo 和实参 Number 的值都是 Nnmber 初始值的 10 倍。 如果当过程或函数执行是要求不改变形参的值,最保险的办法是使用常量参数。在参数表的参数名称前加上保留字 const 可以使一个形参成为常量参数。使用常量参数代替数值参数可以保护您的参数,使您在不想改变参数值时不会意外地将新的值赋给这个参数。 2.1.9 定义新的数据类型 Object Pascal 有一些系统预定义的数据类型,在 2.1.2 中已经对它们作了介绍。您可以利用这些数据类型以建立新的数据类型来满足程序的特定需要。下面简单地叙述了您能建立的主要数据类型,如枚举型、子界型、数组型、集合型、记录型、对象型等。 2.1.9.1 枚举类型 一个枚举型的说明列出了所有这种类型可以包括的值: type Tdays=( Sunday ,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday); 可以定义上述枚举类型的变量: var DayOfWeek:TDays; 在枚举型中,括号中的每一个值都有一个由说明它的位置决定的整形值。例如 Sunday 有整形值 0 , Monday 有整形值 1 等。您可以把 DayOfWeek 说明为一个整形变量,并将一星期的每一天赋一个整形值以达到相同的效果,但用枚举型会使得程序可读性好,编写容易。当您在枚举型中列出值时,您同时说明了这个值是一个标识符。例如您的程序中如果已经含有 TDays 类型且说明了 DayOfWeeks 变量,则程序中便不能使用 Monday 变量,因为它已经被说明为标识符了。 2.1.9.2 子界类型 子界型是下列这些类型中某范围内的值:整形、布尔量、字符型或枚举型。在您想限制一个变量的取值范围时,子界型是非常有用的。 type Thours = 0..23; TValidLetter = 'A' .. 'F'; TDays = ( Sunday ,Monday,Tuesday,Wednesday,Thursday, Friday,Saturday); { 枚举型} TWorkDay = Monday..Friday; { 一个 TDays 型的子界} 子界型限定了变量的可能取值范围。当范围检查打开时, ( 在库单元的 Implementation 后面有{ $R*.DFM} 字样表示范围检查打开,否则您可以在 Options|Project|Complier Options 中选择 Range Cheking 来打开范围检查 ) ,如果变量取到子界以外的值,会出现一个范围检查错误。 2.1.9.3 数组类型 数组是某种数据类型的有序组合,其中每一个元素的值由其相对位置来指定,您可以在数组的某个位置上放置数据,并在需要时使用这些数据。下面的类型说明了一个 Double 型的数组变量: var Check : array [1..10] of Double; 它表示 Check 指向一个含有 10 个 Double 型元素的数据串列,代表每一个元素的是 1 到 10 之间的数字,称为索引。数组的每一项由数组名称加上 [] 中的索引来表示。 Check 包含 10 个变量, Check[1] 表示第一个变量。您也可以把数组定义成类型: type TCheck = array[1..10] of Double; 则变量说明改为: var Check :TCheck; 您可以通过给数组赋值等方法来使用数组。下面的语句将 0.0 赋给 Check 数组中的所有元素: for J := 1 to 10 do Check[J] := 0.0; 数组也可以是多维的,下面的类型定义了一个 20 行、 20 列的数组。 type Ttable = array[1..20,1..20] of Double; var table1:TTable; 想将这一表格的所有数据初始化为 0.0 ,您可以使用 for 循环: var Col,Row:Integer; … for Col :=1 to 20 do for Row := 1 to 20 do Table1[Col,Row] := 0.0; 2.1.9.4 字符串类型 字符串类型事实上是一个一维的字符数组。当您说明一个字符串型的变量时,您应当指明这个字符串的大小,下面是说明字符串类型的例子: type MyString: string[15]; var MyName: MyString; 则变量 MyName 被说明成为最多可以包含 15 个字符。如果您没有说明字符串的大小, Delphi 会认为字符串包含最大值 255 个字符。给字符串赋值可以直接使用单引号括起的字串赋值: MyName := 'Frank.Smith'; 或 MyName := ' 张明 ' ; 因为 MyName 是一个可以包含 15 个字符的 MyString 型变量,上文的两个的变量都是有效的,一个汉字可以视作两个字符。当您给字符串型变量赋的值多于定义数值时,例如将 MyName 赋为‘ FrankSmith.Franklin ’,则 Delphi 只会接受前 15 个字符‘ FrankSmith.Fran ’。在内存中,字符串通常占用比所说明的大小多一个字节的空间,因为第一个位置是一个包含这个数组大小的字节。您可以使用索引值来访问字符串的字符, MyName[1] 可以得到 MyName 的第一个字符 'F' 。 您可以使用 Delphi 丰富的运算符、过程和函数来处理字符串型的变量和属性。下面介绍几个常用的运算符和 Delphi 过程或函数: Concat 和 (+) 功能相同,都可以将多个字符串组合在一起,建立一个较大的字符串; Copy 会返回一个字符串中的子字符串; Delete 在一个字符串中从一个指定位置起删除一定数目的字符; Insert 在一个字符串中插入一个字符串; Length 返回字符串的长度; Pos 返回一个子字符串在一个字符串中的位置,即索引值。 2.1.9.5 集合类型 集合类型是一群相同类型元素的组合,这些类型必须是有限类型如整形、布尔型、字符型、枚举型和子界型。在检查一个值是否属于一个特定集合时,集合类型非常有用。下面的例程可以说明集合类型的用法: 在窗体上加入一个编辑框和一个按钮,清除编辑框中的文字,在其上加上 Caption 为“输入元音”的标签 Label ,并在编辑框的下方加入一个空的标签,将按钮的 Default 属性改为 True ,建立按钮的事件处理过程如下: procedure TForm1.Button1Click(Sender:TObject); type Tvowels=set of Char; var Vowels:TVowels; begin Vowels := ['a','e','i','o','u']; if Edit1.Text[1] in Vowels then Lable2.Caption := ' 是元音 '; else Lable2.Caption := ' 请再试 '; end; 执行这个程序,在编辑框中输入字母,表达式 Edit1.Text[1] in Vowels 的结果是布尔型的, in 是运算符,用来判断字母是否存在于集合中。输入的判别结果会显示在编辑框的下方。以上就用到了集合类型 TVowels 。 2.1.9.6 记录类型 记录是您的程序可以成组访问的一群数据的集合。下面的例程说明了一个记录类型的用法: type TEmployee=record Name : string[20]; YearHired:1990..2000; Salsry: Double; Position: string[20]; end; 记录包含可以保存数据的域,每一个域有一个数据类型。上文的记录 TEmployee 类型就含有四个域。您可以用以下的方式说明记录型的变量: var NewEmployee,PromotedEmployee:TEmployee; 用如下的方法可以访问记录的单域: NewEmployee.Salary := 1000; 编写如下的语句可以给整个记录赋值: with PromotedEmployee do begin Name :=''; YearHired := 1993; Salary := 2000.00 Position := 'editor'; end; 您的程序可以将记录当成单一实体来操作: PromptEmployee := NewEmployee; 以上介绍了用户常用的自定义类型。在 Delphi 的编程中,对象是非常重要的用户自定义数据类型。象记录一样,对象是结构化的数据类型,它包含数据的域 (Field) ,也包含作为方法的过程和函数。在 Delphi 中,当您向窗体中加入一个部件,也就是向窗体对象中加入了一个域;每一个部件也是对象,每当您建立一个事件处理过程使得部件可以响应一个事件时,您即自动地在窗体中加入了一个方法。在本章第 2 节中,将详细讲述 Delphi 面向对象编程的方法和技巧。 2.1.10 Object Pascal 的库单元 Unit Units 是常量、变量、数据类型、过程和函数的集合,而且能够被多个应用程序所共享。 Delphi 已经拥有许多预定义的程序库单元可供您建立您的程序库单元使用。 Delphi 的 Visual Component Library 由多个程序库单元组成,它们说明了对象、部件以供您的应用程序用来设计用户界面。例如,当您在窗体中加入一个 Check Box 时, Delphi 自动在您的程序库单元中加入了 Stdctrls 库单元,因为 TCheckBox 部件是在 StdCtrls 库单元中说明的。 当您设计您的窗体时, Delphi 自动建立一个和您的窗体有关的库单元。您的库单元不必都和窗体有关,也可以使用预定义的只包含数学运算函数的库单元,或是自行编写数学函数库单元。在一个库单元中所有的说明都相互有关系,例如, CDialogs 程序库单元包含了在您的应用程序中使用的普通对话框的所有说明。 2.1.10.1 Object Pascal 程序库单元的结构 不管一个库单元是否和一个窗体有关,库单元的结构都是相同的。其结构如下: unit < 库单元名称 > interface uses < 选择性的库单元列表 > { 公有说明} implementation uses < 选择性的库单元列表 > { 私有说明} { 过程和函数的执行部分} initialization { 选择性的} { 选择性的初始化程序} end. 2.1.10.2 程序库单元的接口部分 interface 是库单元的接口部分,它决定了本库单元对其他任何库单元或程序的可见 ( 可访问 ) 部分。您可以在接口部分说明变量、常量、数据类型、过程和函数等等。 Delphi 在您设计窗体的库单元中,将窗体数据类型、窗体变量和事件处理过程都说明在这一部分。 interface 标志库单元接口部分的开始。在 interface 中的说明对要使用这些说明的其他库单元或应用程序是可见的。一个库单元可以使用其他 Unit 的说明,只需要在 uses 子句中指明那些库单元即可。例如,您在库单元 A 中编写程序代码,且您想调用 UnitB 于 interface 部分说明的程序。您可以把库单元 B 的名称加入到 A 的 interface 部分的 uses 子句中,则任何 A 中的程序都可以调用 B 中说明的程序。而且,如果 B 中 interface 部分的 uses 子句中出现 C 库单元,尽管 A 中未曾出现 C , A 同样可以调用 B 、 C 库单元在 interface 中说明的程序。但如果 B 出现在 A 的 interface 部分的 uses 子句中,那么库单元 A 便不能出现在 B 的 interface 的 uses 子句中。因为这样会产生对库单元的循环访问。当试图编译时,会产生出现错误信息。 2.1.10.3 程序库单元的实现部分 实现部分 implementation 中包含 interface 中说明的过程、函数、事件处理过程的具体实现程序代码。这一部分可以有自己的额外说明,但这些说明是私有的,外部程序不能调用这些说明。在 interface 中说明的函数实体必须在 implementation 部分出现,可以使用标题简写:只输入 procedure 或 function 保留字,后面跟过程或函数的名称即可,其后则是程序的实现部分了。如果您在 implementation 部分说明任何常式,其标题并未出现在 interface 部分,则必须写全其标题部分。 在 implementation 部分的 uses 子句中指定的库单元,只供给本库单元的程序使用其 interface 中说明的程序。其他使用本库单元的库单元,不能访问这些在 implementation 的 udes 子句中库单元的说明,因为在 implementation 后进行的库单元包含是私有的。所以上例中,如果 C 出现在 B 的 implementation 部分,则 A 不能使用 C 的公有部分,除非 C 出现在 A 的 uses 子句中。在 implementation 中出现的循环访问是 Delphi 所允许的,如果 A 的 implemetation 的 uses 子句中出现 B ,则 B 的 implementation 部分也可以出现 A 。 2.1.10.4 程序库单元的初始化部分 初始化当前库单元所使用的数据,或是通过 interface 部分将数据提供给其他应用程序、库单元使用时,您可以在库单元中加入一个 initialization 部分,在库单元的 end 前加上您的初始化语句。当一个应用程序使用一个库单元时,在库单元中的 initialization 部分会先于其他的代码执行。如果一个应用程序使用了多个库单元,则每一个库单元的初始化部分都会在所有的程序代码前执行。 2.1.10.5 使用 Delphi 的可视化部件及其库单元 当您在窗体中加入可视化部件时,如果该部件在可视化部件库中, Delphi 会在您的库单元的 interface 部分的 uses 子句中自动加上需要使用的库单元名称。但有些对象在 Delphi 的环境中并没有可视化部件存在,例如,您想在库单元中加入一个预定义的信息框,则您必须把 MsgDlg 库单元加入您的 uses 子句中。如果您要使用 TPrinter 对象的话,必须将 Printer 库单元加入 uses 子句中。在在线帮助中可以查到对象所属的预定义库单元。 要使用在其他库单元中说明的函数,应在函数的前面加上这一库单元的名称,并用‘ . ’号隔开。例如,要在 Unit2 中使用 Unit1 中说明的 Calculate 函数,应使用下面的方法: Number := Unit1.Calculate(10); 您可以在任何标识符如属性、常量、变量、数据类型、函数等之前加上库单元的名称。您可以在自由地在任何 Delphi 库单元中加入程序代码,但不要改变由 Delphi 生成的程序。 2.1.10.6 建立与窗体无关的新库单元 如果您想在工程中建立一个和任何窗体无关的新库单元,可以现选用 File|New Unit 。这时一个新的库单元加入了工程,新库单元的代码如下: unit Unit2; interface implementation end. Delphi 将根据您的工程中的文件数目为您的库单元选择名称,您可以在程序骨架间加入您的程序代码。 当编译您的工程时,这个新加入的库单元会被编译为一个具有 .DCU 后缀的文件。这个新生成的文件是链接到工程的可执行文件上的机器代码。 |