单元 程序头:uses(子句列出了那些链接到程序的单元),block(声明,命令语句)
一个程序包括:
• 一个程序头(program heading)
• 一个uses 子句(可选),和
• 一个包含声明和命令语句的块(block)
一个单元以单元头(unit heading)开始,后面跟interface、implementation、initialization
和finalization 部分,后面两个部分是可选的
一个uses 子句可以出现在:
• 程序或库的工程文件
• 单元的接口部分,和
• 单元的实现部分
在单元声明以及uses 子句中(尤其是在Linux 下),单元名称必须和文件名大小写一致。在其它情
况(比如使用限定符的标志符),单元名是大小写无关的。要避免在单元引用中出现问题,要明确指出单元文件:
uses MyUnit in "myunit.pas";
如果像上面这样在工程文件中明确引用一个单元,在其它源文件中就可以像下面那样简单地引用它,
而不必考虑大小写问题:
uses Myunit;
uses 子句给编译器提供各模块间的依赖信息,因为这些信息是存于模块自身的,所以,Object Pascal
程序不需要makefile 文件、头文件以及include 预处理指令(这些概念你可能要参考C 语言)。每当一个
工程调入IDE 时,Project Manager 创建一个makefile,但只有在包含多个工程的工程组中才保存这些文
件。
• 单元源文件 ( .pas)
• 工程文件 ( .dpr)
• 包源文件 ( .dpk)
• 窗体文件,扩展名为 .dfm(Delphi)或 .xfm(Kylix)
• 资源文件,扩展名为 .res (已经编译的资源文件)
• 工程选项文件,扩展名为 .dof(Delphi)或 .kof(Kylix)
要改变程序的图标,可使用Project Options 对话框。
事件句柄通过窗体文件(Windows 下是 .dfm,Linux 下是 .xfm)赋给指定的事件
空格(#32)和控制符(#0 到#31,其中#13 叫回车符或行的结束符)被称为空白符(blank)。
特殊符号是非文字和数字字符,或这类字符的组合,它们有固定的意义。
‘[’相当于‘(.’,‘]’相当于‘.)’;‘(*’和‘*)’分别相当于‘{’和‘}’(表示注释)。
,!(惊叹号)、 ”(双引号)、 %(百分号)、 ?(问号)、 /(反斜杠)、 _(下划线)、 |(通道)和 ~(破折
号)不是特殊符号
$前缀表示一个16 进制数字,比如$8F
标签是一个不超过4 位的阿拉伯数字序列,也就是从0 到9999,打头的0 没有意义
。若单引号中没有内容(’’),它被称为空串(null string)。在一个引用串中,两个连续的单引号(’’)表示一个字 符,也就是单引号本身(’)控制串由一个或多个控制字符(控制符)所组成,每个控制符包含一个#,后跟一个无符号整数(10
进制或16 进制),整数的范围从0 到255,表示相应的ASCII 字符。下面的控制串
#89#111#117
就相当于引用串
’You’
运算符包括:@ not ^ * / div mod and shl shr as + - or xor = > < <> <= >= in 和 is。
一些运算符的行为因为传给它们的数据类型不同而不同。比如,not 运算符用于整数时,是对它的
位进行求反运算,而对布尔类型进行逻辑非运算。
除了 ^ ,is 和 in,其它运算可应用在Variant 类型上。
• 不管x 和y 的类型是什么,x/y 的结果总是扩展类型(extended);对其它运算符,只要有一个
运算数是实数类型,它的结果就是扩展类型;另外,只要有一个运算数是Int64 类型,它的结果就是Int64 类型;否则
,结果就是整数类型。如果一个运算数是整数的子界类型,它就像整数类型一样被对待。
• x div y 的值取整数,也就是取得x/y 的值,然后以0 的方向取得最近的整数。
• mod 运算返回对运算数进行整数除后得到的余数。换句话说,就是x mod y = x - (x div y)*y。
布尔运算符not、and、or 和xor 作用于任何布尔类型的运算数,并返回一个布尔类型的值。
使用 $B 编译器指示字控制计算方式,默认状态是 {$B-},它采用部分计算。要在局部进行完全计
算,在代码中使用 {$B+} 指示字。你也可以在Compiler Options 对话框中选择Complete Boolean
Evaluation 选项,此时在整个项目范围使用完全计算。
若任何一个运算数是variant 类型,编译器总是进行完全计算(即使在 {$B-} 状态下)
Logical (bitwise) operators(位逻辑运算符):not and or xor shl shr
• 位反(not)运算的返回类型和运算数相同;
• 若and、or 或xor 的运算数都是整数类型,则它的返回类型是包含运算数所有可能的值、且范围最小的预定义(内置)整数类型;• 运算 x shl y 和 x shr y 把 x 的值向左或向右移 y 个位,也就等同于 x 乘或除以 2^y(2的 y 次方),返回类型 和 x 相同。比如,若 N 存储的是01101(10 进制的13),那么 N shl 1 返11010(10 进制26)。注意,y 的值被解释为对 x 所属类型大小(位数)进行模运算,比如,若 x 是一个integer,x shl 40 被解释为 x shl 8,因为integer的大小是32 位(4字节),40 mod 32 等于8。
关系运算符 =、<>、<、>、<= 和 >= 都能对字符串进行操作 + 运算符连接两个字符串
• + 运算符的运算数可以是字符串、packed string(packed arrays of type Char)或字符。但是,若其中一个运算数
是宽字符(WideChar)类型,其它运算数必须是长字符串。
• + 运算符的返回结果和任何字符串类型是兼容的。但是,若运算数都是短字符串或字符,并且它们的组合长度大于255
,则返回结果取前面的255 个字符。
关系运算符 <、>、<= 和 >= 能对PChar 类型进行操作。其它的指运算符:+- 指针加减 ^ 取得指针所指的内容 = <>
等于 不等于
对于Pointer 类型,在dereference 之前必须进行类型转换。
• 若I 是一个整数,P 是一个字符指针,那么P + I 是把P 的地址加上I,也就是一个指向P 后面
第I 个字符处的指针(表达式I + P 等同于P + I);P – I 是把P 的地址减去I,也就是一个指向P
前面第I 个字符处的指针。
• 若P 和Q 都是字符指针,那么P – Q 计算P 的地址(高地址)和Q 地址(低地址)之差,也就
是返回一个表示P 和Q 之间字符数目的整数。P + Q 没有意义。
Set operators(集合运算符):
+并集 - 差集 * 交集 <= 小于等于(子集)>= 大于等于(超集) =等于 <>不等于 in 成员关系
以下规则适用于 +、– 和 * 运算符:
• 当且仅当序数(集合基础类型中的一个值)O 属于集合X 或集合Y(或同时属于X 和Y)时,
O 属于X + Y;当且仅当O 属于X 但不属于Y 时,O 属于X – Y;当且仅当O 同时属于X 和Y
时,O 属于X * Y。
• +、– 和 * 的运算结果属于集合类型set of A..B,这里A 是结果中的最小序数,B 是结果中的
最大序数。
以下规则适用于 <=、>=、=、<> 和 in 运算符:
• 只有当X(集合)中的每个成员也是Y(集合)中的成员时,X <= Y 才为真;Z >= W 等同于
W <= Z;只有当U(集合)和V(集合)正好拥有相同的成员时,U = V 才为真,否则U <> V
为真;
• 对于序数O 和集合S,只有当O 是S 的一个成员时,O in S 才为真。
• 除了实数和整数能一起比较外,两个运算数必须是兼容的类型;
• 对字符串进行比较,是依据它的每个字符在扩展ASCII 字符集中的顺序,字符类型被当作长度
为1 的字符串;
• 两个packed string 要进行比较,它们必须具有相同数目的元素;一个具有n 个元素的packed string
与一个字符串比较时,它被看作长度为n 的字符串;
• 只有当两个PChar 指针都指向同一个字符数组的范围内时,<、>、<= 和 >= 运算符才能作用
于它们;
• 运算符 = 和 <> 能以类或类引用类型作为运算数。当用于类类型时,= 和 <> 的计算规则与
指针一样,只有当C 和D 指向同一个实例对象时,C = D 为真,否则C <> D 为真;当用于类
引用时,只有当C 和D 表示同一个类时,C = D 为真,否则C <> D 为真。
as 和 is 运算符使用类和对象(实例)作为运算数,as 也用于接口类型。
@ 运算符返回一个变量、函数、过程或方法的地址,也就是说,@ 运算符构建一个指向运算数的
指针。
• 若X 是一个变量,@X 返回X 的地址(当X 是一个过程类型的变量时有特殊的规则,请参考语
句和表达式中的过程类型)。若默认的编译器指示字 {$T–} 在起作用,则@X 的类型是
Pointer(通用指针);在 {$T+} 状态下时,@X 的类型是 ^T,这里T 是X 的类型;
• 若F 是一个例程(一个函数或过程),@F 返回F 的入口点,@F 的类型总是Pointer;
• 当 @ 作用于类的方法时,必须使用类名来限定方法名。比如
@TMyClass.DoSomething
它指向TMyClass 的DoSomething 方法。
Set constructors(集合构造器)
[5, 6, 7, 8]
[ 5..8 ]
[red, green, MyColor]
[1, 5, 10..K mod 12, 23]
[’A’..’Z’, ’a’..’z’, Chr(Digit + 48)]
集合构造器[]表示空集
字符串、数组、数组属性以及指向字符串或数组的指针能被索引。比如:FileName[21]
强制类型转换的语法是
typeIdentifier(expression)
若表达式是一个变量,结果被称为variable typecast(变量转换);否则,结果是一个value typecast(值转换)。虽
然它们的语法相同,但它们有不同的转换规则。
在值转换中,类型标志符和转换的表达式必须都是有序类型或指针类型。值转换的例子包括
Integer(’A’)
Char(48)
Boolean(0)
Color(2)
Longint(@Buffer)
声明的语法以及声明的位置取决于要定义的标志符的种类。通常,声明只能出现在块(block)的开始处,
以及单元的接口或实现部分的开始处(在uses 子句之后)。
Hint 指示字platform、deprecated 和library 能附加在任何声明之后。在声明过程或函数的情况下,应
使用分号把hint 指示字和声明的其它部分分开。比如:
procedure SomeOldRoutine; stdcall; deprecated;
var VersionNumber: Real library;
type AppError = class(Exception)
...
end platform;
当源代码在{$HINTS ON} {$WARNINGS ON}状态下编译时,对使用上述指示字声明的标志符的每个引
用都将产生一个适当的提示或警告。使用platform 标记一个条目和特定的操作系统(比如Windows 和
Linux)相关;使用deprecated 表示条目已经废弃或支持它仅为了向后兼容性;使用library 表示依赖于
特定的库或组件框架(比如VCL 或CLX)。
赋值语句的格式如下
variable := expression
这里,variable 是任何变量引用,包括变量、变量类型转换、解除引用的指针,或者一个结构变量的组
成部分;expression 是任何一个赋值兼容的表达式。(在函数块中,variable 能被函数名取代,参考Procedures
and functions。
当启用扩展语法时({$X+}),调用函数也可以像调用过程那样 当这样调用函数时,它的返回值被忽略。
goto label
label: statement
label label;
label label1, ..., labeln;
• 复合语句或with 语句只是简单地执行一系列语句;
• 条件语句,也就是if 或case 语句,根据指定的标准,最多执行一个分支;
• 循环语句,包括repeat、while 和for 循环,重复执行一系列语句;
• 一组特殊的语句,包括raise、try...except 和try...finally 结构,用来创建和处理异常。
with 语句的语法是
with obj do statement
或
with obj1, ..., objn do statement
if 语句有两种形式:if...then 和if...then...else
比如,
if J = 0 then
Exit
else
Result := I/J;
if J <> 0 then
begin
Result := I/J;
Count := Count + 1;
end
else if Count = Last then
Done := True
else
Exit;
Case Statements(Case 语句):
case selectorExpression of
caseList1: statement1;
...
caseListn: statementn;
else
statements;
end
case I of
1..5: Caption := ’Low’;
6..9: Caption := ’High’;
0, 10..99: Caption := ’Out of range’;
else
Caption := ’’;
end;
selectorExpression 是任何一个有序类型的表达式(字符串无效),和C++一样的。
。Object Pascal 有三种循环:repeat 语句、while 语句和for 语句。
使用Break 和Continue 过程来控制repeat、while 或for 语句的流程。
repeat 语句的语法是
repeat statement1; ...; statementn; until expression
比如:
repeat
Write(’Enter a value (0..9): ’);
Readln(I);
until (I >= 0) and (I <= 9);
while 语句的语法是
while expression do statement
比如:
while I > 0 do
begin
if Odd(I) then Z := Z * X;
I := I div 2;
X := Sqr(X);
end;
for 语句的语法是
for counter := initialvalue to finalvalue do statement
或
for counter := initialvalue downto finalvalue do statement
比如:for C := Red to Blue do Check(C);
一个块包含一系列的声明,后面跟一个符合语句。所有的声明必须一起出现在块的开始处,所以,块的
形式是
declarations;
begin
statements;
end;
比如:
function UpperCase(const S: string): string;
var
Ch: Char;
L: Integer;
Source, Dest: PChar;
begin
...
end;
类型可以分为基本(fundamental)和一般(generic)类型。基本类型的范围和形式是相同的,不管是基于何种CPU 和
操作系统;而一般类型的范围和形式是平台相关的
大多数内置类型属于基本类型,但少数整数、字符、字符串和指针类型属于一般类型。
在需要的时候使用一般数据类型是一个好注意,因为它们提供优化的性能和轻便性。
类型也可以分为简单类型、字符串类型、结构类型、指针类型、过程类型和变体类型。
下面的提纲显示了Object Pascal 数据类型的分类:
simple简单类型
ordinal有序类型
integer 一般(generic)整数类型是Integer 和Cardinal
基本整数类型包括Shortint、Smallint、Longint、
Int64、Byte、Word 和Longword
character 基本字符类型是AnsiChar 和WideChar。
一般字符类型是Char,它相当于AnsiChar。
Boolean 布尔类型为Boolean、ByteBool、WordBool和LongBool,Boolean是首选的
enumerated
subrange
real实数类型 Real48 Single Double Extended Comp Currency
string 字符串类型 ShortString AnsiString WideString
structured
set
array
record
file
class
class reference
interface
pointer
procedural
Variant
(type identifier)
标准函数SizeOf 作用于所有变量和类型标志符,它返回一个整数,表明存储指定类型的数据所需要的内存数(字节)。
有序类型包括整数、字符、布尔、枚举和子界类型。有序类型定义一个有次序的数值集合,除了它的第一个值以外,其它每个值都有一个唯一的前驱值(predecessor);除了最后一个外,其它每个值都有一个唯一的后继值(successor)
Ord 有序类型表达式 序数值
Pred 有序类型表达式 表达式的前驱值
Succ 有序类型表达式 表达式的后继值
High 有序类型的变量或标志符 类型的最大值
Low 有序类型的变量或标志符 类型的最小值
例程High、Low、Succ、Pred、Inc、Dec、IntToStr 和IntToHex 完全支持Int64 参数。
而且,Round、Trunc、StrToInt64 和StrToInt64Def 返回Int64 类型的值;
少数例程(包括Ord)根本不能对Int64 数值进行操作。
当把整数类型的最后一个值增大或把第一个值减少时,结果将回到整数类型的开头或尾部
。内置函数Chr,返回一个在AnsiChar 或WideChar取值范围内任意一个整数的字符值。比如,Chr(65)返回字母A。
对ByteBool、WordBool 和LongBool 来说,若它的序数不为0,它就被认为是True。在一个需要Boolean类型的环境种,
编译器自动把非0 值转换为True。
在Object Pascal 中,布尔表达式不能和整数或实数进行换算。所以,若X 是一个整数变量,语句
if X then ...;
会产生编译错误
定义一个枚举类型,使用下面的语法:
type typeName = (val1, ..., valn)
type Size = (Small = 5, Medium = 10, Large = Small + Medium);
比如:type Suit = (Club, Diamond, Heart, Spade);
在声明变量时,可以直接使用(val1,…,valn)结构,它就像一个类型名称:
var MyCard: (Club, Diamond, Heart, Spade);
子界类型表示其它有序类型(称为基础类型)的一个子集:它的形式为Low..High
• Extended 类型比其它实数类型有更高的精度,但不够轻巧。当使用Extended 类型创建的文件要跨平
台共享时,务必小心。
• Comp(computational)类型对Intel CPU 是优化的,表示为64 位整数,但它被分类位实数类型,因为它的行为不像有序类型(比如,你不能递增和递减)。保留此类型只是为了向后兼容性,使用Int64可获得更好的性能。
• Currency 类型有固定的小数点,可以把在货币运算中出现的错误减到最少。It is stored as a scaled
64-bit integer with the four least significant digits implicitly representing decimal places. 当在赋值
语句和表达式中与其它实数类型混合使用时,它的值会自动除以或乘上10000。
在赋值语句和表达式中,字符串类型可以混合使用,编译器自动进行所需的转换。但对于过程或函数,
当使用引用方式传递字符串时,类型必须正确。
在默认的{$H+}状态下,编译器把string(当它的后面没有包含数字的中括号时)解释为AnsiString;
使用{$H-}指示字把它解释为ShortString。
标准函数Length 返回一个字符串的字符个数;SetLength 设置一个字符串的长度。
可以像数组一样对字符串使用索引。若S 是一个字符串变量,i 是一个整数表达式,则S[i]表示S 中第
i 个字符(或者,严格说来,是第i 个字节)。对于ShortString 或AnsiString,S[i]是AnsiChar 类型;对于
WideString,S[i]是WideChar 类型。语句 MyString[2] := ’A’; 把值A 赋给MyString 的第2 个字符。
若S 是一个ShortString 变量,Ord(S[0]),和Length(S)一样,将返回S 的长度;给S[0]赋值,就像调用SetLength
,将改变S 的长度。
var MyString: string[100];声明一个叫做MyString 的变量,它的最大长度是100 字节,
标准函数High 和Low 能作用于short-string 类型名和变量,High 返回它的最大长度,Low 返回0。
AnsiString 类型又称为长字符串,它可以动态分配,并且长度只受内存限制。它使用8 位ANSI 字符。
长串变量是一个指针,占据4 个字节的内存。当变量为空时(也就是长度为0 的字符串),指针为nil,
此时,它不需要额外的内存
因为长串变量是指针,所以,两个或更多的变量可以引用同一个值,而不必使用额外的内存。编译器利
用这一点节省资源和进行快速赋值。只要一个长串变量被销毁或赋给一个新值,原来的串(变量的前一
个值)引用计数减1,而新的值(如果有的话)引用计数加1。若引用计数为0,它的内存被释放。这个
过程被称为reference-counting。当使用字符串索引改变其中的一个字符时,若字符串的引用计数大于1,
将生成串的一个拷贝,这被称为copy-on-write 机制。
WideString 类型是动态分配的、由16 位Unicode 字符所构成的字符串。在大多数方面,它和AnsiString
相似。(注:宽字符串没有引用计数,不支持copy-on-write 机制,但支持内存动态分配。)
在Win32 下,WideString 和COM BSTR 类型兼容。
Object Pascal 支持单字节和多字节字符以及字符串,适用的类型有:Char、PChar、AnsiChar、PAnsiChar
和AnsiString。对多字节字符串使用索引是不可取的,因为S[i]表示S 中第i 个字节(不一定是第i 个字
符),但是,标准字符串处理函数有多字节版本,它们还实现了locale-specific ordering for characters(多
字节函数的名称通常以Ansi 开头,比如StrPos 的多字节版本是AnsiStrPos)。多字节支持依赖于操作系
统和本地设置(current locale)。
Object Pascal 使用WideChar、PWideChar 和WideString 类型支持Unicode 字符和字符串。
字符串常量和类型PChar、PWideChar 是赋值兼容的,后两者表示指针,
它们指向一个以0 结尾的Char 或WideChar 字符数组。
一个字符指针(PChar 或PWideChar)可以像数组一样使用索引。
结构类型包括集合、数组、记录,也包括类、类引用(class-reference)和接口类型。
除了集合只能包含有序值以外,结构类型可以包含其它的结构类型,且结构的层次不受限制。
默认情况下,一个结构类型的值被圆整为字(word)或者双字(double-word),这样访问起来更迅速。
当声明一个结构类型时,可以包含关键字packed,这将对数据的存储进行压缩(并不是压缩,只是不再
圆整数据,而保留它的自然结构)。比如:
type TNumbers = packed array[1..100] of Real;
使用packed 使数据访问变慢,并且在使用字符数组的情况下,能影响类型兼容性
集合(Set)是同一种有序类型的值的聚集,它们包含的值没有内在的顺序,且一个值在集合中包含两次并没有
实际意义。声明:
type
TSomeInts = 1..250;
TIntSet = set of TSomeInts;
這等同於:type TIntSet = set of 1..250;
有了上面的声明,你就可以像下面这样构造集合了:
var Set1, Set2: TIntSet;
...
Set1 := [1, 3, 5, 7, 9];
Set2 := [2, 4, 6, 8, 10]
你也可以直接使用set of …构造直接声明变量:
var MySet: set of ’a’..’z’;
...
MySet := [’a’,’b’,’c’];
静态数组类型以下面的格式声明:array[indexType1, ..., indexTypen] of baseType
比如:var MyArray: array[1..100] of Char;
一个多维数组是数组的数组,比如:
type TMatrix = array[1..10] of array[1..50] of Real;
就等价于
type TMatrix = array[1..10, 1..50] of Real;
可使用这样的索引:MyMatrix[2,45],或像这样:MyMatrix[2][45]。
packed array[Boolean,1..10,TShoeSize] of Integer; 等价于
packed array[Boolean] of packed array[1..10] of packed array[TShoeSize] of Integer;
标准函数Low 和High 作用于数组类型(的标志符)或变量,它们返回数组第1 个索引(类型)的最小
值和最大值;Length 返回数组第1 维的元素个数
一维、压缩的(packed)、Char 类型的静态数组称为packed string,它和字符串类型兼容,也和其它具有
相同元素个数的packed string 兼容。
array[0..x] of Char 类型的数组,是0 下标开始的字符数组,它用来存储零结尾字符串,并且和PChar 类
型兼容。
給動態數組賦值或把它傳給SetLength函數時,它的內存被重新分配。
动态数组以下面的形式声明: array of baseType
比如:var MyFlexibleArray: array of Real;
要取消动态数组的分配,给它的变量赋值nil,或者把变量传给Finalize
不要对一个动态数组变量使用运算符‘^’,也不要对它使用New 或Dispose 过程
若X 和Y 是同一类型的动态数组变量,X := Y 使X 指向和Y 相同的数组(在这个操作之前,不必给X
分配内存)。不像字符串和静态数组,动态数组不会在被写之前自动拷贝。
使用索引给动态数组赋值(比如,MyFlexibleArray[2] := 7),不会为数组重新分配内存;编译时,索引
边界检查也不会给出提示。
要截断一个动态数组,把它传给SetLength 或Copy,并把返回的值赋给数组变量(SetLength 通常更快)
记录(类似于其它语言中的结构)表示不同种类的元素的集合,每个元素称为“字段”,声明记录类型时
要为每个字段指定名称和类型。声明记录的语法是
type recordTypeName = record
fieldList1: type1;
...
fieldListn: typen;
end
比如:
type
TDateRec = record
Year: Integer;
Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
Day: 1..31;
end;
只有在实例化时才进行分配,像下面的样子:
var Record1, Record2: TDateRec;
Record2 := Record1; 把Record1 的值拷贝给Record2
也可以使用record ...构造直接声明变量:
var S: record
Name: string;
Age: Integer;
end;
一个记录类型能拥有变体部分,它看起来就像case 语句
type
TEmployee = record
FirstName, LastName: string[40];
BirthDate: TDate;
case Salaried: Boolean of
True: (AnnualSalary: Currency);
False: (HourlyWage: Currency);
end;
要声明一个文件类型,使用下面的语法:type fileTypeName = file of type
日期:2004年3月15日 星期一 天气:晴朗 作者:Napoleo
Delphi語法筆記2
发表:2004-3-15 20:26:47 出处:你的博客网(yourblog.org)
--------------------------------------------------------------------------------
^符号有两个用途,在我们的例子中都用到了。当它出现在一个类型标志符前面时:
^typeName
它表示一个指向typeName 类型的变量的指针;当它出现在一个指针变量的后面时:
pointer^
它表示对指针解除引用,换句话说,它返回在指针所指向的地址处保存的值。
除了使用@运算符,你也可以使用几个标准例程给一个指针赋值。New 和GetMem 过程把一个内存地址
赋给指针,而Addr 和Ptr 函数则返回一个指向特定变量或地址的指针。
保留字nil 是一个特殊常量,可赋给任何指针(类型)。当nil 被赋给一个指针时,指针不表示任何东西。
使用下面的语法,你能声明一个任意类型的指针,
type pointerTypeName = ^type
当定义一个记录类型(或其它数据类型)时,习惯上也就定义了一个此类型的指针,这使得处理更容易,
我们不需要拷贝一大块内存。
在Pointer 类型变量的后面使用^运算符会引发编译错误。要访问一个Pointer 类型引用的变量,首先把它
转换为其它指针类型,然后再解除引用。
基本(fundamental)类型PAnsiChar 和PWideChar 分别表示AnsiChar 和WideChar 值的指针,一般(generic)
类型PChar 表示一个指向Char 的指针(在当前实现中,它表示AnsiChar)。这些字符指针用来操纵零结
尾字符串
System 和SysUtils 单元定义了许多常用的标准指针类型:
Pointer type Points to variables of type
PAnsiString,PString AnsiString
PByteArray TByteArray (declared in SysUtils).
Used to typecast dynamically allocated memory for array access.
PCurrency,PDouble,
PExtended,PSingle Currency,Double,Extended,Single
PInteger Integer POleVariant OleVariant
PShortString ShortString. Useful when porting legacy code that uses the old PString type.
PTextBuf TTextBuf (declared in SysUtils).
TTextBuf is the internal buffer type in a TTextRec file record.)
PVarRec TVarRec (declared in System)
PVariant Variant
PWideString WideString
PWordArray TWordArray (declared in SysUtils).
Used to typecast dynamically allocated memory for arrays of 2-byte values.
过程类型允许你把过程和函数作为“值”看待,它可以赋给变量或传给其它过程和函数。比如,假设你
定义了一个叫做Calc 的函数,它有两个整型参数并返回一个整数值:
function Calc(X,Y: Integer): Integer;
你可以把Calc 函数赋给变量F:
var F: function(X,Y: Integer): Integer;
F := Calc;
我们只取过程或函数头(heading)并把procedure 或function 后面的标志符去掉,剩下的就是过程类型
的名称。
可以在声明变量时直接使用这样的名称(就像上面的例子一样),也可以声明新类型:
type
TIntegerFunction = function: Integer;
TProcedure = procedure;
TStrProc = procedure(const S: string);
TMathFunc = function(X: Double): Double;
var
F: TIntegerFunction; {F 是一个无参数、返回整数值的函数}
Proc: TProcedure; { Proc 是一个无参数过程}
SP: TStrProc; { SP 是一个使用string 类型参数的过程}
M: TMathFunc; { M 是一个使用Double 类型参数、返回Double 值的函数}
procedure FuncProc(P: TIntegerFunction); { FuncProc 是一个过程,它的参数是一个无参数、返回整数值的函数}
上面的所有变量都是过程指针,也就是指向过程或函数地址的指针。若想引用一个实例对象的方法(参
考Classes and objects),你需要在过程类型的名称后面加上of object。比如
type
TMethod = procedure of object;
TNotifyEvent = procedure(Sender: TObject) of object;
这些类型表示方法指针。方法指针实际上是一对指针:第一个存储方法的地址,第二个存储方法所属的
对象的引用。给出下面的声明
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
...
end;
var
MainForm: TMainForm;
onClick: TNotifyEvent
我们就可以进行下面的赋值:
onClick := MainForm.ButtonClick;
两个过程类型是兼容的,如果它们具有
相同的调用约定,
相同类型的返回值(或没有返回值),并且具有
相同数目的参数,并且相应位置上的类型也相同(参数名无关紧要)
过程指针和方法指针是不兼容的。nil 可以赋给任何过程类型。
嵌套的过程和函数(在其它例程中声明的例程)不能被用作过程类型值,内置的过程和函数也不可以。
若想使用内置的过程作为过程类型值,比如Length,你可以给它加一个包装:
function FLength(S: string): Integer;
begin
Result := Length(S);
end;
。要比较F 和MyFunction 的过程值,使用
if @F = @MyFunction then ...;
@F 把F 转换为无类型指针变量,它包含的是地址,@MyFunction 返回的是MyFunction 的地址。
要取得过程变量的内存地址(而不是它包含的地址),使用@@。比如,@@F 返回F 的地址。
@运算符也可以用来把一个无类型指针值赋给过程变量,比如
var StrComp: function(Str1, Str2: PChar): Integer;
...
@StrComp := GetProcAddress(KernelHandle, ’lstrcmpi’);
调用GetProcAddress 函数,并使StrComp 指向结果。
要测试一个过程变量是否被赋值,使用标准函数Assigned:
if Assigned(onClick) then onClick(X);
除结构类型和指针外,变体类型能存储其它的任何类型;
变体类型能存储接口,并能通过它使用接口的方法和属性(参考Object interfaces);
变体类型能存储动态数组,也能存储一种特殊的静态数组:变体数组(Variant array)。
变体类型能和其它变体类型、整数、实数、字符串和布尔值在表达式和赋值语句中混合使用,编译器自动完成类型转换。
可以通过自定义来扩展变体类型,从而能存储任意值。比如,你可以定义一个使用索引的变体字符串
类型,或者让它存储特定的类引用、记录或静态数组。自定义变体类型通过TCustomVariantTyp 的子类
来创建。
所有的变体类型在创建时被初始化为Unassigned,Null 表示未知或没有数据。
标准函数VarType 返回变体类型的类型码,常量varTypeMask 是一个位掩码,用来从VarType 的返回值
中提取类型码,所以,在下面的例子中
VarType(V) and varTypeMask = varDouble
若V 包含Double 或Double 数组,则它返回True
在System 单元定义的TVarData 记录类型能被用来转换变体类型,并且可以访问它们的内部构造。
VarAsType 和VarCast 标准例程能用来改变一个Variant 的内部表示。
除了^、is 和in,所有运算符都可以使用Variant 作为运算数
对Variant 的操作返回Variant 值;若有一个运算数是Null 则结果为Null;
若有一个运算数为Unassigned 则引发异常。
在二元运算中,若只有一个运算数是Variant,则另一个被转换为Variant。
不能把一个普通的静态数组赋给Variant,取而代之的是,通过调用VarArrayCreate 或VarArrayOf 两者
之一来创建Variant 数组。比如,
V: Variant;
...
V := VarArrayCreate([0,9], varInteger);
要创建字符串类型的Variant 数组,使用varOleStr
使用VarArrayRedim 函数来更改Variant 数组的大小。其它用于Variant 数组的标准例程包括
VarArrayDimCount 、VarArrayLowBound 、VarArrayHighBound 、VarArrayRef 、VarArrayLock 和VarArrayUnlock.
。Variant 和OleVariant 的主要区别是,Variant 能包含只有当前程序才能理解的数据类型,
OleVariant 只包含为Ole 自动化兼容而定义的数据类型,它说明,这些数据类型能在程序间或通过网络传送,
而不必担心另一端是否知道如何处理它们。
type
T1 = Integer;
T2 = T1;
T3 = Integer;
T4 = T2;
T1、T2、T3、T4 和Integer 都是指的同一种类型。要声明一种不同的类型,在声明中重复type 关键字。
比如
type TMyInteger = type Integer;
创建了一种新类型TmyInteger,它和Integer 不同。
一个类型声明指定一个标志符,来表示一种数据类型。类型声明的语法为
type newTypeName = type
这里,newTypeName 是一个有效的标志符。比如,给定如下的类型声明
type TMyString = string;
你就可以声明变量
var S: TMyString;
同时声明多个变量时不能包括初始化,Variant 和文件类型的变量声明也不能初始化。
如果你没有明确地初始化一个全局变量,编译器把它初始化为0。相反,不能在声明局部变量时进行初
始化,它们的值是随机的,直到赋给它们一个值。
你可以创建一个新变量,它和另一个变量在内存的同一个位置。要这样做的话,声明这个新变量时在类
型名的后面跟关键字absolute,后面再跟一个已存在(先前声明)的变量。比如,
var
Str: string[32];
StrLen: Byte absolute Str;
指定变量StrLen 从Str 的地址开始。因为短字符串的第一个字节包含字符串的长度,StrLen 的值就是Str
的长度。
使用absolute 声明时不能初始化变量,也不能组合其它指示字(和absolute 一同使用)。
可以调用GetMem 或New 过程来创建动态变量,这种变量在堆中分配内存,它们不能自动管理。
使用FreeMem 来释放由GetMem 创建的变量,使用Dispose 释放由New 创建的变量。
其它能作用于动态变量的标准例程包括ReallocMem、Initialize、StrAlloc 和StrDispose。
声明线程局部变量时,使用threadvar,而不是var,比如,
threadvar X: Integer;
线程变量声明
• 不能出现在过程或函数中
• 不能包含初始化
• 不能指定absolute 指示字
不能创建指针或过程类型的线程变量,也不能在动态调入库中使用线程变量(除了包)。
由编译器管理的动态变量,即长字符串、宽字符串、动态数组、Variants 和接口,能被声明为threadvar,
但编译器不能自动释放由每个线程创建的堆内存。若使用这些类型的线程变量,要负责释放它们的内存。
资源字符串的声明像真常量,除了用resourcestring 代替const。表达式等号的右边必须是常量表达式并
且返回一个字符串。
在默认的{$J-}编译状态下,类型常量不能被赋予新值,实际上,它们是只读变量;但如果使用了{$J+}
编译器指示字,类型常量能被赋予新值,它们在本质上就像初始化的变量。
要声明数组常量,把数组元素的值用括号括起来,值之间用逗号隔开,这些值必须是常量表达式。比如,
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’;
要声明一个记录常量,在括号中使用fieldName: value 的形式来指定每个字段的值,每个字段用分号隔开。
每个字段的值必须是常量表达式。字段列出的顺序必须和声明的相同,若有tag 字段,则必须指定它的
值;若记录有一个Variant 部分,只有tag 字段选定的Variant 才能被赋值。
举例如下:
type
TPoint = record
X, Y: Single;
end;
TVector = array[0..1] of TPoint;
TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
TDate = record
D: 1..31;
M: TMonth;
Y: 1900..1999;
end;
const
Origin: TPoint = (X: 0.0; Y: 0.0);
Line: TVector = ((X: -3.1; Y: 1.5), (X: 5.8; Y: 3.0));
SomeDay: TDate = (D: 2; M: Dec; Y: 1960);
记录常量不能包含文件类型的值。
过程和函数统称为例程(routine)
标准过程Exit 可出现在任何过程或函数中,它结束例程的执行,并立即把程序控制返回到例程调用的地方。
一个过程声明有如下格式:
procedure procedureName(parameterList); directives;
localDeclarations;
begin
statements
end;
函数声明和过程声明类似,除了它要指定一个返回值的类似和返回值。函数声明有如下格式:
function functionName(parameterList): returnType; directives;
localDeclarations;
begin
statements
end;
函数名本身也扮演一个特殊的变量,它和内置的变量Result 一样,存储函数的返回值。
Result 和函数名并不是能完全互换的,当函数名出现在赋值语句的左边时,编译器假设它
用来跟踪(存储)返回值(就像Result);在任何其它情况下,编译器把它解释为对它的递归调用。而对
Result,它可以作为变量用在运算、类型转换、集合构造器、索引以及调用其它例程。
只要启用了扩展语法({$X+}),Result 在每个函数中被隐含声明,不要试图重新声明它。
若还没有给Result 或函数名赋值,程序就结束了,则函数的返回值没有被定义(undefined)。
在声明过程或函数时,你可以使用下面的指示字之一来指明调用约定:register、pascal、cdecl、stdcall
以及safecall。比如,
function MyFunction(X, Y: Real): Real; cdecl;
...
调用约定决定了参数被传递给例程的顺序,它们也影响从堆栈中删除参数、传递参数时寄存器的使用,
以及错误和异常处理。默认的调用约定是register。
• register 和pascal 调用从左到右传递参数,也就是说,最左边的参数最早被计算并传递,最右边的
参数最后被计算和传递;cdecl、stdcall 和safecall 调用从右到左传递参数;
• 除了cdecl 调用,过程和函数在返回之前从堆栈中移除参数,而使用cdecl,当调用返回时,调用者
从堆栈中移除参数;
• register 调用能使用多达3 个CPU 寄存器传递参数,而其它调用则全部使用堆栈传递参数;
• safecall 调用实现了异常“防火墙”,在Windows 下,它实现了进程间COM 错误通知。
默认的register 调用是最有效的,因为它通常避免了要创建堆栈结构(stack frame)(访问公布属性的方
法必须使用register);当调用来自C/C++编写的共享库中的函数时,cdecl 是有用的;通常,当调用外部
代码时,推荐使用stdcall 和safecall。在Windows 中,系统API 使用stdcall 和safecall,其它操作系统
通常使用cdecl(注意,stdcall 比cdecl 更有效)。声明双重接口的方法必须使用safecall;保留pascal 调用是为了向后兼容性。
指示字near、far 和export 用在16 位Windows 编程中,它们对32 位程序没有影响,保留它们是为了向后兼容性。
在声明过程或函数时,用forward 指示字取代例程块(包括局部变量声明和语句),比如,
function Calculate(X, Y: Integer): Real; forward;
forward 声明的目的是把过程或函数标志符的作用域提前,这允许在它被实际定义之前,其它过程和函
数可以进行调用。除了能使你更灵活地组织代码外,forward 声明对相互递归调用(mutual recursion)有
时是必须的。
在声明过程或函数时,用external 指示字取代例程块,能允许你调用和程序分开编译的例程。外部例程
可以来自目标文件或动态调入库(dynamically loadable library)。
当导入一个带有可变数目参数的C++函数时,要使用varargs 指示字。比如,
function printf(Format: PChar): Integer; cdecl; varargs;
varargs 指示字只能用于外部例程,并且只能使用cdecl 调用约定。
要调用目标文件中的例程,首先要使用$L(或$LINK)编译器指示字把目标文件链接到你的程序中。比
如,
在Windows 下: {$L BLOCK.OBJ}
在Linux 下: {$L block.o}
把BLOCK.OBJ(Windows)或block.o (Linux)链接到程序或它所在的单元。然后,声明你想调用的
函数和过程:
procedure MoveWord(var Source, Dest; Count: Integer); external;
procedure FillWord(var Dest; Data: Integer; Count: Integer); external;
现在,你就能调用来自BLOCK.OBJ 或block.o 的MoveWord 和FillWord 例程了。
像上面的声明,经常用来访问由汇编语言编写的外部例程,你也可以直接在Object Pascal 源代码中放置
汇编语言写的例程。
要从一个动态调入库(.so 或.DLL)导入例程,把如下格式的指示字
external stringConstant;
放在一个正常的过程头或函数头的尾部。这里,stringConstant 是用单引号括起来的库文件的名称。比如,
在Windwos 下
function SomeFunction(S: string): string; external ’strlib.dll’;
从strlib.dll 导入一个叫做SomeFunction 的函数。
在Linux 下,
function SomeFunction(S: string): string; external ’strlib.so’;
从strlib.so 导入一个叫做SomeFunction 的函数。
在导入例程时,它的名称可以和库中的名称不同。如果你这样做,在external 指示字中指定它的原始名
称。
external stringConstant1 name stringConstant2;
这里,第一个stringConstant 给出了库文件的名称,第二个stringConstant 是例程的原始名称。
在Windows 下:比如,下面的声明从user32.dll(Windows API 的一部分)导入一个函数。
function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer;
stdcall; external ’user32.dll’ name ’MessageBoxA’;
函数的原始名称是MessageBoxA,但导入后的名称是MessageBox。
你可以使用一个数字代替名称,来指定你要导入的例程:
external stringConstant index integerConstant;
这里,integerConstant 是输出表(export table)中例程的索引。
在Linux 下:比如,下面的声明从libc.so.6 导入一个标准系统函数。
function OpenFile(const PathName: PChar; Flags: Integer): Integer; cdecl;
external ’libc.so.6’ name ’open’;
函数的原始名称是open,但导入后的名称是OpenFile。
在你的导入声明中,要保证例程的名称没有拼写错误,并且大小写一致。但在以后调用这些例程时,它
们是不区分大小写的。
重载例程必须使用overload 指示字,并且它们有不同的参数列表。
当重载例程被声明为forward、或在单元的接口部分声明时,在它的定义声明部分必须重新列出它的参数。
参数以下面几种方式进行分类:
• 每个参数分为value(数值参数)、variable(变量参数)、constant(常量参数)或out(out 参数),
默认是数值参数。关键字var、const 以及out 分别表示变量参数、常量参数和out 参数。
• 数值参数总是有类型的,而常量参数、变量参数和out 参数既可以是有类型的,也可以是无类型的。
• 数组参数有特殊规则。
文件类型以及包含文件的结构类型(的实例)只能作为变量参数传递。
数值参数通过数值传递,而变量参数通过引用传递。
如果例程声明了一个var 参数,你必须给它传递一个能被赋值的表达式,也就是一个变量、类型化常量
(typed constant,在{$J+}状态下)、dereferenced 指针、字段或者索引变量(indexed variable)
当使用out 参数时,传给例程的引用参数的初始值被忽略。out 参数只是为了输出,也就是说,它告诉
函数或过程在哪里存储输出,但不提供任何输入。
out 参数经常用在分布式对象模型中,比如COM 和CORBA。而且,当向函数或过程传递未初始化的变
量时,你应当使用out 参数。
特殊标志符OpenString 能用于声明可变长度的短字符串参数:
procedure Check(S: OpenString);
当编译器指示字{$H-}和{$P+}都起作用时,在声明参数时关键字string 等同于OpenString。
短字符串、OpenString、$H 和$P 是为了向后兼容性。在新代码中,使用长字符串来避免这种情况
开放数组参数遵循下列规则:
• 元素的下标总是从0 开始,第一个是0,第二个是1,依此类推。标准函数Low 和High 返回0 和
Length-1。SizeOf 函数返回传给例程的实际数组的大小;
• 它们只能通过元素进行访问,不允许给整个开放数组赋值;
• 它们只能被当作开放数组参数或无类型var 参数传给其它过程和函数,它们不能传给SetLength 函
数;
• 你可以传递一个变量而不是数组,变量的类型就是开放数组的基础类型,它被当作一个长度为1 的
数组。
Variant 开放数组参数允许你向一个过程或函数传递由不同类型的元素构成的数组。要定义这样一个例
程,指定array of const 作为参数的类型,这样
procedure DoSomething(A: array of const);
声明了一个叫做DoSomething 的过程,它能接收不同类型的数组。
array of const 结构等同于array of TVarRec。TVarRec 在System 单元定义,表示一个拥有变体部分的记
录,它能存储整数、布尔、字符、实数、字符串、指针、类、类引用、接口和变体类型的值。TVarRec
记录的VType 字段指示数组中每个元素的类型。一些类型以指针而不是以数值形式进行传递,特别是,
长字符串以指针类型传递,必须被转换为string。
有默认值的参数必须出现在参数列表的最后
在过程类型中指定的默认值会覆盖实际例程中指定的默认值。所以,给出下面的声明
type TResizer = function(X: Real; Y: Real = 1.0): Real;
function Resizer(X: Real; Y: Real = 2.0): Real;
var
F: TResizer;
N: Real;
语句
F := Resizer;
F(N);
导致(N, 1.0)传给Resizer。
默认参数局限于能被常量表达式所表示的值,所以,动态数组、过程、类、类引用或者接口类型的参数
除了nil 外不能给它们指定默认值,而记录、变体、文件、静态数组和对象类型则根本不能指定默认值。
若在重载例程中使用默认参数,要避免引起歧义。
开放数组构造器和集合构造器类似,是由逗号隔开的表达式序列,并且被一对中括号包围。
开放数组构造器只能当作数值参数或常量参数传递。构造器中的表达式必须和数组参数的基础类型是赋
值兼容的。对于Variant 开放数组参数,表达式可以是不同的类型。
一个类声明有如下格式
type className = class (ancestorClass)
memberList
end;
在类声明中,方法看起来就像函数(或过程)头,而没有函数(或过程)体。方法的定义出现在程序的
其它地方。比如,这里是Classes 单元中TMemoryStream 类的声明
type
TMemoryStream = class(TCustomMemoryStream)
private
FCapacity: Longint;
procedure SetCapacity(NewCapacity: Longint);
protected
function Realloc(var NewCapacity: Longint): Pointer; virtual;
property Capacity: Longint read FCapacity write SetCapacity;
public
destructor Destroy; override;
procedure Clear;
procedure LoadFromStream(Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SetSize(NewSize: Longint); override;
function Write(const Buffer; Count: Longint): Longint; override;
end;
除了TObject,System 单元还声明了一个类引用类型TClass。
类和它的祖先类是赋值兼容的,所以,某个类类型的变量能引用它的任何子类类型的实例。
除了类类型,你可以使用如下语法声明一个object 类型
type objectTypeName = object (ancestorObjectType)
memberList
end;
Object 类型不能有published 成员。因为object 类型不是从TObject 继承,它们没有内置的构造函数和析构函数,也没有其它方法。
你能使用New 过程创建Object 类型的实例,并使用Dispose 过程销毁它们,你也可以像使用记录一样,采用简
单方式声明object 类型的变量。Object 类型只是为了向后兼容性,不推荐使用它们。
类的每个成员都有一个称为可见性的属性,用下面的关键字之一来表示它:private、protected、public、published 和automated。
private表示最小程度的访问能力,protected 表示中等程度的访问能力,public、published 和automated 表示最大程度的访问能力。
若声明一个成员时没有指定其可见性,则它和前面的成员拥有相同的可见性;若在类声明的开始没有指
定可见性,当在{$M+}状态下编译类时(或者继承自一个在{$M+}状态下编译的类),它的默认可见性是
published,否则,它的可见性是public。
通过重新声明,你可以在派生类中增大一个成员的可见性,但你不能降低它的可见性。比如,一个protected
属性在派生类中能被改变为public,但不能改为private。还有,published 成员在子类中不能改为public。
若声明一个类时以class 和分号结束,也就是有下面的格式,
type className = class;
在class 后面没有列出父类,也没有成员列表,这是一个forward 声明。Forward 声明的类必须在同一个
声明区域进行定义声明,换句话说,在forward 声明和它的定义声明之间除了类型声明外,不能有任何
其它内容。
虽然类声明既可以出现在单元的interface 部分,也可以出现在implementation 部分,但类方法的实现(定
义声明)必须出现在implementation 部分。
。指示字应当只出现在类声明中,并且以下面的顺序列出:
reintroduce; overload; binding; calling convention; abstract; warning
这里,binding 是virtual、dynamic 或override;calling convention 是register、pascal、cdecl、stdcall 或
safecall;warning 是platform、deprecated 或library。
关键字inherited 在实现多态行为时扮演着特殊角色,它出现在方法定义中,后面跟一个标志符或者不跟。
方法分为静态方法(默认)、虚方法和动态方法。虚方法和动态方法能被覆盖,它们可是是抽象的。
方法默认是静态的。
要实现虚方法或动态方法,在声明时包含virtual 或dynamic 指示字。不像静态方法,虚方法和动态方
法能在派生类中被覆盖。当调用一个被覆盖的方法时,类或对象的实际类型决定了哪种实现被调用(运
行时),而不是它们被声明的类型。
要覆盖一个方法,使用override 指示字重新声明它就可以了。声明被覆盖的方法时,它的参数的类型和
顺序以及返回值(若有的话)必须和祖先类相同。
只有虚方法和动态方法能被覆盖,但是,所有方法都能被重载
虚方法和动态方法在语义上是相同的,唯一的不同是在运行时决定方法调用的实现方式上,虚方法在速
度上进行了优化,而动态方法在代码大小上做了优化。
在声明方法时,如果它和继承的方法具有相同的名称和参数,但不包含override,则新方法仅仅是隐藏
了继承下来的方法,并没有覆盖它。这样,两个方法在派生类中都存在,方法名是静态绑定的。
reintroduce 指示字告诉编译器,当隐藏一个先前声明的虚方法时,不给出警告信息。比如,
procedure DoSomething; reintroduce; // 父类也有一个DoSomething 方法
当要使用新方法隐藏继承下来的虚方法时,使用reintroduce 指示字。
抽象方法是虚方法或动态方法,并且在声明它的类中没有实现,而是由它的派生类来实现。声明抽象方
法时,必须在virtual 或dynamic 后面使用abstract 指示字。比如,
procedure DoSomething; virtual; abstract;
只有当抽象方法在一个类中被覆盖时,你才能使用这个类或它的实例进行调用。
一个方法可以使用overload 指示字来重新声明,此时,若重新声明的方法和祖先类的方法具有不同的参
数,它只是重载了这个方法,并没有隐藏它。当在派生类中调用此方法时,依靠参数来决定到底调用哪
一个。
若要重载一个虚方法,在派生类中重新声明时使用reintroduce 指示字。
在一个类中,你不能以相同的名字公布(published)多个重载的方法,维护RTTI 信息要求每一个公布
的成员具有不同的名字。
作为属性读写限定符的方法不能被重载。
实现重载的方法时,必须重复列出类声明时方法的参数列表。
按惯例,构造函数通常命名为Create
constructor Create;
constructor Create(AOwner: TComponent);
Message 方法用来响应动态分派的消息。Message 方法在各个平台上都是支持的,VCL 使用message 方
法来响应Windows 消息,CLX 不使用message 方法来响应系统事件。
比如,在Windows 下:
type
TTextBox = class(TCustomControl)
private
procedure WMChar(var Message: TWMChar); message WM_CHAR;
...
end;
消息处理函数很少直接调用,相反,消息是通过继承自TObject 的Dispatch 方法来分派给对象的。
procedure Dispatch(var Message);
传给Dispatch 的参数Message 必须是一个记录,并且它的第一个字段是Cardinal 类型,用来存储消息号
码。
Dispatch 按类的层次结构向后搜索(从调用对象所属的类开始),它将调用和传给它的消息具有相同号码
的message 方法。若没有发现指定号码的message 方法,Dispatch 调用DefaultHandler。
比如,给定下面的声明
property Color: TColor read GetColor write SetColor;
GetColor 方法必须被声明为:
function GetColor: TColor;
SetColor 方法必须被声明为下面之一:
procedure SetColor(value: TColor);
procedure SetColor(const value: TColor);
(当然,SetColor 的参数名不必非得是value。)
数组属性是被索引的属性,它们能表示像下面的一些事物:列表中的条目、一个控件的子控件和位图中
的象素等等。
声明数组属性时包含一个参数列表,它指定索引的名称和类型,比如,
property Objects[Index: Integer]: TObject read GetObject write SetObject;
property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;
property values[const Name: string]: string read Getvalue write Setvalue;
定义数组属性时可以在后面使用default 指示字,此时,数组属性变成类的默认属性。
若一个类有默认属性,你能使用缩写词object[index]来访问这个属性,它就相当于object.property[index]。
索引限定符能使几个属性共用同一个访问方法来表示不同的值。
若一个属性有索引限定符,它的读写限定符必须是方法而不能是字段。
可选指示字stored、default 和nodefault 被称为存储限定符,它们对程序的行为没有影响,但决定了RTTI
的维护方式,它们决定是否把公布属性的值存储到窗体文件中。
stored 指示字后面必须跟True、False、Boolean 类型的字段名或者一个返回Boolean 值的无参数方法。
比如,
property Name: TComponentName read FName write SetName stored False;
若一个属性没有stored 指示字,就相当于指定了stored True。
default 指示字后面必须跟随一个和属性具有相同类型的常量,比如,
property Tag: Longint read FTag write FTag default 0;
要覆盖一个继承下来的默认值而不指定新值,使用nodefault 指示字。default 和nodefault 只支持有序类
型和集合类型(当它的基础类型是有序类型,并且上下边界都在0 到31 之间时)。若声明属性时没有使
用default 或者nodefault,它被当作nodefault 看待。对于实数、指针和字符串,它们分别有隐含的默认
值0、nil 和 ’ ’(空串)
声明时没有指定类型的属性称为属性覆盖,它允许你改变一个属性继承下来的可见性或限定符。
类引用类型有时称为元类,用如下的构造形式表示 class of type
每个类从TObject 继承了两个分别叫做ClassType 和ClassParent 的方法,前者返回对象的类引用,后者
返回对象的父类类引用。这两个方法的返回值都是TClass(这里TClass = class of TObject)类型,它们
能被转换为更加明确的类型。每个类还继承了一个叫做InheritsFrom 的方法,它测试调用的对象是否从
一个指定的类派生而来(如果对象是类的一个实例,结果如何?)。这些方法被is 和as 运算符使用,很
少直接调用它们。
is 运算符执行动态类型检查,用来验证运行时一个对象的实际类型。
object is class
as 运算符执行受检查的类型转换。表达式
object as class
类方法是作用在类而不是对象上面的方法(不同于构造函数)。类方法的定义必须以关键字class 开始,
比如,
type
TFigure = class
public
class function Supports(Operation: string): Boolean; virtual;
class procedure GetInfo(var Info: TFigureInfo); virtual;
...
end;
类方法的定义部分也必须以class 开始,比如,
class procedure TFigure.GetInfo(var Info: TFigureInfo);
begin
...
end;
类方法既可以通过类引用来调用,也可以使用对象,当使用后者时, Self 值等于对象所属的类。
要创建一个异常对象,在raise 语句中调用异常类的构造函数。比如,
raise EMathError.Create;
通常,raise 语句的格式是
raise object at address
这里,object 和at address 都是可选的。
异常在try...except 语句中被处理,比如,
try
X := Y/Z;
except
on EZeroDivide do HandleZeroDivide;
end;
SysUtils 单元声明了几个标准例程来处理异常,它们包括ExceptObject、ExceptAddr 以及ShowException。
日期:2004年3月17日 星期三 天气:晴朗 作者:Napoleo
Delphi语法笔记3
发表:2004-3-17 15:36:54 出处:你的博客网(yourblog.org)
--------------------------------------------------------------------------------
在使用文件变量前,必须调用AssignFile 过程把它和一个外部文件相关联。外部文件通常是一个命名的
磁盘文件,但它也可以是一个设备,比如键盘或显示器。
一个已存在的文件可使用Reset 过程打开,使用Rewrite 过程能创建一个新文件并打开它。使用Reset 打开的
文本文件是只读的,使用Rewrite 和Append 打开的文本文件只能写入。对类型文件和无类型文件,不管用Reset 还是用
Rewrite 打开,它们都是可读写的。
对类型文件和无类型文件,能使用Seek 进行随机访问
标准函数FilePos 和FileSize 能用来确定当前文件位置和当前文件大小。
当程序完成一个文件的处理时,必须使用CloseFile 关闭文件。
每行以一个Eoln 标志(一个回车符,或许还跟一个换行符)结束
有两个标准文本文件变量,Input 和Output。Input 是一个只读文件,和操作系统的标准输入(通常是键
盘)相关联。Output 是只写文件,和操作系统的标准输出(通常是显示器)相关联。在程序执行前,Input
和Output 自动打开,
无类型文件使用关键字file 声明,没有其它内容。
var Datafile: file;
代替Read 和Write,BlockRead 和BlockWrite 两个过程用于高速数据传输。
System 单元提供了三个函数,WideCharToString、WideCharLenToString 和StringToWideChar,它们用来
把0 结束宽字符串转换为单字节或双字节长字符串。
动态调入库(dynamically loadable library)在Windows 下是一个动态链接库(dynamic-link library,DLL),
在Linux 下是一个共享目标库(shared object library)
导入过程或函数最简单的方法是用external 指示字声明它们,比如,
在Windows 下: procedure DoSomething; external ’MYLIB.DLL’;
在Linux 下: procedure DoSomething; external ’mylib.so’;
你可以直接调用操作系统的库函数来访问一个库中的例程,这些库函数包括LoadLibrary、FreeLibrary 和
GetProcAddress。在Windows 下,这些函数在Windows.pas 单元声明,在Linux 下,为了兼容性考虑,
它们在SysUtils.pas 单元实现,实际的Linux 例程是dlopen、dlclose 和dlsym(这些都在Kylix 的Libc 单
元声明)。此时,我们使用过程类型的变量来引用导入的例程。
当一个例程在exports 子句中列出时,它将被输出,它的格式如下
exports entry1, ..., entryn;
只有在Windows 下能使用索引说明符,它包括指示字index,后面跟一个介于1 到2,147,483,647 之间的
数字常量(为提高程序效率,使用较小的索引值)。若入口中没有指定索引,在输出表中例程被自动赋予
一个号码。
名称说明符包括指示字name,后面跟一个字符串常量。若入口没有名称说明符,例程被输出时使用声
明的原始名称,包括拼写和大小写。当要使用不同的名称输出一个例程时,使用name 子句。比如,
exports
DoSomethingABC name ’DoSomething’;
当在动态调入库中输出重载的函数或过程时,你必须在exports 子句中指定它的参数列表
exports 子句可出现在程序或库声明部分的任何位置,次数也不受限制,同样,当出现在单元的接口或实
现部分时,情况也是如此。程序很少包含exports 子句。
一个库的块(block)所包含的语句构成了库的初始化代码,每当库被调入时,这些代码执行一次。它们
的典型任务包括注册窗口类和初始化变量等。库的初始化代码也可以使用ExitProc 变量安装一个退出过
程(exit procedure),就像在Exit procedures 中描述的那样。退出过程在库被卸载时执行。
库的初始化代码通过设定ExitCode 变量为非0 来标记一个错误。ExitCode 在System 单元声明,默认值
时0。若库的初始化代码把ExitCode 设置为其它值,库将被卸载,调用程序被通知发生了错误。类似地,
若初始化代码执行中发生了未处理的异常,调用程序也将被通知调入库时失败。
在共享库中声明的全局变量不能被Object Pascal 程序导入。
使用IsLibrary 变量来确定代码是作为程序还
是库执行,IsLibrary 在程序中总是True,在库中总是False。在库的生命期内,HInstance 存储了它的
实例句柄,CmdLine 在库中总是nil。
DLLProc 变量允许一个库监测操作系统对它的入口点(entry point)的调用,这个特征通常只是由支持多
线程的库使用。DLLProc 在Windows 和Linux 下都存在,但用起来不同。在Windows 下,DLLProc 用
于多线程程序,在Linux 下,它用来判断库何时被卸载。对所有的退出行为,你应该使用finalization
sections,而不是退出过程。
在Windows 下,若DLL 输出的例程以长字符串或动态数组作为参数或者作为函数的返回值(不管是直
接的,还是通过记录或对象封装的),那么,DLL 和它的客户程序(或DLL)必须使用ShareMem 单元;
当一个程序或DLL 调用New 或GetMem 分配内存,而在另一个模块中调用Dispose 或FreeMem 来释放
内存时,上面的规则同样适用。ShareMem 单元应当在程序或库的uses 子句中第一个列出。
ShareMem 是BORLANDMM.DLL 内存管理器的接口单元,它允许在模块间共享动态分配的内存。
BORLANDMM.DLL必须连同使用ShareMem单元的程序和DLL一同发布。当程序或DLL使用ShareMem
时,它的内存管理器被BORLANDMM.DLL 中的取代。
Linux 使用glibc 的malloc 来管理共享内存。
为了区分包和其它库,包被存储在文件
• 在Windows 下,包的扩展名是.bpl(Borland package library)
• 在Linux 下,包通常以前缀bpl 开始,扩展名是.so。
通常,程序启动时包被静态调入,但你可以使用LoadPackage 和UnloadPackage 例程(在SysUtils 单元)
来动态调入包。
包源文件不包括类型、数据、过程或函数声明。取而代之的是,它包含
• 包的名称;
• 它所需要的其它包的列表。这些包被链接到新包中;
• 包被编译时所包含的(或绑定的)单元文件列表。包实际上是这些代码单元的一个外包装,这些单
元为编译后的包提供功能。
包的声明有如下形式:
package packageName;
requiresClause;
containsClause;
end.
package DATAX;
requires
baseclx,
visualclx;
contains Db, DBLocal, DBXpress, ... ;
end.
在Windows 下,一个程序的堆栈由两个值定义:堆栈的最小值和最大值。这两个值受编译器指示字
$MINSTACKSIZE 和 $MAXSTACKSIZE 所控制,它们的缺省值分别是16,384(16K)和1,048,576
(1M)。在Linux 下,堆栈大小只能由环境设置。
在register 约定下,最多有3 个参数可通过CPU 寄存器传递,其余(若有的话)参数被传递到栈。此时,
参数以声明的顺序(和pascal 相同)被传递,前3 个有资格的参数分别使用EAX、EDX 和ECX 寄存器。
过程和函数必须保留EBX、ESI、EDI 和EBP 寄存器,但可以修改EAX、EDX 和ECX。当在汇编语言
中实现构造和销毁时,保证预留DL 寄存器。过程和函数被调用时,是假定CPU 的direction 标志是清除
的(对应于CLD 指令),并且返回时,direction 标志也必须是清除的。
以下约定适用于函数的返回值:
可能的话,有序类型通过寄存器返回值:字节通过AL 返回,字通过AX 返回,双字通过EAX 返
回。
实数类型的返回值在浮点协处理器的栈顶寄存器(top-of-stack register,ST(0))。对于Currency 类
型的返回值,ST(0)中的值被乘以10000。比如,Currency 值1.234 在ST(0)中的值为12340。
对字符串、动态数组、方法指针、Variant、或Int64 类型的返回值,就像函数在其它参数的后面额
外声明了一个var 参数。换句话说,是函数调用者传递一个额外的32 位指针,它指向的变量用来
返回结果。
指针、类、类引用和过程指针类型,结果通过EAX 返回。
对静态数组、记录和集合类型,若结果占用1 个字节,它通过AL 返回;若结果占用2 个字节,
它通过AX 返回;若结果占用4 个字节,它通过EAX 返回。否则(结果超过4 个字节),结果通
过一个额外的var 参数返回,它在所有声明的参数的后边。
在register 调用约定下,Self 就像在所有其它参数的前面声明,所以,它总是通过EAX 寄存器传递。
在pascal 调用约定下,Self 就像在所有其它参数的后面声明(有时还要包括返回函数值的额外的var 参
数),所以,它最后被压入栈,所在的地址比其它参数要低。
像类一样,接口只能在程序或单元的最外层声明,而不能在过程或函数中声明。一个接口类型的声明有
如下格式
type interfaceName = interface (ancestorInterface)
[’{GUID}’]
memberList
end;
• memberList 只包括方法和属性,字段在接口中是不允许的;
• 因为接口没有字段,所以属性的读(read)和写(write)限定符必须是方法;
• 接口的所有成员都是公有的(public),不允许使用可见性限定符和存储限定符(但一个数组属性能
被声明为default);
• 接口没有构造函数和析构函数,它们不能被(直接)实例化,除非使用实现了它们(的方法)的类;
• 方法不能被声明为virtual、dynamic、abstract 或override。因为接口自己不实现它们的方法,这些
声明没有意义。
在某些接口声明中,interface 关键字被换成了dispinterface,这种构造(连同dispid、readonly 和writeonly
指示字)是平台相关的,不能在Linux 程序中使用。
声明一个接口时可以指定一个祖先接口,如果没有指明的话,则它直接继承自IInterface。IInterface 在
System 单元定义,是其它所有接口的根类。IInterface 定义了三个方法:QueryInterface、_AddRef 和
_Release。
注意:IInterface 和IUnknown 是相同的。考虑到平台无关性,通常要使用IInterface;IUnknown 最好用
在一些特殊的程序中,它依赖于Windows 平台。
默认的调用约定是register,但当接口在程序模块(尤其当它们用其它语言编写时)间共享时,需要声
明所有的方法为stdcall 调用方式;实现CORBA 接口时使用safecall 调用约定;在Windows 下,你可以
用safecall 来实现双重调度接口的方法。
接口声明的属性只能通过接口类型的表达式进行访问,类类型的变量不行;并且,接口的属性只在接口
被编译的程序中是可见的。比如,在Windows 下,COM 对象没有属性。
在接口中,属性的读和写必须通过方法来完成,因为不存在字段。
因为每个接口都继承自IInterface,所以,一个实现接口的类必须实现QueryInterface、_AddRef 和
_Release 方法。System 单元中的TInterfacedObject 实现了这些方法,所以,其它实现接口的类可以方便
地通过继承它来实现。
implements 指示字允许你在实现类中委托一个属性来实现接口,比如
property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
上面声明了一个叫做MyInterface 的属性,它实现了接口IMyInterface。
在属性声明中,implements 指示字必须是最后一项,它可以实现多个接口,接口之间以逗号分隔。
实现委托接口的类应当从TAggregatedObject 派生。
如果委托的属性是接口类型,那么此接口(或者它的派生接口)必须出现在类声明中的祖先列表中(也
就是声明实现这些接口)。委托的属性必须返回一个对象,此对象所属的类完全实现了implements 所指
明的接口,并且没有使用方法解析子句。
如果委托属性是一个类类型,那么在定位实现的方法时,会先搜索这个类以及它的祖先类,然后再搜索
当前类(也就是定义属性的类)以及它的祖先类。所以,可以在属性指定的类中实现某些方法,而另一
些方法在当前类实现。可以象平常一样使用方法解析子句来避免含糊的声明,或者(只是)使用一个特
别的方法声明。一个接口不能委托给多个类类型的属性实现。
如果你声明一个接口类型的变量,则它可以引用任何实现这个接口的类实例。这样的变量使你可以调用
接口的方法,而不必在编译时知道接口是在哪里实现的。但要注意以下限制:
• 使用接口类型的表达式只能访问接口定义的方法和属性,不能访问实现类的其它成员;
• 一个接口类型的表达式不能引用实现了它的派生接口的类实例,除非这个类(或它继承的类)还明
确实现了此祖先接口。
接口引用通过引用计数进行管理,它依赖于从IInterface 继承的_AddRef 和_Release 方法。若一个对象只
通过接口来引用,我们没必要手动销毁它,当它最后的引用超出范围时,它会自动销毁。
全局类型的接口变量能只被初始化为nil。
要判断一个接口类型的表达式是否引用了一个对象,通过标准函数Assigned 来完成。
一个类和它实现的任何接口是赋值兼容的,一个接口和它的任何祖先接口是赋值兼容的。nil 可以被赋给
任何接口类型的变量。
一个接口类型的表达式可以被赋予一个变体类型(Variant):若接口类型是IDispatch 或它的后代,则
Variant 变量的类型码是varDispatch,否则为varUnknown。
类型码为varEmpty、varUnknown 或者varDispatch 的Variant 变量,可以赋给IInterface 类型的变量;类
型码为varEmpty 或varDispatch 的Variant 变量,可以赋给IDispatch 类型的变量。
若对象所属的类实现了IDispatch 接口(在System 单元声明),则此对象是一个自动化对象。自动化对象
只适用于Windows。
派遣接口类型定义了一个自动化对象的方法和属性,它们通过IDispatch 接口来实现。调用派遣接口的方
法是通过在运行时调用IDispatch 接口的Invoke 方法来实现的,a class cannot implement a dispatch
interface。
派遣接口声明具有如下格式:
type interfaceName = dispinterface
[’{GUID}’]
memberList
end;
除了dispid,在派遣接口中声明的方法不能使用其它指示字,它的参数以及返回值必须属于自动化类型,
也就是说,必须是Byte、Currency、Real、Double、Longint、Integer、Single、Smallint、AnsiString、WideString
双重接口既支持编译时绑定,也支持通过自动化动态绑定(运行时)。双重接口必须从IDispatch 接口派生。
双重接口的所有方法(除了从IInterface 和IDispatch 继承的方法)必须使用safecall 调用约定,并且方
法的参数和返回值必须是自动化类型。(自动化类型包括Byte、Currency、Real、Double、Real48、Integer、
Single、Smallint、AnsiString、TdateTime、Variant、OleVariant 和WordBool)