所谓标准标识符是指由 Delphi 预先定义的标识符,用于标识 Delphi 预先定义的函 数,变量等。主要有以下几种:
1. 标准常量及变量名称,如 FALSE、TRUE 等。
- 标准类型名称,如 Integer、Real 等。
- 标准例程名称,如 Sin、IntToStr 等。
4. 标准文件名称,如标准 I/O 名称 Input、Output 等。
5. Delphi2010 的保留字及指令符,如 procedure、class 等。 在选择标识符时,应尽量与标准标识符相冲突,否则可能会造成想不到的结果。
2.1.2 自定义标识符
顾名思义,自定义标识符就是程序员根据需要所定义的名称,一个合法的自定义标识 符需满足以下条件:
1. Delphi 语言不区分大小写,标识符亦然,如 PASCAL 与 pascal 将被视为同一标识 符。
2. 自定义标识符不能和当前域中的其它标识符相同。
3. 标识符长度应小于或等于 255 个字符。若超出此长度,超出的部分将被舍弃,只取
前 255 个字符。
4. 标识符由英文字母、下划线、数字组成,不包含空格,第一个字符不能是数字。
实际上,Delphi2010 采用 unicode 字符集,其标识符可由任何 unicode 中的字符组成,但 有一个小小的例外:

Unicode 的前 256 字符对应于 ansi 字符集的 256 个基本字符,在这些字符的前 128 个 字符中,只有英文字母和数字及下划线'_'可用于标识符,其它如英文标点、空格等字符均 不能用于标识符。
如英文标点‘?’不可作为标识符,但中文中的‘?’却可以。因为中文的‘?’不属 于英文字符。这也是为什么在有些早期版本中不支持中文作为文件名的原因。
之所以不能使用英文字符,是由于英文中的符号往往有其特殊用途,如‘:’用于声明 变量,‘$’用于标识十六进制数字,这类系统预定义的符号称之为特殊符号。Delphi2010 中的特殊符号有:



# $ & ' ( ) * + , - . / : ; < = > @ [ ] ^ { } 以下的组合符号亦为特殊符号:
(* (. *) .) .. // := < = > = <>

其中,‘(.’组合等效于‘[’,‘.)’组合等效于‘]’,‘(*’组合等效于‘{’, ‘*)’组合等效于‘}’。 除此之外,以下符号虽非特殊符号,但亦不能用于标识符:
% ? \ ! " | ~
2.1.3 标识符的作用域
如同中国的法律只能使用于中国境内,每个标识符只在某个特定范围内有效。按照有 效范围的大小可将标识符分为全局标识符及局部标识符。
局部标识符是指定义于例程(函数或过程)中的标识符,此种标识符只能用于定义它们 的例程。举例子来说,局部标识符类似于北京市出台的政策,这些政策只能用于北京。
除局部标识符外的所有标识符均为全局标识符。定义于.pas 文件的全局标识符按照定 义时所处位置分类,可分为公有及私有标识符。所谓公有标识符是指定义于.pas 文件的 Interface 部分的全局标识符,其它为私有标识符。二者在定义它们的.pas 文件中的有效 范围均为定义时所处的位置到.pas 文件的末尾。不同的是,公有标识符不但在本.pas 文件
中有效,在所有引用了本.pas 文件的其它文件如.dpr 文件中也有效,故称其为公有。
2.2 保留字与限定符
所谓保留字是指由 Delphi 预先规定只能由 Delphi 使用的单词,如最为常见的 begin、end 等等。理所当然地,这些保留字不能作为自定义标识符的名称。读者并不需要 记住这些保留字,默认情况下,Delphi2010 的 IDE 会自动将这些单词以深蓝色显示,使用 时稍加留意即可。
指令符不同于保留字,这些单词只在特定的位置上有特殊意义,如 virtual 只在定义 虚函数时作使用。在其它位置,指令符与一般标识符没有区别。随意使用指令符作为标识 符可能会导致意料之外的结果,故不建议使用指令符作为标识符。与保留字相同,当指令 符位于其特定位置上时,IDE 将会以特定颜色显示。
为方便叙述,在本书我们将用关键字同时代表保留字与限定符。 Delphi2010 的保留字及指令符如下:
表 1 Delphi2010 保留字
|
and |
else |
inline |
property |
try |
|
array |
end |
interface |
raise |
type |
|
as |
except |
is |
record |
unit |
|
at |
exports |
label |
remove |
until |
|
asm |
file |
library |
repeat |
uses |
|
begin |
finalization |
mod |
resourcestring |
var |
|
case |
finally |
nil |
set |
while |
|
class |
for |
not |
shl |
with |


