Object Pascal语言
控制串由一个或多个控制字符组成,由“#”开头,后面紧跟一个范围在0~255的无符号的整数,用于代表相应的ASCII字符。
下面再列出一些变量声明的例子:
var X, Y, Z: Double;
I, J, K: Integer;
Digit: 0..9
C: Color;
Done, Error: Boolean;
Operator: (Plus, Minus, Times);
Hue1, Hue2: set of Color;
Today: Date;
Results: MeasureList;
P1, P2: Person;
Matrix: array[1..10, 1..10] of Double;
在声明变量时,还可以带一个可选的Absolute子句,用于指定变量的值在内存中存储的绝对位置,
例如:
var CrtMode:Byte Absolute $0040:$0049
上例中,声明了一个Byte类型的变量CrtMode,Absolute指示字后第一常量是段的值,第二个常量是偏移量。变量可以在定义时就指定一个初值。
例如:
var I: Integer = 7;
等价于:
var I: Integer; ...I := 7;
指定的初值可以是任何变量类型的常数表达式。
1.纯常量
纯常量是一种预先定义的标识符,其值在定义后永远不会改变。
例如:const MaxValue = 237;
2.类型常量
与纯常量不同,类型常量用于保存数组、记录、过程以及指针等类型的值。类型常量不能出现在常量表达式中。
在缺省的编译器状态(即{$J+})下,类型常量的值可以改变,这时类型常量更像初始化过的变量。但当编译器状态改为{$J-}时,则类型常量的值在运行期就无法改变,此时,类型常量实际上变成了只读变量。
声明类型常量的语法规则为:
const identifier: type = value
这里,常量名要符合 Pascal语言关于标识符的规则。type是除了文件型和可变型之外的所有类型。Value是一个类型为type的表达式。例如:
const Max: Integer = 100;
一般来说,Value为一个常量表达式,但当type是数组、记录、过程和指针时,必须执行特殊的规则。
6.3 数 据 类 型
类型是某类数据的名称,用于确定能存储信息,及能存储多少信息等。ObjectPascal是一种强类型的语言,其数据类型的定义、声明以及数据的赋值和传递都必须遵循严格的语法规则。因此,学习和掌握数据类型是设计好程序的关键。
Pascal支持丰富的数据类型,本书将其分为6大类:
?简单类型,包括有序类型和实数型。其中有序类型是指整数类型、字符类型、布尔类型、枚举类型以及子界类型。
?字符串类型。?结构类型,包括集合类型、数组类型、记录类型、文件类型、类类型、类引用类型、接口类型等。
?指针类型。
?过程类型。
?变体类型。
6.3.1 简单类型
Object Pascal的简单类型包括有序类型和实数型。其中有序类型是指整数类型、字符类型、布尔类型、枚举类型以及子界类型。
下面分别进行讨论。
1.有序类型
有序类型是指整数类型、字符类型、布尔类型、枚举类型以及子界类型。有序类型是一个有序数的集合。在每一种有序类型中,任何一个该类型的元素都有一个唯一的先行数(第一个除外)和唯一的后继数(最后一个除外)。而且。每个值均有一个确定的序号。对整型数而言,该序号就是该整数的本身。而其它的有序类型(子界类型除外),第一个值的序号是0,第二个是1,依此类推。如果某个有序类型的值为N,则其先行数为N-1,后继数为N+1。
Object Pascal预先定义了一些函数,专门用于处理有序类型的表达式和变量,表6-1列出了最常用的几个。
表6-1 常用函数
函数 参数 返回值 备注
Ord 有序类型表达式 有序类型表达式值的序号 不能带Int64参数
Pred 有序类型表达式 有序类型表达式值的先行数 不能在拥有Write过程的
特性上使用
Succ 有序类型表达式 有序类型表达式值的后继数 不能在拥有Write过程的特性上使用
High 有序类型标识符或变量 该类型中序号最大的值 也可在short-string类型或数组
Low 有序类型标识符或变量 该类型中序号最小的值也可在short-string类型或数组
例如,表达式 High(Byte)将返回255,这是因为Byte类型的序数最大为255。表达式Succ(2) 将返回3,这是因为 3紧跟在2的后面。标准过程Inc和Dec用于增加或减少一个有序类型变量的值,例如:Inc(I)等价于执行 I := Succ(I)如果I是一个整数类型,还等价于执行: I := I + 1
下面分别对整数类型、字符类型、布尔类型、枚举类型以及子界类型进行介绍: (1)整数类型(Integer)在Object Pascal中,Integer类型是所有有符号整数的统称。
实际上,整数类型可以分为基本整数类型(Fundamental type)和一般整数类型(generic type)。一般整数类型(generic type)包括Integer和Cardinal两种。在实际编程时,请尽量区分这两种,因为底层CPU和操作系统对结果进行了优化。
表6-2列出了Integer和Cardinal的取值范围及存储格式。
表6-2 一般整数类型的取值范围及存储格式
数据类型 取值范围 存储格式
Integer ?147483648..2147483647 32位有符号
Cardina l0..4294967295 32位无符号
基本整数类型包括Shortint、Smallint、Longint、Int64、Byte、Word和Longword。表6-3列出了它们的取值范围及存储格式。
表6-3 基本整数类型的取值范围及存储格式
数据类型 取值范围 存储格式
Shortint -128..127 signed 8-bit
Smallint -12768..32767 signed 16-bit
Longint -2147483648..2147483647 signed 32-bit
Int64 -2^63..2^63? signed 64-bit
Byte 0..255 unsigned 8-bit
Word 0..65535 unsigned 16-bit
Longword 0..4294967295 unsigned 32-bit
一般整数类型的实际范围和存储格式随着Object Pascal的不同实现而不同,但通常根据当前CPU和操作系统来采取最佳的操作方式。
一般整数类型是最常用的整数类型,可以充分利用CPU和操作系统的特性,因此程序中应当尽量使用一般整数类型。基本整数类型独立于操作系统和CPU,只有当应用程序的数据范围或存储格式特别需要时,才使用基本整数类型。
通常情况下,整数的算术运算结果为Integer类型,等价于32位的Longint类型。只有当操作数存在 Int64类型时,才返回Int64类型的值。因此,下面的代码将产生错误的结果:
var I: Integer; J: Int64;
...
I := High(Integer);
J := I + 1;
在这种情况下,要取得一个Int64的值,必须进行类型转换:
J := Int64(I) + 1;
注意:绝大多数例程在遇到Int64时都把它转换为32位。但例程High,Low,Succ,Pred,Inc,Dec,IntToStr和IntToHex则完全支持Int64参数。Round,Trunc,StrToInt64,和StrToInt64Def函数可以返回Int64类型的结果。
(2)字符类型(Char)
字符类型中Char类型设计来只存储一个字符。一个字符占一个字节,因此Char数据类型可以存储256个不同的字符,其对应的整数为0到255。
除了Char数据类型外,Delphi还提供了Char类型的扩展,即AnsiChar和WideChar型。表6-4是字符数据类型的列表。
表6-4 字符整数类型
字符类型 占用字节数 存储内容
AnsiChar 1 存储一个Ansi字符。
WideChar 2 存储一个UniCode字符。
Char 1 目前,对应AnsiChar。但Delphi将来的版本可能对应于WideChar。
Ansi字符集是扩展的ASCII字符集,仍然占一个字节。目前,Char对应AnsiChar,但Borland公司在将来的Delphi版本中可能使Char对应WideChar。
WideChar用来支持泛字符集(Unicode)。Unicode字符占用两个字节,可以有65536种不同的取值,可以表达现代计算机中使用的世界上所有的字符,包括图形符号和用于出版业的特殊符号等。
UniCode字符集的前256个字符对应着ANSI字符。如果你把一个AnsiChar字符放到WideChar字符类型的变量中,WideChar字符类型变量的高字节将全部置为0,AnsiChar字符存放到WideChar字符类型的变量的低字节中。
注意:Windows NT全面支持Unicode字符号集,但Windows 95却不同。如果你希望书写的程序同时能在两种系统上运行,必须使用SizeOf()函数,以确定字符占多少字节。
(3)布尔类型(Boolean)
Boolean数据类型的变量只存储一个逻辑值,例如True或False。共有4种Boolean数据类型,见表6-5。
表6-5 布尔类型
类型 说明
Boolean 占1个字节
ByteBool 占1个字节
WordBool 占2个字节
LongBool 占4个字节
Delphi提供多种Boolean数据类型的目的是为了兼容,因为在某些情况下,Windows需要用一个字(2个字节)或双字(4个字节)来表示一个布尔值。
(4)枚举型
所谓枚举类型,就是用一组数量有限的标识符来表示一组连续的整数常数,在类型定义时就列出该类型可能具有的值。枚举类型是一种用户自定义的简单数据类型。在类型定义时就列出该类型可能具有的值。下面是枚举类型定义的一些例子:type
TDays=(Monday,YuesDay,Wednesday,Thursday,Friday,Saturday,Sunday);
TPrimaryColor=(Red,Yelloow,Blue); TDepartment=(Finance,Personnel,Engineering,Marketing,MIS); TDog=(Poodle,GoldenRetriever,Dachshund,NorwegianElkhound,Beagle);
枚举类型定义中的每个值都对应一个整数,整数值由该值在类型定义表中的位置决定,通常类型定义的第一个数对应的整数值为0。例如,在TDay类型定义中Monday对应值为0、Tuesday值为1,等等。如果你把DayOfWeek定义为Integer,通过赋整数值来代表星期几,也可以得到同样的结果。但是,由于枚举类型表达的意思明确、直观、便于记忆,因此使用枚举类型仍有必要。
下面是声明一个枚举类型的语法(图6.2)。
其中标识符列表中的标识符之间用逗号隔开,它列出该类型可能具有的值。
下面是声明一个枚举类型变量的举例:
var DayOfWeek:TDays;
Hue:TPrimaryColor;
Department:TDepartment;
Dogbreed:TDog;
也可以把类型声明和变量声明合二为一,例如:
var DayOfWeek:(Monday,YuesDay,Wednesday,Thursday,Friday,Saturday,Sunday);
在声明枚举类型和枚举变量时,请注意以下几点:
1)枚举的元素只能是标识符,标识符的命名必须符合 Pascal关于标识符的规定,例如下面的声明就是错误的:
type TDays=(0,1,2,3,4,5,6,7);
2)同一个枚举元素不能出现在多个枚举类型中。例如下面的声明就是错误的:
type TColors1=(Red,Blue,Green,White,Black);
TColors2=(Yellow,Lime,Silver,Green);
两个类型变量中都有Green元素,是不允许的。
3)不能直接用枚举类型中的元素参加运算,例如,下面的语句就是错误的:
X:=Red+Blue;
但是,可以用某个枚举类型中的元素对枚举变量赋值,例如,下面的语句:
DayOfWeek:=Monday;
(5)子界型
子界类型是Integer,Boolean,Char及枚举型等称为宿主类型数据的一个子集。当你要限制一个变量的数据范围时,使用子界类型就特别有用。子界类型也是一种用户自定义的简单数据类型。要定义子界类型,必须说明区间的最大值和最小值,下面是子界类型定义的一些例子:
type TCompassRange = 0..360;
TValidLetter ='A'..'F'
TMonthlyIncome=10000..30000;
THours =0..23; TPrecipitation=(Drizzle,Showers,Rain,Downpour,Thunderstorm); {枚举型}
TRain =Drizzle..Downpour; {上面枚举型的子界型}
下面是声明一个子界类型的语法规则(图6.3)。
其中两个常数(称为上界和下界)必须是同一种有序类型,如Integer,Boolean,Char及枚举型等,但不能是Real数据类型。第一个常数必须小于或等于第二个常数。
下面是声明子界类型变量的举例:
var Compass:TCompassRange;
ValidChar:TValidLetter;
在声明子界类型和子界类型变量时,请注意以下几点:
1)上界常数和下界常数必须是同一类型,且都是有序类型。
2)子界类型变量具有宿主类型数据的所有运算特性,但运算的结果必须在范围内。
3)上界常数和下界常数可以是表达式。例如:
const X = 10; Y = 50;
type Color = (Red, Green, Blue);
Scale = X * 2..X * 20;
2.实数类型(Real)
实数类型是带有小数部分的数值,存储实数。有6种不同的Real数据类型,它们在范围、精确度、大小等方面都不相同。见表6-6。
实数类型
数据类型 取值范围 有效位 存储字节
Real48 2.9 x 10^-39 .. 1.7 x 10^38 11..12 6
Single 1.5 x 10^-35 .. 3.4 x 10^38 7..8 4
Double 5.0 x 10^-324 .. 1.7 x 10^308 15..16 8
Extended 3.6 x 10^-4951 .. 1.1 x 10^4932 19..20 10
Comp -2^63+1 .. 2^63 ? 19..20 8
Currency 22337203685477.5808..922337203685477.5807 19..20 8
当前通常使用的Real等价与Double。
6.3.2 字符串类型
Delphi在处理字符串时,提供了多种方式,表6-7是Delphi使用的字符串类型。
表6-7 字符串类型
类型 最大长度 存储的字符 是否以Null结尾
ShortString 255个字符 AnsiChar 否
AnsiString~ 2^31个字符 AnsiChar 是
String 或者255或者~2^31个字符 ANSIChar都可能
WideString ~2^30个字符 WideChar 是
从上表可知,Delphi主要支持两种类型的字符串: ShortString和AnsiString。WideString类似于AnsiString,只是其存储的字符为WideChar。
ShortString数据类型定义的是长度在1到255之间动态长度字符串。像数组一样,单个字符可以通过引用它们的索引进行存取。位于0的字节存储了代表字符串当前所赋值长度的数值(只能通过关闭范围检查才能访问)。ShortString数据类型主要是为了能和Delphi 1.0和Borland Pascal的早期版本兼容。
AnsiString(又称为long String或huge String)数据类型的定义是动态分配的,长度几乎可以无限的(仅受可用内存限制)以NULL结尾的字符串。AnsiString中的字符由AnsiChar数据类型的字符组成。
建议最好使用AnsiString数据类型。这是因为AnsiString数据类型的变量是动态分配的,当把一个更长的字符串放入AnsiString数据类型的变量时,Delphi会从新为该变量申请空间。如果要显式地改变字符串的长度,可以使用SetLength() 函数来分配一块恰当的内存;使用AnsiString数据类型的另外一个优点是,AnsiString字符串是以NULL结尾,即在最后一个字符之后自动加上一个NULL字符表示字符串结束,与操作系统的大多数函数例程兼容,例如Win32 API,从而在调用操作系统函数例程时更加方便,不需要使用StrPCopy()来将以Pascal风格的字符串转换为以NULL结尾的字符串。Delphi VCL构件的所有特性、事件使用AnsiString来传递参数,以简化、统一VCL和API之间的接口。
String既可以是SHortString类型也可以是AnsiString类型,缺省是AnsiString类型。例如,如果你像下面那样定义字符串:
var S: String;// S is an AnsiString
则编译器假定你要创建一个AnsiString数据类型变量。
使用$H编译命令可以改变缺省定义。当在程序中把编译开关$H的状态改为{H-}时,String缺省是ShortString类型;当在程序中把编译开关$H的状态改为{H+}时,String缺省是AnsiString类型。例如:
var {$H-} S1: String; // S1 is a ShortString
{$H+}
S2: String; // S2 is an AnsiString
如果定义中指明了长度(最大为25 5),则String为ShortString。例如:
var S: String[63]; // S是一个 长度为63的ShortString变量。
6.3.3 结构数据类型
结构类型在内存中存储一组相关的数据项,而不是像简单数据类型那样单一的数值。Object Pascal结构类型包括集合类型、数组类型、记录类型、文件类型、类类型、类引用类型、接口类型等。这里,我们只介绍集合类型、数组类型、记录类型和文件类型。类类型、类引用类型和接口类型放在下一章介绍。
1.数组(Array)
数组是一种数据类型数据的有序集合,是代表一定数量具有相同类型变量的一种数据类型。Object Pascal数组可与任何简单数据类型或字符串类型等一起使用。数组可用于声明一个简单变量或作为一个记录类型定义的组成部分。
(1)数组的定义
下面是声明一个数组类型的语法规则(图6.4)。
要声明一个数组变量,要求你提供一个标识符,使用array保留词,在方括号中指定数组的界限,并指定编译器数组将用于存储什么类型,例如:
Var Check:array[1..100] of Integer;
范围标点‘..’用于表示Check是一个有100个整数的数组,这些整数从1到100编号。范围编号是一个子界类型,可以是0,也可以是正数或负数,或者字符,或其它有序类型。
下面是声明数组类型及数组类型变量的举例:
Type TCheck = array [1..100] of Integer;
Var CheckingAccount:TCheck;
上面是先定义数组类型,然后定义数组变量。其实上,也可以同时定义类型、变量,例如:
var Kelvin:array[0..1000] of Temperatures;
TwentiethCentury: array[1901..2000] of Events;
LessThanzeroo: array[-999..-400] of Shortint;
DigiTValues:array ['0'..'9' of Byte;
SecretCode:array[''A'..'Z' of char;
访问数组中的元素很简单,只要在标识符后面的方括号中给出指定的元素的索引号即可。例如:
Check[5]:=12;
J:=9;
Check[J]:=24;
要访问数组中的所有元素,可以使用循环语句。例如 :
For J:=1 to 10 do Check[J]:=0;
(2)多维数组
上面介绍的是一维数组。实际上,数组可以是多维的。例如,如果你想编写一个数组来容纳一张电子表格中的值,那么就可以使用2维数组。下面的例子说明如何使用2维数组定义一个有20行和20列的表格:
Type Ttable = array[1..20,1..20] of Double;
Var BigTable:Ttable;
要将2维数组中的所有数据初始化,可以使用如下语句:
Var Col,Row:Intger;
.
.
.
for Col:=1 to 20 do
for Row:=1 to 20 do
BigTable[Col,Row]:=0.0;
使用多维数组时,要记住的一件事是数组为每维所占据的RAM数都呈幂级数增加。例如:
Aline:Array[1..10] of byte;占用10个字节
AnArea:Array[1..10,1..10] of byte;占用10*10=100个字节
Avloume:Array[1..10,1..10,1..10] of byte;占用10*10*10=1000个字节
(3)字符数组
前面介绍的字符串,实际上就是一个1维字符数组,只不过Pascal对字符串类型作了特殊的准许,你可以把它看作一个整体。字符串类型本质上等同于下列类型:type StringType:array[0..255] of char;但是,虽然你可以把一个字符串看待,但它仍然保持其数组的特性。例如在定义一个字符串类型变量时,你可以说明字符串的大小,就像你定义字符数组的大小一样。下面是几个字符串类型定义:
type MyString:string[15];
BigString:string;
LittleString:string[1];
上面语句定义MyString类型包含15个字符,LittleString包含1个字符,BigString没有说明大小,就取字符串包含字符的最大个数255。然后你可以定义这些类型的变量,就像使用其它类型一样:
Var MyName:MyString;
Letter,Digit:LittleString;
你可以对字符串变量进行赋值: MyName:='Frank P.BorLand';
?因为MyName长度为15,因此只能容纳15个字符。如果执行下面语句: MyName:=Frank P.Borland?则MyName变量中只存有FranK.P.Borlan其余部分被舍弃。
为了取得字符串中的一个字符,可以按如下方法进行:AChar:=MyName[2];
但是,如果索引大于字符串变量的长度,则结果不可知。例如: AChar:=MyName[16];则AChar将被设置为某个不确定的字符,换句话说,就是废字符。
在字符串类型的数组中,字符串的第一个位置[0]包含有字符串的长度,因此数组的实际长度比该字符串长度大1个字节。你可以使用Length函数或下面的代码来得到一个字符串的长度:L:=Ord(String[0]);
(4)数组类型常量
数组类型常量的每个字段都是类型常量,下面是声明数组类型常量的语法规则(图6.5)。
从图中可以知道,一个数组类型常量由括号括起来的类型常量组成,不同类型常量用逗号隔开。
像简单类型常量一样,数组类型常量用来定义一个数组常量,下面是一个例子。
type TStatus = (Active, Passive, Waiting);
TStatusMap = array[TStatus] of string;
const StatStr: TStatusMap = ('Active', 'Passive', 'Waiting');
上面的例子首先定义一个数组TStatusMAp,然后定义一个数组常量StatStr。该数组常量的目的是把TStatus类型的值转化为对应的字符串。下面是数组常量StatStr元素的值:
StatStr[Active] = 'Active'StatStr[Passive] = 'Passive'
StatStr[Waiting] = 'Waiting'
数组常量的元素类型可以是除文件类型以外的任何类型。字符数组类型常量既可以是字符也可以是字符串,例如:
const Digits: array[0..9] of Char = ('0', '1', '2', '3', '4', '5','6', '7', '8', '9');
该数组常量也可以表示为:const Digits: array[0..9] of Char = '0123456789';
初始化字符数组类型常量的字符串长度可以小于数组类型的定义长度,例如:var FileName: array[0..79] of Char = 'TEST.PAS';这时数组余下的字符空间自定置NULL(#0),因此数组也变成了一个以NULL结尾的字符串。
多维数组类型常量的定义采用括号的形式,每一维用括号括起,不同维及不同元素常量之间用逗号隔开。最里面的常量对应最右面的维数。
例如:type TCube = array[0..1, 0..1, 0..1] of Integer;const Maze: TCube = (((0, 1), (2, 3)), ((4, 5), (6, 7)));
Maze常量数组各元素的值为:
Maze[0, 0, 0] = 0
Maze[0, 0, 1] = 1
Maze[0, 1, 0] = 2
Maze[0, 1, 1] = 3
Maze[1, 0, 0] = 4
Maze[1, 0, 1] = 5
Maze[1, 1, 0] = 6
Maze[1, 1, 1] = 7
(5)开放式数组
所谓开放式数组,是指数组作为形参传递给过程或函数时其长度是可变的,这样在调用过程或函数时,可以传递不同长度的数组作为实际参数。
开放式数组在过程或函数中作为形参可以定义为: array of T这里T是数组的元素类型标识符,实际参数必须是T类型的变量或元素类型为T的数组变量。在过程或函数内形参的作用可看作为下面的数组: array[0..N - 1] of T
这里N是实参中元素的个数。实际上实参的上下界被映射到0到 N-1。如果实参是类型T的简单变量,则它被看成为只有类型T元素的数组。
开放数组只能以开放数组参数或一个未定义变量参数的的形式传递到过程或函数。开放数组可以作为数值参数、常数参数或变量参数,并与这些参数具有同样的语法规则。作为形式参数的开放数组不允许整体赋值,只能访问它的元素。并且对元素的赋值不影响实参。
当开放式数组作为数值参数时,编译器将在内存中开辟一块区域存放实参的拷贝,等过程或函数退出后再释放这块区域,这样当实参是个很大的数组时,可能会发生栈溢出的问题。在使用开放数组参数时,可以使用Low函数获得当前最小下标(不过总是为0),使用High函数获得当前最大下标,使用SizeOF函数获得当前数组大小。下面是一个例子,演示了开放式数组的使用。
{定义两个长度不同的数组变量}
Var X1:array[1..10] of Double;
X2:array[1..30] of Double;
{Clear过程对一个Double数组的各元素清0,SUM过程计算一个Double数组的各元素之和。两个过程的参数都是开放式数组。}
procedure Clear(var A: array of Double);
var I: Integer;
begin
for I := 0 to High(A) do
A[I] := 0;
end;
function Sum(const A: array of Double): Double;
var I: Integer; S: Double;
begin
S := 0;
for I := 0 to High(A) do S := S + A[I];
Sum := S;
end;
begin
Clear(X1);
Clear(X2);
Sum(X1);
Sum(X2);
end;
当开放式数组的元素类型为Char时,实参可以是一个字符串常数。例如:
procedure PrintStr(const S: array of Char);
var I: Integer;
begin
for I := 0 to High(S) do
if S[I] <> #0 then Write(S[I])
else Break;
end;
下面是合法的过程调用语句:
PrintStr('Hello world');
PrintStr('A');
(6)动态数组
在Delphi中,除了定义静态数组外,还可以定义动态数组。动态数组只需说明数组的类型信息(包括数组的维数和数组元数的类型),但不需要定义元素的个数。例如:
A: array[1..100] of string;//静态数组
B: array of integer//动态数组
C: array of array of string;//动态数组
这里A是静态数组,B是一维的整数动态数组,C是二维的字符串动态数组。
动态数组没有固定的长度。相反,当为动态数组赋值或使用SetLength过程时,动态数组的内存空间将重新分配。动态数组的定义形式是:
array of baseType
例如: var MyFlexibleArray: array of Real;
定义了一个类型为实数型的一维动态数组。注意,声明语句并没有为MyFlexibleArray分配内存。要为动态数组分配内存,需要调用SetLength过程。例如:
SetLength(MyFlexibleArray, 20);上面语句分配20个实数,标号从0到19。
动态数组的标号是整数类型,标号总是从0开始。使用Length,High和Low函数可以取得有关动态数组的特性。Length函数返回数组中元素的个数。High函数返回数组的最大标号,Low返回0。
2.集合类型
集合类型是Integer,Boolean,Char,枚举型,子界型等类型数据的一个子集。在应用程序中,当要检测一个数是否属于一个特定的集合时,就可以使用集合类型。(1)集合类型的定义下面是声明一个集合类型的语法规则(图6.6)。
其中Set of是保留字,ordinal Type是集合的基类型,可以是任何有序类型如整数型,布尔型,字符型,枚举型和子界型,但不能是实型或其它自定义类型。下面是一些集合类型的例子:
type VoterDataSet= Set Of (Democrat,Republican,Male,Female, LowOpinion,HighOption,Confused);
Chars = Set of Char;
Letters = Set of 'a'..'z';
VIBGYOR= (Violet,Indigo,Blue,Green,Yellow,Orange,Red); {这是枚举型}
ColorSet = set of VOBGYOR;{上面枚举型的集合类型}
一个集合类型的变量的值实际上是它的基类型的一个子集,可以为空集。一个集合最多可有256个元素。因此下面的集合定义是错误的:
type SET1= Set Of Integer;
这是因为Integer集合的元素个数远远大于256。
下面是集合类型变量的一些例子:
var Voter: VoterDataSet;
Color: ColorSet;
Lets:Letters;
Pascal使用方括号来表示集合,例如:
[Democrat];表示只含Democrat的集合。
一个集合可以拥有0个元素,这时称之为空集,用两个方括号表示,其中什么也没有。对于集合类型变量,你可以进行+,-,=,*(并),IN等运算。见下表6-8。
表6-8 集合类型运算
操作符 描述 举例
+ 往一个集合中添加元素 Aset:=Aset+AnotherSet;
- 从一个集合中去除元素 Aset:=Aset-AnotherSet;
* 去除两个集合中都没有的元素 Aset:=Aset*AnotherSet;
In 测试元素 Bool:=AnElement in Aset
下面是集合运算的例子:
Voter:=[HighOption];
Voter:=Voter+[Democrat];
Voter:=Voter+{male};
Voter:=Voter-[HighOption];
If HighOption in Voter then SendtheVoterFlowers;
(2)集合类型
常量像简单类型常量一样,集合类型常量用来定义一组常量的集合。例如:
type TDigits = set of 0..9;
TLetters = set of 'A'..'Z';
const EvenDigits: TDigits = [0, 2, 4, 6, 8];
Vowels: TLetters = ['A', 'E', 'I', 'O', 'U', 'Y'];
上面的例子首先定义两个集合类型TDigits和Tletters,然后定义了两个集合常量,其中EvenDigits的值域是[0, 2, 4, 6, 8],它为TDigits的一个子集;Vowels的值域是 ['A', 'E', 'I', 'O', 'U', 'Y'],它为TLetters的一个子集。
3.记录类型
记录是一系列相关的变量,这些变量被称为域,它们放在一起,作为一个整体使用。例如,一个雇员可能包含姓名、雇用时间、薪金等数据,这时你可以像下面那样定义一个雇员记录类型:
type TEmployee = record LastName: String[20];
FirstName:String[15];
YearHired:1990..2000;
Salary:Double;
Position:string[20];
end;
Pascal的记录类型跟数据库中的记录很相似,记录类型中的元素可以理解为数据库中的字段,事实上Pascal正是借用了数据库中的记录和字段的概念。
(1)记录类型的定义
下面是声明记录类型的语法规则
记录可以一个字段也没有,即为空记录;一个记录可以有一个固定部分(fixed part),在固定部分,每个字段都有其确定的标识符和数据类型,它们在内存中分别占用不同的区域;一个记录也可以加入一个可变部分(variantpart)。声明记录变量与声明其它类型变量一样,下面是两个记录变量的说明:
var NewEmployee,PromotedEmployee:TEmployee;
记录类型中的每个域都有一种数据类型,你既可以单独访问这些域,也可以把记录作为一个整体来使用。例如,你可以像下面那样访问NewEmployee记录中的Salary域或整个记录:
NewEmployee.Salary:=43211.00;
PromotedEmployee:=NewEmployee;
当你要访问记录内的域时,需要指定记录名,并在记录名后加(.),然后跟上域名。例如:
PromotedEmployee.Position
如果要对多个域赋值,则每个域前都必须加记录名。例如:
PromotedEmployee.LastName :='Gates'
PromotedEmployee.FirstName:='Bill'
PromotedEmployee.YearHired:=1990;
PromotedEmployee.Salary:=92339.00;
PromotedEmployee.Position:='Manager'
?Pascal提供了With语句,使你可以减少重复书写记录名的烦恼。With语句的语法是: With记录变量名Do ...
每个可变部分由至少由一个常量(Constant)标识,所有常量必须是唯一的,并且类型为与tag field type指定类型相容的类型。Identifier用于记录可变部分的可选部分,称为识别字段标识符。如果定义了识别字段标识符,程序可以使用该标识符决定在给定的时间内哪个可变部分是活动的,如果没有定义识别字段标识符,程序必须根据其它规则选定记录可变部分。
下面是带有可变部分的记录类型的例子:
type TPolygon = record X, Y: Real;
case Kind: Figure of
TRectangle: (Height, Width: Real);
TTriangle: (Side1, Side2, Angle: Real);
TCircle: (Radius: Real);
End;
注意:记录可变部分的字段不能是长字符串类型和变体类型,也不能含有长字符串类型和变体类型分量的构造类型。
(2)记录类型常量
记录常量的每个字段都是类型常量,
一个记录类型常量每个字段由一个标识符和类型常量组成,不同字段用分号隔开,字段部分用括号括起。像简单类型常量一样,记录类型常量用来定义一个记录常量,下面是一些例子。
type TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
TDate = record D: 1..31;
M: Month;
Y: 1900..1999;
end;
const
SomeDay: TDate = (D: 2; M: Dec; Y: 1960);
上面的例子首先定义一个记录类型TDate,然后定义了一个记录常量SomeDay。注意:记录类型常量中个字段的出现顺序必须与记录类型定义中的顺序一致。如果记录类型包括文件类型字段,则不能定义该记录的记录常量。如果记录类型包括可变部分,则只有被选择的可变部分可以定义常量。
4.文件类型
文件是指相同类型元素的有序集合。Delphi处理文件有三种方式,一种是使用Object Pascal标准的文件处理技术;一种是使用Windows的文件处理函数;还有一种是使用文件流对象。
Object Pascal标准的文件处理技术,有专门的数据类型和文件处理例程,并且与Windows的标准API不兼容,但对于熟悉Pascal的用户来说,仍然是操作文件的好选择。下面我们就对此进行介绍。
声明一个文件类型的语法如下: type fileTypeName = file of type
这里,fileTypeName是任何有效的标识符, type是一种大小固定的数据类型,称之为基类型。 基类型不能使用指针类型,不能包括动态数组、长字符串、类、对象、指针、可变类型以及其它文件类型。但可以是其它结构类型,例如:
type
PhoneEntry = record FirstName, LastName: string[20];
PhoneNumber: string[15];
Listed: Boolean;
end;
PhoneList = file of PhoneEntry;
这里,PhoneEntry是自定义的结构类型,PhoneList是以PhoneEntry为基类型的文件类型。在定义了文件类型后,就可以直接定义文件类型的变量了。例如:
var List1: PhoneList;
有时侯,我们也可以使用file of基类型的方式直接定义文件类型变量。例如下面的定义与上面的形式有同样的效果:
var List1: file of PhoneEntry;
如果我们在声明文件类型时不指明其基类型。则这样的文件我们称之为无类型文件,如:
var DataFile: file;
无类型文件主要用于直接访问磁盘文件的多层I/O操作。6.3.4 指针类型指针类型对程序员来说可能是最复杂和最灵活的数据类型。当你在Delphi中创建一个数据结构时,首先要分配内存空间。分配的内存空间用于存储数据结构中的数据。而指针就是指向分配空间的内存地址。使用指针,可以使程序不必每次需要时都去分配,只要申请一次即可,其它过程或函数使用同一块内存空间时,只要使用该内存空间的地址。例如,假设你的一个邻居问你怎样去百货店,你并不需要把整个百货店搬到邻居家里,只需要告诉它去百货店的路径即可,这个路径类似于一个指针。
1.指针类型的声明
声明指针类型的语法规则见图6.10。
其中基类型可以是简单类型,也可以是前面介绍的结构,或只是一个标识符。如果基类型是一个未定义的类型标识符的话,则该类型标识符必须在同一块内声明。
下面是带有指针类型声明的例子:
type WordPtr =^Word;
RealPtr =^Real;
PersonType=Record LAstNAme:String;
FirstNAme:String; Age:Integer;
end;
PersonPointer = ^PersonType;
上例中,声明了三个指针类型,一个是WordPtr,指向^Word,一个是RealPtr,指向^Real,还有一个是PersonPointer,指向一个标识符,而该标识符标识一个记录类型。
声明了指针类型之后,就可以声明指针类型变量,
例如:Var WP:WordPtr;
RP:RealPtr;
Person:PersonPointer;
2.指针的使用
Delphi提供专门的过程和函数操作指针,这些过程和函数是:New过程,@操作符,PTR函数,GetMem过程。下面分别介绍。
(1)New过程
New过程是 Pascal中的标准例程(在System单元声明),用于在应用程序堆栈中为动态变量申请一块区域,并把该区域的地址赋予指针变量。New过程的语法为:
procedure New(var P: Pointer);
其中P是一个指针变量。所分配区域的大小由指针变量P的基类型决定。如果在应用程序堆栈中没有足够的内存空间供分配,将触发EOutOfMemory异常。
新分配的内存空间由P指向,P^即为类型的动态变量。应用程序不再需要该动态变量时,可以调用Dispose标准例程释放为该变量分配的内存空间。
下面是使用New过程的举例:
type PListEntry = ^TListEntry;
TListEntry = record
Next: PListEntry;
Text: string;
Count: Integer;
end;
var List, P: PListEntry;
begin
...
New(P);
P^.Next := List;
P^.Text := 'Hello world';
P^.Count := 1;
List := P;
Dispose(P);
...
end;
上例中,声明了一个指针类型 PListEntry,指向标识符 TListEntry,而该标识符标识一个记录类型 TlistEntry,然后定义了两个指针变量List和P。程序首先用New过程在应用程序堆栈中为动态变量申请一块区域,并把该区域的地址赋予指针变量P。P^即为记录类型TListEntry的动态变量。不再需要该动态变量后时,调用Dispose释放为该变量分配的内存空间。
(2)@操作符
@操作符是个一元操作符,用于获得操作数的地址,其使用语法见图6.11。
从图中可以知道,@后面的操作数可以是变量、过程、函数或类类型中的方法。
程序示例如下:
procedure ChangeValue(X:Integer)
Var IntPtr:^Integer;
begin
IntPtr:=@X;
Writeln(IntPtr^);
IntPtr^:=20;
end;
假设主程序如下:
begin
Param:=10;
ChangeValue(param);
Writeln(Param); {10}
end;
上例中,ChangeVAlue过程首先声明了一个指向整型数的指针Ptr,然后用@操作符取出X的地址赋予IntPtr指针,并显示Ptr指针指向的数,最后改变这个数。
(3)PTR函数
Ptr函数是 Pascal中的标准例程(在System单元声明),用于把一个指定的地址转换为指针。Ptr函数的语法为:
function Ptr(Address: Integer): Pointer;
其中Address是一个整数,用于表示一个32位地址,函数执行的结果是把32位地址转化为指针。
(4)GetMem过程
GetMem过程也是Pascal中的标准例程(在System单元声明),类似于New,用于在应用程序堆栈中为动态变量申请一块指定大小的区域,并把该区域的地址赋予指针变量。GetMem函数的语法为:
procedure GetMem(var P: Pointer; Size: Integer);
其中P是一个指针变量,Size指定区域的字节数。
所分配区域的大小由指针变量P的基类型决定。如果在应用程序堆栈中没有足够的内存空间供分配,将触发EOutOfMemory异常。如果程序不再需要该动态变量时,可以调用FreeMem标准例程释放为该变量分配的内存空间。
程序示例如下:
Var F: file;
Size: Integer;
Buffer: PChar;
begin
AssignFile(F, 'test.txt');
Reset(F, 1);
try
Size := FileSize(F);
GetMem(Buffer, Size);
try
BlockRead(F, Buffer^, Size);
ProcessFile(Buffer, Size);
finally
FreeMem(Buffer);
end;
finally
CloseFile(F);
end;
end;
上例打开一个名字为test.txt的文件,并把文件读入动态分配的缓冲区,缓冲区大小为文件的大小,然后对文件进行处理,最后释放动态分配的缓冲区,并关闭文件。
Pascal中有一个特殊的保留字nil,这是一个空指针常量,当指针的值为nil时,表示指针当前没有指向任何动态变量。值为nil的指针变量不能访问动态变量。
3.无类型指针
无类型的指针是指指针变量在声明时没有指明基类型。无类型指针在声明中只使用Pointer。例如:
var pAnyPoint:Pointer;
指针pAnyPoint可以指向任何变量类型。无类型的指针的作用是它可以指向任何类型,但是,不能用指针变量符后加^的形式来引用它的动态变量。
4.字符指针类型
字符指针类型即PChar数据类型,是一个指向以NULL(不是零)字符结尾的字符(Char)串的指针。这种类型主要用于与外部函数如在Windows API中所用的函数兼容。与Pascal字符串不同,Windows和C字符串没有一个长度字节。取而代之的是它们从0字节索引开始,以一个NULL(#0)结束。Pascal RTL字符函数根据长度决定存储在字符串变量中的字符数目。C函数实际上一次搜索字符数组的一个字符,直到碰到NULL,表示字符串结尾。在Windows API中所用的许多函数以指向NULL结束字符串或用NULL结束填入缓冲区的字符。在Pascal中使用这些函数就需要PChar类型变量。内存将分配给变量并被所需函数使用。
除了PChar外,Delphi还包含PAnsiChar和PWideChar数据类型。
PAnsiChar数据类型是一个指向以NULL(不是零)字符结尾的AnsiChar字符串的指针,在Delphi中,PCHAR等同于PAnsiChar。
?PWideChar数据类型是一个指向以NULL(不是零)字符结尾的WideChar字符串的指针,用于UniCode字符集。实际上,PAnsiChar和PWideChar数据类型的定义为:
type
PAnsiChar = ^AnsiChar;
PWideChar = ^WideChar;
PChar = PAnsiChar;
字符串类型与PCHAR类型赋值兼容,即一个字符串可以直接赋给一个PCHAR类型的变量,例如:
var P: PChar;
...
begin
P := 'Hello world...';
end;
上面赋值语句首先申请一块区域,该区域包含字符串 'Hello world...',并在最后加上NULL,然后P指向这块内存区。上述例子等价于下列形式:
const TempString: array[0..14] of Char = 'Hello world...'#0;
var P: PChar;
...
begin
P := @TempString;
end;
6.3.4 过程类型
Object Pascal允许把过程和函数作为一个整体赋给变量和作为参数传递。实现这一功能的途径是使用Object Pascal的过程类型。
声明一个过程类型的语法与声明过程或函数的首部的语法相似,不同的是声明一个过程类型时不需要过程或函数保留字后面的标识符。声明过程类型时可以指定一种调用约定方式,缺省的调用方式是Register。下面是声明过程类型的举例:
type
TProcedure = procedure;
TStrProc = procedure(const S: string);
TMathFunc = function(X: Double): Double;
上例声明的三个过程类型中,第一个是不带任何参数的过程,第二个是带一个参数S的过程,第三个是带一个参数X的函数,函数返回值为Double。
过程类型根据其是否运用于对象分为两类:全局过程指针和方法指针。
声明过程类型时不带of Object的是全局过程指针。全局过程指针指向的是全局的过程或函数。例如上面的过程类型Tprocedure,TstrProc,TMathFunc都是全局过程指针。
声明过程类型时带有of Object的是方法指针。方法指针指向的是一个对象的过程或函数方法。例如下面的过程类型是方法指针。
type TMethod = procedure of object;
TNotifyEvent = procedure(Sender: TObject) of object;
声明过程类型变量的方法与声明其它类型变量的方法相同,下面例子声明两个过程类型变量:
var Proc:TProcedure;
StrProc:TStrProc;
过程类型变量的值可以取下列四种之一:
nil一个过程类型变量
一个全局过程或函数标识符一个方法指示符下面举例说明过程类型的用法。
type TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
...
end;
var MainForm: TMainForm;
MathFunc: TMathFunc;
OnClick: TNotifyEvent;
function Tan(Angle: Double): Double;
begin
Result := Sin(Angle) / Cos(Angle);
end;
上例的TMainForm是一个类类型,TMathFunc是前面定义的全局过程指针,TnotifyEvent是前面定义的方法指针。其中MathFunc和OnClick是两个过程类型变量。变量MathFunc和OnClick的赋值方式为:
MathFunc := Tan;OnClick := MainForm.ButtonClick;
调用结果为:
X := MathFunc(X);{等价于 X := Tan(X) }
OnClick(Self);{等价于 MainForm.ButtonClick(Self) }
过程类型变量值等于NIL表示该过程类型变量没有赋值,因此在过程语句或函数调用中使用值等于NIL的过程类型变量将发生错误。防止的办法是使用Assigned()函数。例如:
if Assigned(OnClick) then OnClick(Self);
如果给定的过程类型变量已经赋值,Assigned函数返回TRUE,如果给定的过程类型变量值为NIL,Assigned函数返回FALSE。在把一个过程或函数赋给一个过程类型变量时要注意赋值兼容,必须满足下列条件:调用约定方式必须相同。?参数个数必须相同,相应的数据类型必须相同。?函数返回的值类型必须相同。
6.3.5 Variant数据类型
Variant主要用于表达需要动态改变类型的数据。例如,当一个数据的实际类型在编译时不知道或运行时需要改变类型时,就可以使用Variant类型。
Variant类型变量可以包含integer, real, string, boolean, 日期和时间等类型值或以及 OLE自动化对象等,还可以表示长度和维数可变的数组。
Variant变量在首次创建时,总是被初始化为Unassigned。Unassigned是Variant变量的一个特殊值,表明Variant变量还未赋值,Variant变量的另一个特殊值是NULL,指示Variant变量未知或丢失数据。
6.4 数据类型的转换
Object Pascal是一种类型严谨的程序设计语言,不是所有类型的数据都可以互相赋值的。只有赋值两边的数据类型一致或兼容才可以进行赋值操作。下面就有关数据类型兼容和强制数据类型转换等概念进行介绍。
6.4.1 类型兼容
所闻类型兼容,是指一种类型的数据可以与另一种类型的数据进行关系运算。类型兼容是赋值兼容的前提条件,也是Object Pascal数据运算的基本前提。
?Object Pascal规定,只有满足下列条件才是类型兼容:
?两种类型都一致。
?两种类型都是实型。
?两种类型都是整型。
?一种类型是另一种类型的子界。
?两种类型都是另一种宿主类型的子界。
?两种类型都是另一种兼容基类型的集合类型。
?两种类型都是紧凑字符串类型,并具有相同的元素个数。
?一种类型是字符串类型,另一种类型是字符串类型、紧凑字符串类型或字符类型。
?一种类型是Pointer类型,另一种类型是任意的指针类型。
?两种类型都是类类型或,类引用类型,并且一种类型继承了另一种类型。
?一种类型是PChar类型,另一种类型是形式为array[0..X] of Char的字符数组。
?两种类型都是基类型相同的指针类型(编译开关$T设置为{$T+})。
?两种类型都是结果类型相同、参数个数相同、参数类型一致的过程类型。
?一种类型是Variant类型,另一种类型是整型、实型、字符串类型或布尔类型。
当两个类型要进行关系运算操作而又不满足类型兼容时,将产生编译错误。
6.4.2 赋值兼容
类型兼容仅仅可以进行关系运算,只有赋值兼容的变量才可以赋值或进行参数传递。
类型T2的值与类型T1的值赋值兼容是指T1和T2允许赋值操作,即:
T1:=T2;
Object Pascal规定,类型T1的值与类型T2的值赋值兼容必须有满足下列条件:
?T1和 T2类型相同,并且都不是文件类型或包含文件类型的自定义类型。
?T1是T2是兼容的有序类型,类型T2的值在类型类型T1的取值范围内。
?T1和 T2都是实型, 类型T2的值在类型T1的取值范围内。
?T1是实型,T2是整数型。
?T1和 T2都是字符串类型。
?T1是字符串类型,T2是字符类型。
?T1是字符串类型,T2是紧凑的字符串类型。
?T1是长字符串类型,T2是PChar类型。
?T1和T2是兼容的、紧凑的字符串类型。
?T1和T2是兼容的、集合类型。 类型T2的所有成员在类型T1的取值范围内。
?类型T2在类型T1的取值范围内。
?T1和T2是兼容的指针类型。
?T1是类类型,T2是T1的继承类类型。
?T1是类引用类型,T2是T1的继承类引用类型。
?T1是PChar类型,T2是字符串常量。
?T1是PChar类型,T2是形式为array[0..X] of Char的字符数组。
?T1和T2是兼容的过程类型。
?T1是过程类型,T2是具有与T1嗤峁嘈拖嗤⒉问鍪嗤⒉问嘈鸵恢碌墓袒蚝?br> ?T1是Variant类型,T2是Integer,real,string或boolean类型。
?T1是Integer,real,string或boolean类型,T2是Variant类型。当两个类型要进行赋值操作而又不满足赋值兼容时,将产生编译错误。
6.4.3 变量强制类型转换
变量强制类型转换就是强制将一种类型变量转换为另一种类型的变量。程序员自己确定强制类型转换的合法性。
图6.13是变量强制类型转换的语法规则。
当变量强制类型转换应用于一个变量时,该变量就被视为由类型标识符说明的类型。变量的大小必须与类型标识符说明的类型的大小相同。变量之前可以放置一个或多个类型允许的限定符。
Word类型的变量W转换为TByteRec, TWordRec(L)将一个LongInt类型的变量L转换为TWordRec类型,而PByte(L)则将LongInt类型变量L转换为指针类型Pbyte。
6.4.4 数值强制类型转换
数值强制类型转换就是强制将数值(或表达式)从一种类型转换为另一种类型。
其中表达式类型必须是有序类型或指针类型。 在转换中,如果结果类型的大小不同于表达式类型的大小,则有可能造成数据的截止或扩展。下面举例说明数值强制类型转换的用法。
Integer('A')//把字符A转换为一个整数。
Char(48)//把数字48转换为一个字符。
Boolean(0)//把数字0转换为一个布尔值。
Longint(@Buffer)//把指针转换为一长整数。
Int64(I)//把一个整数转换为64位整数
6.5 数据类型运算符
操作符 操作 操作数据类型 结果数据类型
DIV 整数除 integer integer
mod 余数除 integer integer
mod运算符的结果是两个操作数相除后的余数的整数部分。
shl 按位左移 integer Boolean
shr 按位右移
in 属于
6.5.4 运算符的优先级
运算符 优先级 分类
@, not 1 (最高) 一元运算符
*, /, div, mod, and, shl, shr, as 2 乘法运算符
+,-, or, xor 3 加法运算符
=, <>, <, >, <=, >=, in, is 4 (最低) 关系运算符
6.6 语 句
这些例子都是DOS窗口方式的,而不是通常的Windows应用程序。如果读者要调试这些程序,需要修改Delphi的一些缺省设置。其步骤是:
(1)开始Delphi。
(2)如果当前不是自动打开一个新项目 ,选择File|New命令开始一个新项目 。
(3)选择Project|Options|Linker命令,使能Generate Console Application核对框,从而是Delphi创建的程序是DOS窗口方式的,而不是通常的Windows应用程序。
(4)选择View|Project Source命令,进入代码编辑器编辑项目 文件代码,键入本书提供的例子。
(5)运行程序,程序将在它自己的DOS窗口运行。要关闭DOS窗口,选择Alt+F4或单击窗口的右上角单击X。
6.6.1 赋值语句
6.6.2 块语句
1.ASM/END块语句
ASM块语句在Pascal中嵌入汇编语言代码。由于Delphi Pascal对计算机资源提供了很好的支持,因此,除非特别需要,一般不需要使用汇编语句。
2.BEGIN/END块语句
6.6.3 Delphi控制语句
Object Pascal使用控制语句来控制程序的执行顺序。7个是分支语句,3个是循环语句:
.分支语句
. if 语句
. case语句
CASE Choice of
'1': EvaluateChoice;
'2': Initialize
ELSE Writeln('Bad Choice,Try Again.');
END;
请注意,CASE语句的常量范围不能重叠。Else要放在所有判断语句之后.
. GOTO语句
GOTO语句强行将程序转向一个指定的点执行。该指定点用一个标号标识。
. Break语句
. Continue语句
. Exit语句
EXIT语句的功能是退出当前的代码块。如果代码块是主程序,EXIT语句导致程序的终止;如果当前块是嵌套的,EXIT语句跳到外一层嵌套继续执行。如果当前块是过程或函数,EXIT语句导致过程或函数执行终止,跳到调用过程或函数的语句的下一条语句执行。
. Halt语句
HALT语句导致程序的非正常结束,并返回到操作系统。通常是在程序遇到致命错误时才使用HALT语句。HALT语句后可跟一个整数代码HALT(1),以指定错误的原因。
. 循环语句
. Repeat/Until语句
REPEAT
Key:=GetChar;
Writeln('Key IS',key);
UNTIL Key=$D;
. While语句
WHILE key<>$D DO key:=GetChar;
. for语句
for V := Expr1 to Expr2 do Body; 可使用DOWNTO;在循环体中,如果不想执行循环下面的语句,而直接进入下一次循环,可以使用Continue语句.
要退出循环,跳到FOR/DO语句下面的语句执行,可以使用break语句;如果不想执行循环下面的语句,而要求直接进入下一个循环,可以使Continue语句。
6.7 过程与函数
6.7.1 过程的定义和调用
要定义和调用一个过程,首先要在程序的TYPE区声明一个过程.
下面是一个过程声明的例子:
procedure NumString(N: Integer; var S: string);
过程声明之后,就应当在Implementation区定义这个过程,定义的规则如下。过程:
Procedure
()
BEGIN
END;
6.7.2 函数的定义和调用
函数的定义和调用与过程的定义和调用类似,不同的是函数的首部,函数的首部多了一个返回结果类型。
function Max(A: Vector; N: Integer): Extended;
Max函数返回类型为Extended。函数声明之后,就应当在Implementation区定义这个函数。
6.7.3 返回值
(1)返回值直接送给函数名。
(2)返回值送给Delphi的一个内置变量Result。
如果你写的函数有可能移植到其它Pascal编译器中使用,最好使用第一种方式。
6.7.4 调用约定
从前面说明的过程和函数的语法规则我们知道,在声明过程或函数时,可以在附属块指定过程或函数的参数的传递方式。Pascal共提供了五种传递方式,分别为Register,Pascal,Cdecl,Stdcall,SafeCall。缺省的调用方式是Register方式。如果一个过程或函数没有指定过程或函数的调用方式,就采用Register调用方式。
调用方式的语法示例如下:
function Max(A: Vector; N: Integer): Extended;Stdcall;
Object Pascal调用方式的区别于以下几点:
(1)传递参数的顺序
Register和 Pascal调用方式传递参数是从左到右,即最左边的参数先产生并首先传递,最右边的参数最后产生并最后传递。而Cdecl, Stdcall和 Safecall 调用方式传递参数则是从右到左。
(2)堆栈中删除参数
使用Pascal、Stdcall和Safecall调用方式的过程或函数在返回时程序自动删除堆栈中的参数,而Cdecl调用方式必须在程序返回时调用者自己删除堆栈中的参数。
(3)使用寄存器传递参数
Register调用方式使用三个CPU寄存器来传递参数,而其它调用方式使用堆栈来传递参数。
Register调用方式通常是最快的参数传递方式,因为它不需要创建栈帧。Pascal和 Cdecl调用方式通常用于调用用C,C++或其它语言书写的动态链接库程序。Stdcall 调用方式通常用于Windows API程序。而Safecall调用方式通常用于实现OLE自动化编程的双接口(Dual interfaces)。
6.7.5 指示字
在声明过程或函数时,可以在附属块使用指示字以进一步指定过程或函数的产生方式。Delphi过程或函数分别提供了Block,External,Asm,Forward。指定调用方式的语法示例如下:
procedure MoveWord(var Source, Dest; Count: Integer); external;
其中Block是缺省方式,表示过程或函数的语句部分是 Pascal程序快,下面对External,Assembler,Forward进行介绍。
1.External
该指示字表示过程或函数是外部的,通常用于从动态链接库中引入过程或函数。External后可以动态链接库名或表示动态链接库的有序数,也可以指定引入的过程或函数名。例如:
function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';
上例中,user32.dll指定用于引入过程或函数的动态链接库名(也可以是一个有序数),MessageBox指定从动态链接库中引入过程或函数名。
(2)Assembler
该指示字表示过程或函数是使用嵌入式汇编语言编写的。例如函数声明:
function LongMul(X, Y: Integer): Longint;Assembler
其定义为:
function LongMul(X, Y: Integer): Longint;
(3)Forward
该指示字表示一个过程或函数是向前查找的。在声明了一个过程或函数是向前查找的之后,该过程或函数的定义必须在后面的某个地方定义。
procedure Walter(M, N: Integer); forward;
procedure Clara(X, Y: Real);
begin
...
Walter(4, 5);
end;
procedure Walter;
begin
...
MessageBeep(0);
end;
注意:不能在单元的interface部分声明向前查找过程。在使用向前查找过程时,要注意相互递归。
6.7.6 参数
当调用过程或函数时,常常需要使用参数传递数据给被调用的过程或函数。在某种程度上,使过程、函数更有用更灵活的方法就是使用参数。
在Pascal中,调用过程或函数使用的参数称为实参,被调用过程或函数使用的参数称为形参,例如,下面语句中,Edit1是实参:
ColorIt(Edit1);
下面的AnEditBox是形参:
Procedure ColorIt(AnEditBox:Tedit);
Delphi传递参数的方式有四种:
(1)传值(Passing By Value)。
变量和结构被完整地拷贝到堆栈中,而不是通过机器的寄存器。通过值传递参数可以防止调用的函数修改原来的参数,因为调用的函数接收到的只不过是参数的一个副本。例如:
procedure Tform1.Button1Click(Sender:Tobject);
var Number:Integer;
begin
Number:=StrToInt(Edit1.text);
Calculate(Number);
Edit2.Text:=IntToStr(Number);
end;
Procedure Calculate(CalcNo:Integer);
begin
CalcNo:=CalcNo*10;
end;
在Calculate过程中,CalcNo参数按值传递,执行该过程后,CalcNo的值扩大了十倍。但是,调用过程Tform1.Button1Click中Number并没有改变,因此Edit1编辑框与Edit2编辑框的值一样。
(2)传引用(Passing By Reference)。
传递一个指向参数的引用(指针),按规则引用可用作指针和值。改变引用传递的参数要影响调用源参数的拷贝。
使用传引用必须在参数前加上Var保留字。例如,把Calculate改写如下:
procedure Calculate(Var CalcNo:Integer);
begin
CalcNo:=CalcNo*10;
end;
修改后,Calculate过程的CalcNo参数为按引用传递,执行该过程后,CalcNo的值扩大了十倍,同时,调用过程Tform1.Button1Click中Number也作了改变,因此Edit2编辑框的值是Edit1编辑框值的10倍。
(3)常量传递(Constant Parameters)。
如果过程或函数运行时,形参的值永远都不会改变,就可以考虑使用常数参数。要使一个参数为常数参数,只要在参数前加上Const保留字。例如:
function TDirectoryOutline.ForceCase(const AString: string): string;
begin
if Assigned(FCaseFunction) then
Result := FCaseFunction(AString)
else
Result := AString;
end;
当你不需要参数改变时,可以使用常数参数防止偶然对该参数的修改。如果程序某个地方对常数参数进行了修改,你将会得到一个非法变量引用错误信息。
(4)默认参数
在Delphi中,可以为过程和函数定义默认参数。默认参数仅仅显示在参数列表的尾部,其形式是:
参数名: 类型 = 值
当调用包括默认参数的过程或函数时,默认参数的值可以省去。例如下面是一个函数的定义:
procedure FillArray(A: array of Integer; Value: Integer = 0);
下面是两个合法的调用语句:
FillArray(MyArray, 1);//直接传递值
FillArray(MyArray);//使用默认参数