# 四、Object Pascal 语言

让我们一起进入对象帕斯卡语言的奇妙世界。

Pascal 是一种强类型语言，它趋向于简单、优雅和干净。由于这些特点，它经常被用作学习语言。然而，它也非常灵活，因为它支持不同的编码风格和范例，如过程和面向对象编程。

Object Pascal 语言的 Delphi 化身功能强大，充满了许多特性，如匿名方法、泛型和运算符重载。如果你在学校使用过帕斯卡，现在的语言——尤其是在德尔菲——远不止如此。

| ![](img/00008.gif) | 注意:请始终记住 Pascal 是一种不区分大小写的语言，就像 Visual Basic 一样。但是，我建议您保持用作标识符的名称的一致性。德尔福将通过代码洞察技术和提出在给定上下文中有效的标识符列表来帮助完成这项任务。这有助于您快速完成代码中的说明。 |

即使世界上的每一个编译器总是忽略它们或者把它们去掉，注释对于记录代码中最微妙的部分是必不可少的，也是任何编程书籍中解释的第一个元素。

Delphi 支持以//开头的单行注释，或使用花括号{…}的经典 Pascal 多行注释，或(*…*)字符序列:

代码清单 2:注释

```delphi
          // This is a single line comment

          (*
            This is a multi-line comment
            that spans more than one row.
          *)

          {
            Hey, this is a multiline comment, too.
            Choose your favorite style
          }

```

有时候你会看到一些奇怪形式的评论，比如:

代码清单 3:编译器指令

```delphi
          {$APPTYPE CONSOLE}
          {$R MyResourceFile.res}

```

这些不是评论，而是指示。它们是影响编译过程的选项。例如，{$R MyResourceFile.res}告诉编译器将指定的资源文件及其内容嵌入到可执行文件中。

Delphi 中的每种类型的项目都有一个主程序文件，其基本组织如代码清单 4 所示。

代码清单 4:程序结构

```delphi
          program ProjectName;
          begin
            // TODO: Put your main logic here…
          end.

```

关键字 program 表示存在一个可运行的应用程序，但是您也可以看到类似 library(对于动态链接库)或 package(对于 Delphi 包，它是具有运行时和设计时支持的类和组件的捆绑包)的东西。

| ![](img/00008.gif) | 注意:无论项目类型如何，您都可以从主菜单中选择[项目&#124;查看源代码]来打开主程序源文件。 |

begin…end 代码块包含程序启动时执行的指令，实际上是应用程序的入口点。

| ![](img/00010.jpeg) | 提示:我不建议在主程序文件中放很多代码。您的逻辑应该使用最好的、最流行的编程实践来实现。这意味着创建单独的模块，其中每个模块都包含负责模型特定方面(例如数据存储、日志记录、安全性等)的数据类型和类。) |

那么，如何在 Delphi 中创建代码模块呢？

你必须把每一行帕斯卡代码放在一个`unit`里面。除了主程序文件之外，一个单元是容纳任何一行代码所必需的最小结构，每个单元都保存在一个单独的 Pascal 文件中(意思是一个带有“”。传递“扩展”)。

代码清单 5:基本单元结构

```delphi
          unit MyModule;

          interface

          implementation

          end.

```

unit 关键字后面是一个标识符，它作为该单元包含的所有元素的一种命名空间。

| ![](img/00008.gif) | 注意:单位名称必须始终与文件名一致。例如，如果单元名为“我的模块”，它必须保存在一个名为“我的模块. pas”的文件中。如果您愿意，您可以始终使用虚线表示法，如“我的公司”。我的模块”，以创建一种命名空间。 |

虽然看起来很奇怪，但关键字 end 实际上结束了该单元。

内部有两个主要部分，接口和实现。接口部分中的每个元素在单元外部都是可见的，因此其他模块(读取“单元”)可以访问它，而实现中的所有内容都表示在单元外部不可用的“黑盒”。