|
const |
function |
on |
shr |
xor |
|
constructor |
goto |
of |
strict private | |
|
destructor |
if |
or |
strict protected | |
|
dispinterface |
implementation |
out |
string | |
|
div |
in |
packed |
then | |
|
do |
inherited |
procedure |
threadvar | |
|
downto |
initialization |
program |
to |
表 2 Delphi2010 指令符
|
absolute |
dispid |
helper |
near |
private |
reintroduce |
stored |
|
abstract |
dynamic |
implements |
nodefault |
protected |
requires |
unsafe |
|
assembler |
experimental |
index |
operator |
public |
resident |
varargs |
|
automated |
export |
inline |
overload |
published |
safecall |
virtual |
|
cdecl |
external |
library |
override |
read |
sealed |
winapi |
|
contains |
far |
local |
package |
readonly |
static |
write |
|
default |
final |
message |
pascal |
reference |
stdcall |
writeonly |
|
deprecated |
forward |
Name |
platform |
register |
strict |
delayed |

注:指令符 private、protected、public、published 及 automated 在定义一个类时被视 作保留字,但在 其它场合则被视为指令符。
2.3 常量
所谓常量就是在第一次赋值后不能改变其值的量。Delphi 支持两种类型的常量:一 种是直接常量;另一种是声明常量。除此之外,Delphi 本身还预定义了一些常量,如常用 的 False,True 以及 Nil 等。
直接常量是指在程序中直接引用的量,这种常量不需要有标识符来表示,其本身就是 一个确定值,如数字 123 只能代表这个数值,字符串‘abcd’也只能代表这个字符串一 样。常用的直接常量类型及书写规则如下:
(1). 整型常量:即一个数学整数。如 123,43 等。其前可加负号‘-’表示负整数。 整数加上符号‘$’可表示十六进制数,如$12 表示十六进制数 12,转化为十进制数为 18。
(2). 实型常量:即数学上所说的小数,由数字和小数点构成。注意,若无小数点,系

统会将常量解析成整数,故小数点不可缺。实型常量还可用科学记数法表示,如 0.0023 用 科学记数法表示为 2.3E-3,E 表示 10 的次方,如 1.2E3 表示 1200。 注意:所有带小数点的实数全部被推断为 Extented 类型
(3). 字符及字符串型常量:任意字符,若两边以单引号包围,即为字符或字符串。若 只有一个字节,则系统将其定义为字符;若大于一个字节,则定义为字符串。如‘a'为字 符,‘12345'为字符串。
(4). 布尔型常量:此类型常量常用的只有二个值:FALSE 和 TRUE,分别表示逻辑真和


逻辑假。
与直接常量不同,声明常量必须用一个合法标识符表示,且常量在定义时必须同时赋 值,一旦定义后代表, 此标识符的值不可再次被更改,否则会导致编译错误。声明常量可 分为符号常量及类型常量。
符号常量亦称为真常量。此类型常量用一个标识符代表一个具体值,编译器在解析代 码时,遇到此标识符直接将其替换成所代表的值,这有点类似于一个值不变的变量。声明 符号常量可使用下列格式的语句:
Const
<标识符 1> = <常量值 1>;
„
<标识符 n> = <常量值 n>;
Const 是 Delphi 的保留字,表示定义常量的开始,在书写时 Const 也可单独写成一 行,不过为了代码的美观性,建议参照本书的代码书写风格。
常量值可以是直接常量或其它符号常量,也可以是二者组成的表达式。如下面的下面 的声明均合法:
Const
CONS_A = 123;
CONS_B = CONS_A+234; CONS_C=CONS_A+CONS_B+ 89;
编译器在编译时将根据值的类型推测常量的类型。也就是说,上面声明的三个常量均
属于数值型,若将其中的 CONS_B 的声明改成: CONS_B = CONS_A + ' delphi ';
编译将会出现错误,因为编译器将 ' delphi ' 识别为字符串型常量,将 CONS_A 识别 为是数值型常量,二者不能相加。
类似于直接常量,编译器在编译代码时会按照与直接常量相同的规则推断符号常量的 类型。
在声明字符串常量时可以使用 resourcestring 来声明: resourcestring
str = 'The Current Edition Is Delphi2010'; 这种方式声明的字符串用于程序中时将被编译至资源文件中。这样做的好处是可以任意修 改字符串而不需要重载编译程序。
符号常量虽然方便,但它有两个弱点: 其一,它只能表示一些简单的值,较复杂的类型如函数指针等很难用符号常量表示。 其二,不能精确地控制其类型。例如,若要定义一个能容纳 3 个字符的字符数组,按
如下方式定义:

