6.3.2 使用[ ]和字符串字符的计数模式
如果你使用过 Object Pascal 或其他编程语言,你可能会知道,访问字符串中的元素是一种关键的字符串操作,通常使用方括号符号([])来实现,与访问数组元素的方式相同。
在 Object Pascal 中,有两种略有不同的方法来执行这些操作:
- Chars[] 字符串类型辅助操作(整个列表在下一节)是一种使用基于零的索引的只读字符访问。
- 标准[]字符串操作符支持读写,默认使用经典的 Pascal 单索引。这一设置可以通过编译器指令进行修改。
在下面简短的历史说明之后,我将对这一问题进行一些澄清。如果你不感兴趣,可以跳过这部分内容,原因是如果不了解一段时间以来发生的事情,就很难理解为什么语言会以现在的方式运行。
注解:让我回顾一下过去,解释一下我们是如何取得今天的成就的。在早期的 Pascal 语言中,字符串被当作字符数组来处理,其中第一个元素(即数组的零元素)用于存储字符串中有效字符的个数或字符串长度。在当时,C 语言每次都要重新计算字符串的长度,寻找 NULL 结束符,而 Pascal 代码则可以直接检查该字节。由于长度使用的是 0 字节,因此字符串中实际存储的第一个字符恰好位于位置 1。
随着时间的推移,几乎所有其他语言的字符串和数组都采用了索引从零开始。后来,Object Pascal 动态数组的索引也是从零开始,大多数 RTL 和组件库都使用了以零为基数的数据结构,只有字符串是一个明显的例外。
在转向移动平台时,Object Pascal 语言设计者决定 "优先 “使用索引从零开始的字符串,允许开发人员在有现有的Object Pascal源代码要迁移的情况下仍然使用旧的模型,并通过编译器指令控制行为。不过,在 Delphi 10.4 中,为在目标平台上保持源代码的一致性,又回到最初的决定。换句话说,决定支持“单源多平台”目标,而不是“像其他现代语言一样”的目标。
要更好地解释索引基数的差异,我们可以通过一个例子进行对比。可以对比一下欧洲和北美是如何计算楼层的(老实说,我不知道世界其他地区是如何计算的)。在欧洲,底层为零层,一楼为其上一层(有时正式表示为 “地上一层”)。在北美,一楼是地面层,二楼是地面层以上的第一层。换句话说,美国使用基于一层的楼层索引,而欧洲使用基于零层的楼层索引。
就字符串而言,绝大多数编程语言都使用从零开始的索引,而不管它们是在哪个大洲发明的。Delphi 和大多数 Pascal 方言使用的是从1开始的索引。
让我来解释一下字符串索引的情况。如上所述,Chars[] 符号总是使用基于零的索引。因此,如果编写:
var
String1: string;
begin
String1 := 'Hello world';
Show(String1.Chars[1]);
输出将是:
e
如果使用直接的[]
记法,以下代码的输出是什么:
Show(String1[1]);
默认情况下,输出将是H。但如果编译器定义 $ZEROBASEDSTRING 已打开,则输出可能为 e。目前(即在 Delphi 10.4 版本之后)的建议是在所有代码中使用从 1 开始的字符串,并避免可能源于围绕默认的从零开始的模型设计的中间遗留代码的问题。
但是,如果你想编写的代码无论 $ZEROBASEDSTRING 设置如何都能正常工作呢?在这种情况下,可以使用 Low(string) 作为第一个值的索引,使用 High(string) 作为最后一个值的索引。无论编译器对字符串索引的基数设置如何,它们都能返回正确的值:
var
S: string;
I: Integer;
begin
S := 'Hello world';
for I := Low(S) to High(S) do
Show(S[I]);
换句话说,字符串中的元素总是对同一字符串调用Low函数和High函数的结果之间的元素。
注解: 字符串就是字符串,基于零的字符串概念是完全错误的。内存中的数据结构没有任何不同,因此您可以将任何字符串传递给使用任何基值符号的函数,完全没有问题。换句话说,如果你有一个使用零基符号访问字符串的代码片段,你可以将字符串传递给一个设置索引从1开始编译的函数。