假设您想要创建一个单元，该单元包含一些使用过程编码风格的共享实用程序(全局)例程。您可以在实现部分声明和实现您的例程:

代码清单 6:全局函数

```delphi
          unit MyUtilities;

          interface

          // Function declaration
          // (makes it visible to other units) function DoubleString(const AValue: string): string;

          implementation

          // Function implementation function DoubleString(const AValue: string): string;
          begin
            Result := AValue + AValue;
          end;

          end.

```

此示例显示了一个静态链接例程，该例程将一个字符串作为参数，然后将该字符串连接到自身，并作为结果返回该值。

| ![](img/00008.gif) | 注意:如果您对 AValue 参数声明之前的 const 关键字的含义感到好奇，它会阻止函数更改该变量的值。该约束允许编译器通过信任函数不改变值来应用优化，从而安全地传递一个指向字符串值的指针，而不创建整个字符串的副本。 |

如果例程有返回值，可以使用 function 关键字，否则必须使用 procedure。当您调用返回值的函数时，您可以自由选择是否使用返回值。

在示例代码中，函数 DoubleString 是可见的，并且可以在其他单元中使用，因为它的声明也出现在接口部分。实际上，它是一个全局函数。要使该功能专用于该单元，只需将其从接口部分移除即可。

要使用已经在另一个单元中声明的元素，您必须在您的单元中添加一个 uses 子句，后跟一个逗号分隔的要导入的单元列表。

| ![](img/00008.gif) | 注意:请始终记住，您只能导入在单元的接口部分中声明的内容；实现部分的所有内容都将保持隐藏。 |

查看代码清单 7，看看这是如何工作的。

代码清单 7:使用子句示例

```delphi
          unit MyBusinessLogic;

          interface

          uses
            MyUtilities;

          implementation

          // Routine implementation procedure DoSomething();
          var
            SomeText: string;
          begin
            SomeText := DoubleString('Double this string');
            WriteLn(SomeText);
          end;

          end.

```

正如您所看到的，DoSomething()过程可以调用 DoubleString()，因为包含该函数的单元在上面的 uses 子句中列出；如果省略，您将在构建时得到最常见的错误之一:“未声明的标识符。”

每次在 Delphi 中声明一个变量，都必须给它赋值一个类型，因为 Pascal 是一种强类型语言。

在前面的例子中，我们已经使用了一个字符串变量来保存一个字符序列。

如果变量在过程或函数内部声明，它们是局部的，因此在例程外部不可见。如果你把一个变量声明放在一个单元的实现部分，你就使它们对整个单元是全局的。如果在接口部分放置一个声明，那么其他所有导入它的单元也可以访问这个变量。