Const
ChArray = 'abc';
编译器会将常量 ChArray 理解成是一个字符串而并非字符数组。 为了解决这些不足,可以使用类型常量。与符号常量不同,类型常量在声明时必须显
式指定其类型并赋值。其声明格式如下: Const
<标识符 1> : <类型 1> = <常量值 1>;
„
<标识符 n> : <类型 n> = <常量值 n>; 其中的标识符的规定与符号常量一致,常量类型可以是内置类型,也可以是自定义类
型。常量值必须与指定的类型一致或兼容于指定的类型,所谓兼容是指常量值的类型经类 型转化能够转化至声明语句中指定的类型。注意,不能将常量声明为文件类型和变体类 型。与符号类型不同,类型常量的常量值中不能含有其它常量。如:
Const
Name:string = 'delphi2010'; //合法
Caption:string = 'my'+name; //不合法,name 是另一个常量,不可出现在表 达式中
MultiPi:real = 2*3.14; //合法,MultiPi 的值为 6.28,‘*’代表乘号
若指定的类型是数组、记录、过程或指针,则另有特定的规则。

[ 注 ]:IDE 中开启{$J+}编译命令时,类型常量可被重新赋值,此时类型常量与普通 变量没有太大区别。但在 Delphi2010 版本中,此开关默认关闭。若无意中开启,只需在程 序中加入{$J-}命令即可关闭。
Delphi 中有些类型由于本身的缘故,其常量声明并非那么直观。接下来我们简要看一 下几种特殊类型的常量的声明,这需要后面的数据类型的相关知识,所以若读者看的不是 很明白也没关系。

数组常量的声明主要有三种:静态数组、字符串、多维数组。 声明静态数组常量时多个成员值之间以逗号隔开,所有的值以一对圆括号围起:
const
Digits: array[0..9] of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8',
'9');

序数从 0 开始的字符数组与 Null 结尾的字符串兼容,将一个字符数组常量初始化为字 符串时,可直接赋值:
const

Digits: array[0..9] of Char = '0123456789'; 声明多维数组常量就比上面要麻烦一点,如对于如下声明:
type
TCube = array[0..1, 0..1, 0..1] of Integer; 在声明这个多维数组的常量时需要从最低维到最高维进行赋值。
首先,第一维有二个成员,所常量的表示形式为(X,Y)。第一维中的每个成员本身 又包括两个成员,所以无论是 X 还是 Y,其形式都是(X1,Y1)。所以 TCube 的常量形式
- 19 -

又可写成((X1,Y1),(X2,Y2))。TCube 是三维数组,所以 X1,Y1,X2,Y2 又有两 个成员:(X11,Y11)。这样一来,整个的数组展开后的形式为:

( ((X11,X12),(Y11,Y12)), ((X21,X22),(Y21,Y22)) )。 所以 TCube 类型的常量 Maze 的声明为:
const
Maze: TCube = (((0, 1), (2, 3)), ((4, 5), (6,7)));

这个常量数组中各成员的值为: 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
注意:数组常量不能含有文件变量,数组的基类型也不可以。严格来说,数组常量不能以 任何形式含有文件变量。
声明记录常量时需要以“字段名:字段值”的形式指定每个字段的常量值,各字段间 以分号隔开。各字段的赋值顺序必须与声明时的顺序一致。
对于变体记录,若其中出现了一个 tag 值,则必须赋值;只有含有 tag 值时变体记录 中的变体部分才能被赋值。

与数组一样,记录常量中不能含有任何形式的文件变量。 type
TPoint = record X, Y: Single;
end;
TRec = record x:integer;
case tag:integer of 1:(i:integer);
2:(n:integer); end;
const
Origin: TPoint = (X: 0.0; Y: 0.0);
VRec: TRec = (x:7; tag:1; i: 2;); 6. 指针常量
指针常量在声明时可以使用任意一个变量的地址作为常量值。对于 PChar 或 PWideChar,声明时可直接使用一个字符串序列作为常量值。

指针常量定义后,指针所指向的变量值可被改变,但指针不允许重载指向其它变量。 如对于如下声明:
var
i, n: integer;
- 20 -
const
pi: ^integer = @i;

i 的值可随时改变,但指针 pi 只能指向 i,不能再指向其它变量。
变量类似于符号常量,不同的是变量可以被重新赋值。根据存储方式可将变量分为动 态变量与静态变量。静态变量在定义时就已经确定其所需内存的大小,而动态变量则在运 行期间根据需求分配合适的内存。
Var
其声明格式如下:
<标识符 1> : <类型 1>;
„
<标识符 n> : <类型 n>;



