Delphi 11 编程语言的完整介绍 作者:Marco Cantu 笔记:豆豆爸
? 在Pascal中,有几种预定义的数据类型,可以分为三组:序数类型、实数类型和字符串。我们将在接下来的几节中讨论序数类型和实数类型,而字符串将在第6章中专门介绍。
? Delphi还包括一种非类型化的数据类型variant,称为可变数据类型,以及其他“灵活”类型,如TValue(增强的RTTI支持的一部分)。其中一些更高级的数据类型将在第5章后面讨论。
? 序数类型基于顺序或序列的概念。不仅可以比较两个值以查看哪个更大,还可以获取任何序数值的下一个或前一个值,并计算数据类型可以表示的最低和最高可能值。
? 三个最重要的预定义序数类型是Integer
、Boolean
和Char
(字符)。不过,还有其他相关类型,它们具有相同的含义,但内部表示形式不同,支持的数值范围也不同。
? 下表列出了用于表示数字的序数数据类型:
位数 | 有符号数 | 无符号数 |
---|---|---|
8 bits | ShortInt: -128 到 127 | Byte: 0 到 255 |
16 bits | SmallInt: -32768 到 32767 (-32K to 32K) | Word: 0 到 65,535 (0 to 64K) |
32 bits | Integer: -2,147,483,648 到 2,147,483,647 (-2GB to +2GB) | Cardinal: 0 到 4,294,967,295 (0 to 4 GB) |
64 bits | Int64: -9,223,372,036,854,775,808 到9,223,372,036,854,775,807 | UInt64: 0 到18,446,744,073,709,551,615 |
? 如您所见,这些类型分别对应于不同的数字表示,这取决于表示值的位数以及是符号位存在与否。有符号值可以是正值或负值,但取值范围较小(对应无符号值的一半),因为用于存储数值本身的位数少了一位。
? Int64 类型表示多达 18 位的整数。运行库中的一些序数类型例程(如 High
和 Low)、数值例程(如 Inc
和 Dec
)以及字符串转换例程(如 IntToStr
)完全支持这种类型。
别名整数类型
? 如果您很难记住 ShortInt 和 SmallInt 之间的区别(包括哪一个实际上更小),而不是实际类型,您可以使用 System 单元中声明的预定义别名之一:
type
Int8 = ShortInt;
Int16 = SmallInt;
Int32 = Integer;
UInt8 = Byte;
UInt16 = Word;
UInt32 = Cardinal;
? 再次强调,这些类型并没有添加任何新功能,但可能更容易使用,因为记住Int16比记住SmallInt实际上要简单。 对于从C和其他使用类似类型名称的语言的开发人员,使用这些类型别名也更容易。
整数类型、64位、NativeInt和LargeInt
? 在Object Pascal的64位版本中,您可能会惊讶地发现Integer类型仍然是32位。这是因为这是在CPU级别进行数字处理的最有效类型。
? 指针类型(稍后将详细介绍指针)和其他相关的引用类型才是 64 位的。如果需要一种能适应指针大小和本地 CPU 平台的数值类型,可以使用两种特殊的NativeInt
和NativeUInt
别名类型。它们与特定平台上的指针大小相同(即在 32 位平台上为 32 位,在 64 位平台上为 64 位)。
? LargeInt 类型的情况略有不同,它通常用于映射到本地平台 API 函数。在 32 位平台和 Windows 32 位平台上是 32 位,而在 64 位 ARM 平台上是 64 位。最好不要使用这种类型,除非你特别需要它用于本地代码,以适应底层操作系统。
整数类型助手
? 虽然在 Object Pascal 语言中,整数类型与对象是分开处理的,但可以使用 "点符号 "对这些类型的变量(和常量值)进行操作。这种符号通常用于对对象应用方法。
注解:从技术上讲,对本地数据类型的这些操作是使用 "内部记录助手 "定义的。类和记录助手将在第 12 章中介绍。 简而言之,你可以自定义适用于核心数据类型的操作。 专业开发人员会注意到,在匹配的固有记录助手中,类型操作被定义为类静态方法。
? 您可以从以下 IntegersTest
示例中提取的代码看到一些演示:
var
N: Integer;
begin
N := 10;
Show(N.ToString);
// 显示一个常数
Show(33.ToString);
// 类型操作,显示存储类型所需的字节
Show(Integer.Size.ToString);
end;
注解:本代码片段中使用的 Show 函数是一个简单的过程,用于在memo控件中显示一些字符串输出,以避免关闭多个 ShowMessage 对话框。这种方法的优点是可以更方便地复制输出并粘贴文本(如下所示)。在本书的大部分演示中,你都会看到这种方法。
? 程序的输出如下:
10
33
4
? 鉴于这些操作非常重要(比运行时库的其他一些操作更重要),值得在这里列出它们:
ToString 将数字转换为字符串,使用十进制格式
ToBoolean 转换为Boolean类型
ToHexString 将数字转换为字符串,使用十六进制格式
ToSingle 转换为单精度浮点数数据类型
ToDouble 转换为双精度浮点数数据类型
ToExtended 转换为扩展的浮点数数据类型
? 第一和第三个操作是使用十进制或十六进制操作将数字转换为字符串。第二个操作是将数字转换为布尔类型,而后三项操作是将数字转换为浮点类型。
? 您还可以对整数类型(以及大多数其他数字类型)进行其他操作,例如:
Size 存储此类型的变量所需的字节数
Parse 将字符串转换为其表示的数字值,如果字符串不表示数字,则引发异常
TryParse 尝试将字符串转换为数字
标准序数类型例程
? 除了上面列出的由整数类型助手定义的操作外,还有一些标准和 "经典"函数可以应用于任何序数类型(不仅仅是数值类型)。一个典型的例子是使用函数 SizeOf、High 和 Low 询问有关类型本身的信息。SizeOf 系统函数(可用于语言中的任何数据类型)的结果是一个整数,表示表示给定类型的值所需的字节数(就像上面的 Size 辅助函数一样)
? 下表列出了可用于序数类型的系统例程:
注解:C 和 C++ 程序员应该注意到,Inc 过程的两个版本(一个参数或两个参数)分别对应于 ++ 和 += 操作符(Dec 存过程也是如此,对应于 – 和 -= 操作符)。Object Pascal 编译器对这些递增和递减操作进行了优化,这与 C 和 C++ 编译器的做法类似。然而,与 C/C++ 不同的是,Delphi 只提供先增和先减版本,而不提供后增和后减版本。另一个区别是 Delphi 的 Inc 和 Dec 不返回值。
? 请注意,编译器会自动评估这些例程中的某些例程,并用其值替换。例如,如果调用 High(X)
,而 X 被定义为整数,编译器会用整数数据类型的最高值替换表达式。在 IntegersTest
示例中,我添加了一个事件,其中包含一些此类序数类型函数:
var
N: UInt16;
begin
N := Low(UInt16);
Inc(N);
Show(N.ToString);
Inc(N, 10);
Show(N.ToString);
if Odd(N) then
Show(N.ToString + ' is odd');
end;
? 这是您应该看到的这样的输出:
1
11
11 is odd
? 您可以将数据类型从UInt16更改为Integer或其他序数类型,以查看输出如何变化。
超出值域范围的操作
? 像 N 这样的变量有效值范围是有限的。如果赋给它的值是负数或过大,就会导致错误。实际上,您可能会遇到三种不同类型的超范围操作错误。
? 第一种错误类型是编译器错误,如果分配给它的常量值(或常量表达式)超出范围,就会发生这种错误。例如,如果将以下代码片段加入到上面的代码中:
N := 100 + High(N);
编译器将发出错误:
[dcc32 Error] E1012 Constant expression violates subrange bounds
? 第二种情况发生在编译器无法预测错误条件的情况下,因为它取决于程序流。假设我们在相同的代码中写入:
Inc(N, High(N));
Show(N.ToString);
? 编译器不会触发错误,因为有一个函数调用,所以编译器事先并不知道它的结果(错误还取决于 N 的初始值)。在这种情况下,有两种可能性。默认情况下,如果编译并运行此程序,变量中的值将完全不合逻辑(在这种情况下,操作结果将是减 1!)。这是最糟糕的情况,因为你没有获得错误警告,但你的程序却不正确。
您可以(强烈建议)打开一个名为"溢出检查"的编译器选项({$Q+}
或{$OVERFLOWCHECKS ON}
),它将防止类似的溢出操作并引发错误,在这种特定情况下就是 “整数溢出”。
? 逻辑真值和假值使用布尔类型表示。这也是条件语句中的条件类型,我们将在下一章看到。布尔类型只能具有 True 和 False 两种可能值中的一种。
警告: 为了与微软的 COM 和 OLE 自动化兼容,数据类型 ByteBool、WordBool 和 LongBool 用-1 表示值 True,而值 False 仍然是 0。 同样,除非绝对必要,一般情况下应忽略这些类型,避免所有低级布尔操作和数字映射。
? 与 C 语言及其某些派生语言不同,布尔类型在 Object Pascal 中是一种枚举类型。代表布尔变量的数值没有直接的转换,因此不应滥用直接类型转换,试图将布尔值转换为数值。不过,布尔类型的辅助函数确实包括 ToInteger 和 ToString 函数。本章稍后将介绍枚举类型。
? 请注意,使用 ToString 函数将返回包含布尔变量数值的字符串。另一种方法是使用全局函数 BoolToStr,将第二个参数设置为 True,以表示输出使用布尔字符串("True"和 “False”)。(有关示例,请参阅下文 "字符类型操作 "部分)。
? 字符变量使用 Char 类型定义。与旧版本不同,现在的语言使用 Char 类型表示双字节 Unicode 字符(也用 WideChar 类型表示)。
注解:Delphi 编译器仍将单字节 ANSI 字符分为 AnsiChar 和 Unicode 字符分为 WideChar,Char 类型定义为后者的别名。建议将重点放在 WideChar 上,而将 Byte 数据类型用于单字节元素。不过,从 Delphi 10.4 开始,所有编译器和平台都可以使用 AnsiChar 类型,以便更好地兼容现有代码。
? 有关 Unicode 字符的介绍,包括码位和代理对的定义(以及其他高级主题),请阅读第 6 章。在本节中,我将重点介绍 Char 类型的核心概念。
? 正如我前面提到的,在涉及字面值时,常量字符可以用符号符号表示,如 “k”,也可以用数字符号表示,如 #78。后者也可以使用 Chr 系统函数来表示,如 Chr(78)。相反的转换可以用 Ord 函数来完成。在表示字母、数字或符号时,一般最好使用符号符号。
? 在表示特殊字符(如 #32 以下的控制字符)时,一般使用数字符号。下面列出了一些最常用的特殊字符:
#8 backspace
#9 tab
#10 new line
#13 carriage return
#27 escape
字符类型操作
? 与其他序数类型一样,Char类型具有可以把点符号应用于该类型变量的几个预定义操作。再次强调,这些操作是通过内在的记录助手定义的。
? 然而,使用情景却有很大不同。首先,要使用此功能,您需要通过在uses
语句中引用Character
单元以启用。其次,Char类型的助手与其它转换函数不同,它包括几十个Unicode
相关的操作,包括IsLetter
、IsNumber
和IsPunctuation
等测试,以及ToUpper
和ToLower
等转换。以下是从CharsTest
示例中摘取的一个示例:
uses
Character;
...
var
Ch: Char;
begin
Ch := 'a';
Show(BoolToStr(Ch.IsLetter, True));
Show(Ch.ToUpper);
该代码的输出是:
True
A
注解:Char类型助手的ToUpper操作完全支持Unicode。这意味着,如果传递一个带重音符号的字母,比如ù,结果将是ù。一些传统RTL函数并不那么智能,仅适用于纯ASCII字符。它们迄今为止还没有被修改,因为相应的Unicode操作速度明显较慢。
Char作为序数类型
? Char 数据类型相当大,但它仍然是一种序数类型,因此可以使用 Inc 和 Dec 函数(如我们在 "标准序数类型例程 "一节中看到的,用于读取下一个或上一个字符,或向前移动给定数目的元素),并使用 Char 计数器编写 for 循环(下一章将详细介绍 for 循环)。
? 下面是一个用于显示几个字符的简单片段,通过从起点开始增加数值来获得:
var
Ch: Char;
Str1: string;
begin
Ch := 'a';
Show(Ch);
Inc(Ch, 100);
Show(Ch);
Str1 := '';
for Ch := #32 to #1024 do
Str1 := Str1 + Ch;
Show(Str1);
? CharsTest
示例的 for 循环向字符串添加了大量文本,使得输出相当长。它以下面几行文字开始:
a
?
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc
defghijklmnopqrstuvwxyz{|}~
// 省略了一些更多的行...
使用Chr进行转换
? 我们已经看到,有一个Ord函数返回一个字符的数值(更具体地说是一个Unicode码点)。 还有一个相反的函数可以用来获得对应于代码点的字符,那就是Chr特殊函数。
32位字符
? 虽然默认的Char类型现在映射到WideChar,但值得注意的是,Delphi还为通用字符集4字节表示定义了4字节字符类型UCS4Char。它在System单元中定义如下:
type
UCS4Char = type LongWord;
这个类型定义和UCS4String的相应类型定义(定义为UCS4Char的数组)很少使用,但它们是语言运行时的一部分,并用于Character单元的一些函数。
? 各种类型的整数都可以用一组顺序值来表示,而浮点数不是顺序数(它们有顺序的概念,但没有元素序列的概念),而是用某个近似值来表示,存在一些误差。
吧浮点数有多种格式,取决于表示浮点数的字节数和近似值的精度。以下是 Object Pascal 中的浮点数据类型列表:
? 所有这些都是具有不同精度的浮点数据类型,对应于IEEE标准的浮点表示,并直接由CPU(或更精确地说是浮点数单元FPU)支持,以获得最大速度。
? 还有两种特殊的非序数数值数据类型,您可以用它们来表示精确而非近似的数值:
注解: Delphi 11 为货币数据类型引入了一个新的记录助手。与后面介绍的浮点类型帮助程序类似,它提供了四舍五入操作、解析和字符串转换功能。
? 所有这些非整数数据类型都没有 High、Low 或 Ord 函数的概念。实数类型代表(理论上)无限的数字集合;序数类型代表固定的数值集合。
为什么浮点数值不同
? 让我进一步解释。有了整数 23,就可以确定其下一个值是哪个。整数是有限的(它们有确定的范围和顺序)。浮点数即使在很小的范围内也是无限的,而且没有顺序。毕竟,在 23.0 和 24.0 之间有多少个值呢?23.46 的后面是哪个数?是 23.47、23.461 还是 23.4601?这真的是不可能知道的!
? 因此,虽然询问字符 "w "在 Char 数据类型范围内的序位置是合理的,但询问浮点数据类型范围内的 7143.1562 的序位置却毫无意义。虽然您确实可以知道一个实数的值是否大于另一个实数,但询问在一个给定的数字之前存在多少个实数是毫无意义的(这就是 Ord 函数的含义)。
? 浮点数值背后的另一个关键概念是,浮点数的实现并不能精确地表示所有数字。通常情况下,你认为计算结果是一个具体的数字(有时是整数),但实际上可能是它的近似值。请看下面这段代码,它来自 FloatTest 示例:
var
S1: Single;
begin
S1 := 0.5 * 0.2;
Show(S1.ToString);
? 您认为结果是0.1,而实际上您可能会得到类似0.100000001490116的结果。这接近预计的值,但并非完全相等。毋庸置疑,如果您对结果四舍五入,将获得预计值。如果您改为使用Double变量,输出将为0.1,就像FloatTest示例所示。
注意:由于我没有时间对计算机上的浮点数数学进行深入讨论,所以我将此讨论缩短。但如果您对Object Pascal语言的这个主题感兴趣,我可以向您推荐Rudy Velthuis先生在http://rvelthuis.de/articles/articles-floats.html上的一篇优秀文章。
浮点助手和Math单元
? 正如您从上面的代码片段中看到的,浮点数数据类型还具有记录助手,允许您直接对变量应用操作,就好像它们是对象一样。事实上,浮点数的操作列表实际上相当长。
? 以下是Single类型实例的操作列表(其中一些操作的名称很明显,其他一些可能需要在文档中查找):
? 运行时库还有一个Math单元,定义了高级数学例程,涵盖了三角函数(例如ArcCosh函数)、财务(例如InterestPayment函数)和统计学(例如MeanAndStdDev过程)。
? 运行时库还有一个数学单元,定义了高级数学例程,包括三角函数(如 ArcCosh 函数)、金融(如 InterestPayment 函数)和统计(如 MeanAndStdDev 过程)。其中有很多例程,有些我听起来很奇怪,比如 MomentSkewKurtosis 函数(如果你还不知道这是什么,我会让你自己去了解)。
? System.Math 单元的功能非常丰富,但你也会发现许多适用于 Object Pascal 的外部数学函数列表。