局部变量必须在 begin…end 块之前声明，begin…end 块标记了过程和函数的主体部分，因此不能“内联”声明变量(例如，就像在 C# 中一样)。

| ![](img/00008.gif) | 注意:每个标识符必须是有效的，才能被德尔福编译器毫无怨言地接受。要做到这一点，千万不要用数字作为名字的开头，也不要用保留字作为单位、例程、类型和变量。 |

在使用任何变量之前，必须使用:=运算符为其赋值。

下面是一些变量声明和赋值的例子:

代码清单 8:变量声明和赋值

```delphi
          // Variable declarations var
            Number: Integer;
            Flag: Boolean;
            Letter: Char;
            Text: string;
            Instance: TObject; 
          begin
            Number := 123;
            Flag := True;
            Letter := 'D';
            Text := 'abc';
            Instance := TObject.Create();
            // TODO: Put your implementation here...
          end;

          end.

```

每个变量赋值都需要是类型安全的:您必须只为属于同一类型的值、后代类型(如果是对象)或不会导致数据丢失的值赋值。如果代码不满足这些条件，Delphi 将在您构建代码时发出编译器错误。

Delphi 提供了一组原语类型。这个名字来源于这样一个事实，即这些是编译器知道的内置类型，它们可以用来声明变量或创建更复杂的类型，如记录和类。

每当您想要引入新类型时，请使用 type 关键字。你将在本章的剩余部分看到它的一些用法。

Delphi 使用整数类型存储非浮点值。我在表 2 中总结了它们。

表 2:整数数据类型

| 类型标识符 | 范围 | 大小 |
| 短英特 | -128..127 | 带符号 8 位 |
| 斯莫列特 | -32768..32767 | 带符号 16 位 |
| 整数 | -2147483648..2147483647 | 带符号 32 位 |
| Int64 | -2^63..2^63-1 | 有符号 64 位 |
| 字节 | 0..255 | 无符号 8 位 |
| 单词 | 0..65535 | 无符号 16 位 |
| 红衣主教 | 0..4294967295 | 无符号 32 位 |
| UInt64 | 0..2^64-1 | 无符号 64 位 |

Delphi 提供了一种布尔类型来表达布尔值和执行标准的布尔逻辑操作。

| ![](img/00008.gif) | 注意:还有其他可用的布尔类型(字节布尔、单词布尔、长布尔)，但它们是为了向后兼容而存在的。 |

代码清单 9:布尔类型

```delphi
          // Variable declaration var
            Proceed: Boolean;
           // Usage Proceed := True;
          Proceed := False;

          if Proceed then

```

枚举类型是一种基于整数的序数类型，它将一组标识符与整数值相关联。下面是一个示例演示:

代码清单 10:枚举类型

```delphi
          // Type declaration type
            TSeason = (Spring, Summer, Fall, Winter);

          // Variable declaration var
            Season: TSeason;
           // Usage Season := TSeason.Summer;

```

Delphi 提供了一个 Char 类型，它是 WideChar 类型的别名，代表一个 16 位 Unicode 字符。字符串类型是 Unicode 字符串的别名，它包含零个或多个 Unicode 字符。

代码清单 11:字符和字符串类型

```delphi
          // Variable declaration var
            SomeChar: Char;
            SomeText: String;
           // Usage SomeChar := 'D';
          SomeText := 'Delphi rocks!';

```

如果必须使用 ANSI 代码页，可以使用 AnsiChar 和 AnsiString 类型；如果必须从第三方 dll 或其他遗留系统导入函数，这种情况经常发生。

Delphi 允许您将一个 AnsiString 赋值给一个执行隐式转换的 unicode 字符串。相反的情况可能会导致数据丢失，因为如果当前的 ANSI 代码页错过了 Unicode 字符，Delphi 可能无法转换它。

到目前为止，世界上的其他地方都(或者应该)符合 Unicode，所以如果您没有处理这些场景，并且不必提供一些向后兼容性，那么就坚持使用常见的 Char 和 String 类型。

| ![](img/00008.gif) | 注意:请注意，Delphi 字符串索引在桌面和移动编译器上实际上是不同的:它是 1-基于桌面和 0-基于移动。如果您使用 RTL 字符串函数来访问字符串中的字符，请注意这一点，或者更好的是，使用 TStringHelper 静态类及其方法在安全模式下执行字符串操作。 |

您还可以定义自己的限定范围的序数类型，指定下限和上限，如代码清单 12 所示。

代码清单 12:子范围类型

```delphi
          // Type declaration type
            TMonthDay = 1..31;
            TYearMonth= 1..12;
            THexLetter= 'A'.. 'F';
           // Variable declaration var
            MonthDay: TMonthDay;
            YearMonth: TYearMonth;
            HexLetter: THexLetter;
           // Usage MonthDay := 31;
          YearMonth := 12;
          HexLetter := 'B';

```

实类型用于存储近似浮点值。以下是属于该系列的类型的完整列表:

表 3:真实数据类型

| 类型标识符 | 范围 | 有效数字 | 大小(字节) |
| 单一的 | 1.5e-45..3.4e+38 | 7-8 | four |
| 两倍 | 5.0e-324..1.7e+308 | 15-16 | eight |
| 真实的 | 5.0e-324..1.7e+308 | 15-16 | eight |
| 延长 | 3.4e-4932..1.1e+4932
(在 32 位平台上)5.0e-324..1.7e+308
(在 64 位平台上) | 10-20 (32 位)15-16 (64 位) | 10 (32 位)8 (64 位) |
| 免费票 | -2 <sup class="calibre40">63</sup> +1..2 <sup class="calibre40">63</sup> -1 | 10-20 | eight |
| 货币 | -922337203685477.5808
..922337203685477.5807 | 10-20 | eight |

| ![](img/00008.gif) | 注意:当用于涉及货币值的计算时，货币数据类型存储为 64 位整数，以最小化舍入误差。 |

您可以声明包含任何类型元素的数组，无论是静态的、具有固定长度和指定范围的，还是动态的、具有可变长度的，您可以在运行时通过调用 SetLength 函数来调整可变长度。

代码清单 13:数组

```delphi
          var
            // Fixed length array.   IntArray: array[1..10] of Integer;

            // Dynamic array.   DynArray: array of Char;
           // Basic usage IntArray[1] := 20;
          IntArray[10] := 200;

          // Dynamic array usage SetLength(DynArray, 50); // allocate memory for 50 elements
          DynArray[0] := 'D';

```

对象 Pascal 对集合类型有特定的语法支持，集合类型是属于同一类型的所有元素的集合。集合变量中可以包含的元素数量取决于基本类型:如果创建一组字节元素，最多可以在其中存储 255 个字节值元素。

值的顺序并不重要，元素只能添加到同一个集合中一次。把它想象成一个文氏图的软件表示。

![](img/00025.jpeg)

图 21:维恩图

声明集合后，您可以初始化它、添加和移除元素子集、查找交集等等。

代码清单 14:集合类型

```delphi
          var
            CharSet: set of Char;

          // Defines the initial set.
          CharSet := ['a', 'b', 'c'];

          // Add a subset of items to the initial set.
          CharSet := CharSet + ['x', 'y', 'z'];

          // Removes a subset of items.
          CharSet := CharSet - ['a', 'x'];

```

我们将在本章后面研究哪些运算符可以应用于设置值。

对象 Pascal 允许您创建记录类型。当一组变量的值代表一条信息时，记录是将它们组合成一个结构的一种方式。

例如，我们可以将一个 TDateTime 值看作一组分别包含日期和时间信息的两个变量。您也可以声明一个点记录类型来保存代表一个位置的 X 和 Y 值。

代码清单 15:记录类型

```delphi
          // Defines the record type.
          type
            TPoint = record
              X: Integer;
              Y: Integer;
            end;
           // Declares a record variable.
          var
            P: TPoint;

```

| ![](img/00010.jpeg) | 提示:我建议在字段数量有限时使用记录类型，并且您可以保持结构尽可能简单。如果必须表示更复杂的对象的状态或在引用之间移动，记录会变得低效，您应该创建一个类来代替。 |

像任何其他传统编程语言一样，Object Pascal 支持许多运算符，您可以使用这些运算符进行计算、连接和逻辑比较。

下表按类别汇总了对象帕斯卡语言中可用的所有运算符。

表 4:算术运算符

| 操作员名 | 标志 | 用法示例 |
| --- | --- | --- |
| 添加 | + | A + B |
| 减法 | - | 甲—乙 |
| 增加 | * | 甲*乙 |
| 除法(真实值) | / | A / 2 |
| 除法(整数值) | 差异 | 宽度 2 |
| 余数(整数值) | 现代的 | 高度 mod 2 |

如果将整数值用于加法、减法、乘法和整数除法，则表达式的结果类型也是整数值。如果应用实数除法，表达式将返回浮点(实数)值。

这组运算符允许您表达比较两个类型兼容值并返回布尔结果的条件:

表 5:比较运算符

| 操作员名 | 标志 | 用法示例 |
| 平等 | = | A = B |
| 不平等 | <> | 甲<>乙 |
| 小于或等于 | <= | A <= B |
| 不到 | < | 甲 |
| 大于 | > | 甲>乙 |
| 大于或等于 | >= | 甲> =乙 |

您可以将它们用于整数值和实数值，也可以用于字符和字符串:字母顺序将决定结果。

您可以使用表 6 中列出的运算符来实现布尔逻辑。

表 6:布尔运算符

| 操作员名 | 标志 | 用法示例 |
| --- | --- | --- |
| 否认 | 不 | 不(继续) |
| 逻辑“与” | 和 | 有效且不空 |
| 逻辑或 | 或者 | 甲还是乙 |
| 异或 | 异或运算 | 异或 |

| ![](img/00008.gif) | 注意:Delphi 使用一种叫做布尔短路求值的东西来确定布尔表达式的结果值。计算在表达式中从左到右进行，一旦可以确定结果，计算就停止，并返回表达式值。您可以通过编译器指令{$B+}打开(这是默认的)或关闭(使用“完全布尔求值”代替)此模式。 |

一些比较运算符和算术运算符可用于包含集值和元素的表达式中。

表 7:集合运算符

| 操作员名 | 标志 | 用法示例 |
| 联盟 | + | 箭头+ SetB |
| 差异 | - | 箭头-setb |
| 交集 | * | 箭头* SetB |
| 子集 | <= | 小集合< =大集合 |
| 超集 | >= | 大集合> =小集合 |
| 平等 | = | 箭头= SetB |
| 不平等 | <> | 箭〔t0〕setb |
| 成员资格 | 在 | SetA 中的元素 |

当需要指定某个值的内存地址，包括过程和函数的地址时，可以使用指针运算符。或者，您可以使用指针运算符移除对地址的引用来获取有效值。

表 8:指针操作符

| 操作员名 | 标志 | 用法示例 |
| --- | --- | --- |
| 地址 | @ | @myvalue |
| 的价值 | ^ | ^MyPointer |

| ![](img/00008.gif) | 注意:当您在 Delphi 中处理对象时，您实际上是在使用引用，它只不过是一个指针变量，包含您想要操作的对象的地址。为了保持代码清晰可读，可以省略^运算符来访问对象成员(属性、字段和方法)。 |

还有一些操作符需要提及，在一些特殊场合会变得很方便。

假设您必须为要导出的过程的名称使用一个保留字，因为您需要这样做，或者您想要为与关键字一致的内容赋予一个有意义的名称。你怎么能做到不出错呢？

只需在标识符前加一个&符号。

代码清单 16:“&”特殊运算符

```delphi
          procedure &Begin;
          begin
            // TODO: Put your code here...
          end;

          // Here we call the procedure.
          &Begin();

```

大多数编程语言都支持“转义序列”，将一些特殊字符放入字符串中。在 Delphi 中，您可以使用#字符来实现这一点:

代码清单 17:字符代码

```delphi
          // We put a line break using the ASCII codes for CR+LF sequence.
          Text := 'This is the first line,'#13#10'and this is the second one';

          // Here we put a TAB character.
          Text := 'Field Name:'#9'Field Value';

```

对象 Pascal 支持许多结构化语句，允许您在代码中实现循环、跳转和条件分支。

对象 Pascal 使用分号(；)来终止每个语句。如果要将一个或多个指令组合在一起，必须将它们包含在 begin…end 块中，从而创建一个复合语句。

代码清单 18:简单和复合语句

```delphi
          // A single instruction.
          WriteLn('Some text...');

          // A block of more instructions.
          begin
            WriteLn('The first line.');
            WriteLn('The second line.');
            WriteLn('The third line.');
          end;

```

基本的`If-Then-Else`语句有以下形式。

代码清单 19:如果-那么-否则语句

```delphi
          if Condition then
            statement
          else
            statement;

```

条件部分可以是布尔变量，也可以是导致布尔结果的表达式。

当条件满足时，Delphi 执行 then 关键字后的简单或复合语句，否则执行 else 关键字后的语句。else 部分是可选的。

如果您需要链接更多 If 语句来指定当表达式采用一组值时应该做什么，请使用 case 语句来避免代码混乱和不可读。

代码清单 20:案例陈述

| `case`选择`of`  1:`begin`*// TODO:此处说明...*`end`；  2:`begin`*// TODO:此处说明...*`end`；  3:`begin`*// TODO:此处说明...*`end`；`else begin`*// TODO:此处说明...*`end`；`end`； |

| ![](img/00008.gif) | 注意:只有标量类型变量可以与 case 语句一起使用，并且任何 Case 都必须用常数值进行标记，因此不能使用对象引用或浮点值。如果你认为这是一个很大的限制，请注意使用许多案例陈述是不鼓励的，并且被认为是“反模式”。最好使用面向对象编程和虚拟方法来建模类似的业务逻辑。 |

对象 Pascal 提供了不同的语句来实现代码中不同类型的循环。

如果您想重复一个语句，直到满足一个条件，您可以编写类似下面的代码。

代码清单 21:重复循环

```delphi
          repeat
             WriteLn('Enter a value (0..9): ');
             ReadLn(I);
           until (I >= 0) and (I <= 9);

```

如果要在输入循环语句之前检查条件，可以使用 while 结构。

代码清单 22: While 循环

```delphi
          while not Eof(InputFile) do
          begin
            ReadLn(InputFile, Line);
            Process(Line);
          end;

```

如果要进行明确的迭代次数，可以使用 for 循环结构。

代码清单 23: For 循环

```delphi
          // Forward loop.
          for I := 1 to 63 do
             if Data[I] > Max then
               Max := Data[I];

          // Backward loop.
          for I := 63 downto 1 do
             if Data[I] < Min then      Min := Data[I];

```

如果您有一个提供迭代器的数据类型，比如数组、字符串或集合，您可以使用 for…in 循环。

代码清单 24:枚举器循环

```delphi
          var
            SomeChar: Char;
            SomeText: String;

          // Iterates each char in a string. for SomeChar in SomeText do
             WriteLn(SomeChar);

```

Delphi 包括对异常处理的支持。异常允许您在处理运行时错误时编写简洁的代码。

包装代码以防止两次尝试之间出现错误...例外和例外之间的例外处理...结束。

代码清单 25:异常处理

```delphi
          try
            R := A div B;
          except
            on E:EDivByZero do
            begin
              Writeln('Division by zero - ', E.ClassName, ': ', E.Message);
            end;
            on E:Exception do
            begin
              Writeln('Unexpected error - ', E.ClassName, ': ', E.Message);
            end;
          end;

```

通过打开...您可以过滤特定的异常层次结构，并存储对保存问题信息的`Exception`对象的引用。

如果你认为 Object Pascal 是很多年前在学校学的 MS-DOS Turbo Pascal 语言，那你就要重新思考了。

如您所见，Object Pascal——尤其是 Delphi 中使用的方言——经过多年的扩展，具有许多现代编程语言所特有的新特性。像 C# 或 Java 一样，您可以找到对运算符重载、匿名方法、闭包、泛型等的支持，并且在 Delphi 的每个新版本中都添加了更多的功能。

Object Pascal 提供了功能强大的工具的平衡组合，清晰可读的语法，不会变得过于冗长。

如果你想成为对象帕斯卡的专家并了解所有最有趣的细节，请查看德尔福产品经理马可·坎特的[对象帕斯卡手册](http://www.marcocantu.com/objectpascalhandbook)。