声明多个类型相同的变量可采用如下简化形式: Var
<标识符 1>,„<标识符 2>:<类型>;
声明语句中,Var 保留字表示声明的开始。标识符为任意合法标识符,但习惯上选取 含义直观的标识符,这样做可增加代码的可读性;类型可以是内置或自定义类型。

在某些时候我们可能需要声明一些变量或例程,其名称与 Delphi 中的某个关键词相 同,在这种情况下,我们可以在这些变量名前加上符号"&"来区分这些名称,如:
var
声明变量后应对其进行初始化,最常见的初始化方式是对其赋值。变量的赋值可使用 符号' := '其格式为:
变量名 := 变量值;
如 v := 3; 赋值语句右边也可以是表达式。如:
v := 3*4; 或
v := function(3); //假设 function 为一函数,其返回 3 的平方
对变量进行赋值时,若所赋之值不在变量的值域之内,会出现两可能的情况: (1):编译器提示错误。此类错误可能是所赋之值与变量并非同一类型,也有可能是所赋之 值太大或太小。 (2):编译器不给出错误,但计算机会将所赋之值与变量能够表示的最大值进行求余运算, 并将结果赋给变量。
- 21 -

变量手动赋值前会由系统赋值,对于不同类型的变量系统有不同的规则。对于全局变 量,系统一律初始化为 0,若是指针则初始化为 nil;若是字符串则初始化为一个空字符 串。对于局部变量系统会随机赋值,但这种赋值是很不可靠的,所以对于局部变量一定要 手动初始化之后才能用。
在声明全局变量时也可直接手动进行初始化。其格式为:

变量名:类型 = 初始值; 例如:
Var
GlobalVar: integer = 100 ;

注意:GlobalVar 必须为全局变量,且不能为变体或文件类型。采用简化形式在同一行声 明多个同类型变量时不能手动初始化。
3. 共址变量(Absolute Addresses) 共址变量是这样一种变量:声明时可以将其与其它的某个变量相绑定,绑定后二个变

量在内存中存储位置的起始地址相同。声明共址变量的格式为:
var
v1:type1 absolute v2;

其中 v2 是一个声明过的变量。程序在编译时会将 v1 与 v2 的起始地址设置为相同位置。我 们以一个例子来说明:
var
c: AnsiChar;
i: Byte absolute c; begin
c := 'A';
writeln(i); //屏幕显示 65
readln; end.
上例中 i 被声明成变量 c 的共址变量,这样一来变量 i 与变量 c 将在内存中的起始位置相
同。i在内存中占用一个字节,当攻取i的值时,系统会将变量c的第一个字节的值当成 是 i 的值,而 c 中第一个字节的值是字符‘A’,故 i 的值为 65。

同理,下面例子中变量 leng 的值为字符串 str 的第一个字节的值(即为字符串的长 度):
var
str: ShortString;
leng: Byte absolute str; begin
str := 'Delphi'; writeln(leng); //屏幕显示 6
readln; end.
需要说明两点:
1. 共址变量具有相互性,若将上例中的声明改为如下形式结果将不会发生变化:
- 22 -

var
i: Byte;
c: AnsiChar absolute i;


2. 共址变量并非仅限于两个变量,可以声明多个变量为共址变量: var
i:Byte;
c:AnsiChar absolute i;
str: ShortString absolute i;
2.5 类型声明
除了变量及常量,在必要时我们还可以自已定义一个数据类型用于声明变量和常量。 声明一个新的数据类型必须使用关键词 type,格式如下:
type
类型名称 = 类型表达式;
类型名称依然可以是任意一个不与其它标识符冲突的合法标识符,类型表达式可以是一个 结构如 string[3]、array of char„,或者是另一个类型名称。下面声明了两个自定义类 型:
type
aSet = set of char; //声明一个子界类型 aSet myInteger = Integer; //声明一个整数类型,其名为 myInteger
若类型表达式是另一个类型名称,则在我们声明的新类型与这个类型完全等同,相当 于给同一种数据类型指定了另一个名称。如上面声明的 myInteger 与 Integer 类型在任何 时候都完全等同。举个例子,数据类型的名称就相当于是一个人的名字,myInteger 与 Integer 均是同一个人的名字。
Delphi 定义数据类型时还有另一种方式,这种方式与上面的方式有微妙的区别,其格 式为:
type
类型名称 = type 类型表达式;
我们用这种方式重新声明 myInteger: type
myInteger = type Integer;
其中的 myInteger 与 Integer 不再是同一个人的名称,而两个人的名称。 只不过这两个人 的各个方面均完全相同(除了名称)。一般情形下这种细小的差别不会被注意到,但在牵 涉到诸如数据类型的动态判断之类的较高级的用法时,这种差别会导致类型的不兼容。
当类型表达式是一个结构时,任意两个以结构本身声明的数据类型间均有差别,如下 面声明了两个子界类型:
type
aSet = set of Char; bSet = set of Char;
- 23 -

