<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} -->
单元 程序头: 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 对话框。
<Leo> 在窗体设计器上右击弹出 PopMenu 有 View as Text 项,可察看事件和过程的关联
事件句柄通过窗体文件( 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 选项,此时在整个项目范围使用完全计算。
<Leo>/// 部分计算 equals 短路计算 in C++/C#
若任何一个运算数是 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 。
<Leo>Inc() 是什么呢?
对 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;
<Leo> 类方法类似 C++/C# 里的 static 成员????
类方法既可以通过类引用来调用,也可以使用对象,当使用后者时, 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 )