从表面上看来,aSet 与 bSet 应当完全一致,但事实并非如此,bSet 与 aSet 之间相当于以 下声明:
type
aSet = type bSet; 或是
bSet = type aSet;
若要 aSet 完全等同于 bSet,可使用如下的声明方式: type
aSet = set of Char; bSet = aSet;
从作用上来说,注释用于提供代码的相关信息使用得读者阅读时能够更容易理解代码 的含义。编译时编译器会忽略所有的注释部分。Delphi 中提供三种注释方式://„、

{„}、(*„*),如下所示: var
I, J, V, W: Integer; begin
I := 4; //I 的值为 4
V := 4;
J := DoubleByValue(I); { J = 8, I = 4 (*DoubleByValue 的参数通过值传递*) }
W := DoubleByRef(V); (* W = 8,{DoubleByRef 的参数通过引用传递}
V = 8 *)
end; 其中//„只能用于单行注释,其余两种则可用于多行注释。
在注释中套嵌其它注释时必须用不同的注释方式。如上面例子中的{ J = 8, I = 4 (*DoubleByValue 的参数通过值传递*) },在{„}中套嵌其它注释时只能选用(*„*)或
//„方式。

注:当以 (*„*) 或 {„} 注释时,若注释内容的第一个字符为’$’,此注释表示编译器 提示符,用于告知编译某些信息。如{$APPTYPE CONSOLE}表示当前程序是一个命令行 程序
能够返回一个确定的值的合法语句均可以称之为表达式。如: 3*4 //数学表达式,其值为 12
78 < 90 //逻辑表达式,其值为 True
Char(65) //数值转型,其值为字符'A'
- 24 -

常见的最简单的表达式为变量及常量。表达式必须有一个返回值,Delphi 的过程调用 不返回任何值,故过程调用不属于表达式。同理,赋值语句:
i := 123; 此语句不返回任何值,故而也不是表达式。而逻辑判断语句: I >= 7;
此语句返回逻辑值(TRUE 或 FALSE),故属于表达式。
注意:Delphi 中的表达式不能单独存在,只能位于赋值符号“ := ”的右边。
2.7.语句
语句定义了程序中的一个演算行为。简单语句如赋值或函数调用可被包含进循环、条 件或结构语句中。块、单元的 initialization 及 finalization 部分中的多条语句中用分 号隔开。
Delphi 中的一条语句由一个或多个表达式、关键字或者运算符(符号)组成。一般来 说,在编写代码时一条语句占一行,不过这并非绝对,在需要时也可以将两条或更多条语 句也可以写在同一行上,每行语句应当以分别结束。
简单是指不包含任何其它语句的语句,常见的简单语句有赋值语句、例程调用语句及 goto 语句。
赋值语句的形式如下:
变量名 := 变量值; 句中的” := ”称为赋值符号。变量名可以为任何变量。变量值可以是一个常数值,可以 是另一个变量值,也可以一个函数,但无论如何,赋值符号右边的部分必须能返回一个 值。

对于不接受参数的过程和函数,在调用时可省略例程名后的括号。如: procedure M1;
...
M1;
...

对于函数,调用时既可以将其返回值赋给一个变量,也可单独调用: procedure M1:integer;
...
var
i:integer;
...
- 25 -

i := m1; m1;
...

Goto 语句可使程序离开当前位置直接跳至某个特定的语句执行,其形式为:
goto label; 后面的 label 表示语句标签,执行 goto 语句会直接导致程序跳到 lable 标记的位置。
大部分能够声明变量的地方都可声明一个标签,声明格式为:
label 标签; 标签可以是任何一个合法标识符或一个 0 至 9999 间的数值。理所当然,Goto 语句及其中 的标签必须在当前范围内必须都有效,鉴于此,在窗体程序中不能定义一个全局标签,除
非是在单元的 initialization 及 finalization 部分。
由于 Goto 语句不利于程序的调试,而且它会造成程序的某些不可捉摸的行为,所以不 推荐使用 Goto 语句。

下面是 Goto 使用的一个例子: var
i:integer; label 1,2; begin
1:writeln('请输入 i 的值:');
readln(i);
if i <> 0 then goto 1;
exit; end.
这个程序会要求输入一个数字作为 i 的值,若输入的值为 0 则程序自动退出,若输入的值
不为 0,则程序会要求再一次输入 i 的值。
结构语句由多条简单语句组成。Delphi 中的结构语句包括复合语句、with 语句、条件 语句、循环语句等。本部分只介绍复合语句和 with 语句。其它类型语句会在后续部分介 绍。

多条简单语句使用 begin 和 end 包围后即为复合语句,如: begin
Z := X;
X := Y; //可以省略分号: X := Y end;
- 26 -

如果读者比较懒,那么复合语句中最后一条简单语句后的分号可以省略。 一个复合语句中可以含有其它任何类型的结构语句。在必要时也可以定义一段空的复

合语句: begin end;

与很多其它的工具一样,Delphi 支持在其中嵌入汇编代码。Delphi 的汇编代码使用 asm 与 end 包围:
var
a:word; begin
asm
mov ax,43; add ax,54; mov a,ax;
end; writeln(a); readln;
end.
本部分较多的牵涉到后面的内容,所以如果读者不熟悉的话,建议看完后面的内容后 再回过来看一下本部分的内容。

先来看一个例子。如对于如下声明: type
TDate = record Day: Integer; Month: Integer; Year: Integer;
end; var

OrderDate: TDate; 你可以使用如下的方式来访问其中的字段:
if OrderDate.Month = 12 then begin
OrderDate.Month := 1;
OrderDate.Year := OrderDate.Year + 1; end
else
OrderDate.Month := OrderDate.Month + 1;
这个例子中使用了 OrderDate 中的多个成员,每个成员均使用对象名来限定。虽然这样没
- 27 -

什么问题,但每次都要键入一启蒙对象名是不是有点烦呢?

使用 with 语句可以避免过多的输入。利用 with 语句我们可以将这些代码写成如下形 式:
with OrderDate do if Month = 12 then
begin
Month := 1;
Year := Year + 1;
end else
Month := Month + 1; 如何?读者喜欢哪种写法?
通过这个例子,可以发现使在引用同一标识符的多个成员时,使用 with 语句可以极大 的方便编码,其声明方式为:

with obj do 语句
with obj1, obj2 do 语句
其中的 obj 可以是任何能引用字段的标识符,如记录名、对象名、接口名等,还可以是指 针的解引用形式、引用等。其后的语句可以简单语句,也可骒复合语句。

对于 with 语句出现的所有的单独的标识符,编译器优先在 obj 中寻找其含义,换句话 说对于如下编译:
type
TRec = record x,y:integer;
end; var
obj:TRec; x:integer;
begin
with obj do begin
x := 3;
end; writeln(obj.x);
end.
可以看到,在 with 语句中赋值的是 obj 中的 x 成员。若要在 with 中赋值给全局变量 x, 可以使用限定形式上述的赋值代码改为如下形式:
project1.x := 3; 当 with 后有多个 obj 时相当于不断的套嵌,例如 with obj1, obj2, ..., objn

do ...相当于: with obj1 do
with obj2 do
...
with objn do
...
- 28 -


在这种情形下,编译器会从最内层的 objn 开始寻找标识符,若未寻找到则在外面一层的 obj 寻找,依次类推,一直到 obj1,若还未寻找到,则在 with 语句之外寻找。如下面的例 子所示:
type
TInner = record x,y:Integer;
end;
TRec = record x,y:integer; z:string;
end; var
Rec:TRec; Inner:TInner; x:integer; z:string;
begin
with rec,inner do begin
x := 3;
z := 'delphi'; end; writeln(Inner.x); writeln(rec.z); readln;
end.
2.8 块和域
Delphi 中大部分的标识符声明及执行语句都被组织成块,然后组成整个源代码。使用 块的最大好处就是可以使同一个名称可以在不同的地方表示不同的含义。每个程序、函 数、过程均含有一个块。
一个块由声明部分与语句部分组成,语句部分使用 begin 与 end 围起。块的一般形式 为:

...//声明部分 begin
...//语句部分 end;
块的声明部分可以声明任何标识符,包括变量、常量、函数、数据类型及标签等。这些声 明原则上没有先后之分。在某些源文件中可能还会看到声明部分出现了若干个 exports 从 句。



下面是一个函数的声明:
function UpperCase(const S: string): string; var
Ch: Char; L: Integer;
Source, Dest: PChar; begin
...
end;
这个声明中的第一行称为函数首部,它声明了一个函数名称 UpperCase。这行语句与其后 的 4 个变量的声明一起构成了块的声明部分。
块中声明的标识符具有局部性,它们只当前的块中。如上面的示例中声明的四个变量 Ch、L、Source、Dest 均只能用于声明它们的块当中。
域指标识符的有效范围。声明于不同位置的标识符具有不同的有效域。 声明于块中的标识符只能用于这个块中,这些标识符称为局部标识符,若是变量则为
局部变量,若是常量则为局部常量。
声明于单元的 Interface 部分的标识符的有效域为任何一个引用这个单元的源文件。 这种标识符称为全局标识符。若这个标识符是一个变量名,这个变量为全局变量;若为常 量名则为全局常量„„。
我们使用下面这张表来总结标识符的域:
|
标识符的定义位置 |
有效域 |
|
程序、函数、过程的声明部分 |
从定义位置起始,到块的末尾,包括所有的 子块 |
|
单元的 Interface 部分 |
从定义位置开始,到单元的末尾,也可以是 任何引用了这个单元的其它单元 |
|
单元的 Implements 部分,但不在任何块内部 |
从定义位置开始,到单元的末尾,包括此部 分 中 所 有 的 块 内 部 , 以 及 单 元 的 initialization 和 finalization 部分。 |
|
记录的内部 |
从定义位置到记录末尾 |

当一个块包含了另一个块时,前者称为外部块,后者称为内部块。内部块的标识符会 掩盖外部块的标识符,例如在下面的代码中,被赋值是声明于函数 M2 中的 s:
function M1:integer; var
s:string;



function M2:integer; var
s:string; begin
s := 'delphi'; end;
begin end;
在第一章,我们介绍了程序或单元可以通过 uses 从句来引用其它单元。这种特性使得
编译器在确定标识符的含义时需要进行更为复杂的搜索:uses 从句中列出的每个外部单元 都替当前单元或程序强加了一个新的域。换句话说,由于 uses 的作用,使得我们可以访问 一些其它单元中的原本不能访问的标识符。
为解决这个问题,Delphi 规定:uses 从句中的第一个单元为最外层,其次为次外 层...。但出乎你的意料,最内层的单元并不是 uses 从句最后出现的单元。所谓近水楼台 先得月,正在使用这个标识符的的单元才是最里层的单元。这个规定有一个例外:system 和 sysinit 单元会在每个程序或单元中自动引用,它们永远是最外层单元。
若两个或更多的单元在 interface 部分中声明了同一标识符,则任何一个未限定的标 识符都会被认为是最里层的标识符。限定标识符也很简单,直接使用“名称.标识符”的形 式即可,如 Module.Fun 表示目前引用的是 Module 中的 Fun。其中的 Module 可以是单元名 或工程名,也可以是结构类型的变量名。
类型兼容性
当 A、B 两种数据类型满足以下条件时,可以将 B 类型的值赋给 A 类型的变量,这种情 形称之为类型兼容。
1. 它们都是实数类型
2. 它们都是整数类型
3. B类型是A子界类型,例如A是Integer类型,而B是byte类型。
4. 两个都是同一种类型的子界类型,例如byte与smallint均为integer的子界类型,故 byte类型的值可赋给smallint类型的变量,反之亦可,此种以情形下应当注意B类型的 值不能超出A类型所能表示的最大值。例如若A是byte类型、B是word类型,在将B类型 的值赋给A类型的变量时,B类型的值最好不要大于255,这会引起编译错误或数据丢 失。
5. 两个都是集合类型,并且它们的基础类型是兼容的。例如以下两种类型兼容: var
s1:set of byte; s2:set of byte;
6. A是字符串类型,B是字符串、packed-string 和Char 类型。
7. A是Variant 类型,B是整数、实数、字符串、字符或布尔类型。反之亦可。
8. 两个都是类、类引用或接口类型,并且B继承于(继承)A。

9. A是PChar 或PWideChar,B是0下标开始的字符数组(array[0..n] of Char) 10. A是Pointer(无类型指针)类型,B是任意指针类型。
11. 两个是同一种类型的指针,并且开启了编译器指示字{$T+}
12. 两个都是过程类型,它们有相同的返回类型,并且参数的个数、位置和类型都相同。
赋值兼容性
赋值兼容性不是一种对称关系。T1 是一个变量,T2 是一个表达式,若 T2 的值在 T1 的 取值范围内,并且至少下面一个条件成立,则 T2 可以赋给 T1:
1. T1和T2是同一种类型,并且不是文件类型或包含文件类型的结构类型
2. T1和T2是兼容的有序类型
3. T1和T2都是实数类型
4. T1是实数类型,T2是整数类型
5. T1是PChar类型或任何字符串类型,而T2是字符串常量
6. T1和T2都是字符串类型
7. T1是字符串类型,T2是字符或packed-string类型
8. T1是一个长串类型,T2是一个PChar类型
9. T1和T2是兼容的packed-string类型
10. T1和T2是兼容的集合类型
11. T1和T2是兼容的指针类型
12. T1和T2都是类、类引用或接口类型,并且T2继承自T1
13. T1是一个接口类型,T2是实现T1的一个类
14. T1是PChar或PWideChar,T2是一个0下标开始的字符数组(array[0..n]ofChar)
15. T1和T2是兼容的过程类型(在一些赋值语句中,一个函数或过程的标志符被认为是过 程类型)
16. T1是Variant类型,T2是整数、实数、字符串、字符、布尔或接口类型
17. T1是整数、实数、字符串、字符或布尔类型,T2是Variant
- T1是IUnknown或IDispatch接口类型,T2是Variant类型(若T1是Iunknown,T2的类型 编码必须是varEmpty、varUnknown或varDispatch;若T1是Idispatch,T2的类型编码 必须是varEmpty或varDispatch。)
2.10 类型转换
所谓类型转换是指在编程时将某种类型转换成另一种类型,如 Integer('A')将字符 A 转换成整数型的值 65。类型转换的语法为:
类型标识符(表达式) 类型名称可以是任意一个已经定义过的类型的名称,这个名称必须是标识符(所以诸如
^Integer 之类的类型表达式不能用于类型转换)。 根据表达式类型的不同,类型转换可以分为值及变量转换。二者语法完全相同,当转
换规则上有一点小小的区别。
- 32 -
值转换中的表达式是一个直接常量值。如 Integer('A')中的字符'A'或 Char(65)中的 整数 65。在默认的情形下,值转换中的类型标识符及表达式均不能是除指针类型及有序类 型之外的其它类型。这句话的意思如下:
Integer(4.5) //错误,表达式只能是有序类型或指针类型 Real(12) //错误,类型标识符只能是有序类型或指针类型
在某些时候转换后的值可能超出了变量所能表示表示的最大范围,如以下情形:
var
ch:AnsiChar;
„
ch := AnsiChar(320);
„
AnsiChar 类型的值域中只有 256 个值(最大序数为 255),所以将整数 320 转换成此类型 型后,ch 无法容纳这个值,这就相当将一把 2 米长的剑塞进 1 米长的鞘的中,当然无法容 纳。在这种情形下,编译器有两种选择,一是在编译期间提示错误,告诉你这把剑不能放 进这把鞘中;二是对数据进行截取,直接把剑砍成一米长后再放进鞘中。
一般情形下截取都会将数据直接截成变量的最大值,如上面的 ch 的值为 255。但这里 的截取却是将数据进行反绕后再赋给变量。具体的操作如下:
1. 将所赋之值除以变量的值域中值的数量,保留得到的余数
2. 将上一步中的余数与变量值域中的最小值相加,得到的值就是目标值 所以,对于上面的例子中的 ch := AnsiChar(320),编译器进行如下操作: 1. 320 除以 256,余数为 64。
2. 将 64 与 AnsiChar 的最小值(0)相加,所得值为 64,即为目标值。
所以 ch 的值为 64。 但无论是反绕还是直接截取,均不会改变所赋之值的符号,也就是说,将一个负数赋
给一个变量时,无论是截取还是反绕,变量得到的值一定是负数,但两者绝对值不一定相 同。
所谓变量转换是指将一个包括变量和常量在内的标识符而非一个值转换成其它类型。 变量转换的第一规则:对于所占用的内存,目标类型永远不要小于源类型。若目标类型所 占字节数大于源类型,编译器会进行反绕或截断。如下面的例子所示:
var
ch:AnsiChar; i:Integer; b:Byte;
begin
ch := AnsiChar(321); i := 320;
b := Byte(i); writeln(integer(ch));
- 33 -

writeln(b); readln;
end.
对于实数的类型转换较为特殊。所有类型的实数在转换至其它类型的值时,首先被转 换为实数中的 Extended 类型,然后再转换成其它类型。而 Extended 类型过大,导致大多 数变量根本无法容纳这种类型的值,所以将实数转换成其它类型时应当使用 Delphi 提供的 例程。常见的例程有 Int、Round、Trunc,它们均定义于 system 单元中。Int 以实数的形 式返回一个实数的整数部分,Round 返回一个与指定的实数最为接近的整数,Trunc 以整数 形式返回一个实数的整数部分。
var
r:real; begin
r := 3.84;
writeln(Int(r)); writeln(Round(r)); writeln(Trunc(r)); readln;
end.
无论是值转换还是变量转换,这些转换表达式均不能放在赋值语句的左边。也就是说 代码中不能出现下列形式的语句:
var
ch:AnsiChar;
„
Integer(ch) := 65;
„
这点与 Delphi 的说明文档正好相反,可能在某情形下这种语句是合法的,但至少在 Delphi2010 的默认情形下不能这么做。

970

被折叠的 条评论
为什么被折叠?



