心血来潮,仔细分析比较了一下 Delphi 字符串的各种陷阱。
字符串的声明
可以用以下三种方式来声明字符串:
字符串定义方式 #1
定义方式:S: string;
初始化:S := ‘Hello World’;
说明:这种情况是编译器默认处理的情况,编译器初始化了字符串 S,分配内存调整引用计数,其中 S[0] 为字符串长度,但是不能直接访问,必须使用 Length 或者 SetLength 函数来访问。
字符串定义方式 #2
定义方式:S: PChar;
初始化:S := ‘Hello World’;
说明:这种情况下,实际上编辑器先做了字符串的初始化,再将字符串赋值给字符指针。Delphi 编译器自动处理 PChar 和 String 的转换过程。
字符串定义方式 #3
定义方式:S: array [1..256] of Char;
初始化: S := ‘Hello World’;
说明:这种情况下,对字符串的初始化长度必须小于给定的数组长度,否则默认情况下编译出错。
另外,不能使用的是 s: array of Char; 这种方式的定义,这种方式定义了一个动态数组必须使用 SetLength 来初始化长度,但仍然不能直接对其进行字符串赋值,只能是当作一个数组来使用了。
使用 FillChar 字符串和数组的区别
对于字符串的 FillChar 初始化:
const Len = 10;
var S: string;
begin
SetLength(S, Len);
FillChar(S[1], Len, 0);
end;
字符串的 FillChar 调用的第一个参数必须是符串的第一个字符,下标是从 1 开始的。 注意,S[0] 是不允许访问的。也可以使用 PChar 来做这件事情:
var
S: String;
P: PChar;
begin
SetLength(S, 5);
P := PChar(S);
FillChar(P[0], 5, 0);
end;
此时,对 P 进行 FillChar 操作时,实际上时按照数组方式来进行,在进行 PChar 转换后, P[0] = S[1] ,因此 P 的下标为 0 。
S := 'Hello';
P := PChar(S);
则: S[1] = P[0] = ‘H’
PChar 和 String 的通用
procedure TForm1.Button1Click(Sender: TObject);
var
S: string;
P: PChar;
begin
S := 'Hello World';
P := PChar(S);
ShowMessage(P);
end;
即:PChar 可以直接当作字符串来使用。连 S := S + P 这样的式子都没有错。
PChar 的使用
S := 'Hello';
P := PChar(S);
实际上 P 此时理论上(使用字符串常数初始化时实际上不是这样)
指向 S[1],需要特别注意 的是,PChar 是指向字符的指针,因此又存在
如下关系:
P[0] = S[1] = P^ = 'H'
P^ 指的是指针指向的内容。同样也存在这样的关系:
Integer(P) = Integer(@p[0])
当使用 SetLength(S, 10) 来初始化 S 时,下列关系成立:
Integer(P) = Integer(@p[0]) = Integer(@S[1])
但是使用字符串常数初始化时实际上不是这样:
Integer(P) = Integer(@p[0]) != Integer(@S[1])
但是 S[1] 的地址却和 P 的地址不同,是否 S := ‘Hello’ 时,S 被复制了?这儿又是一个编译器魔法)
PChar 和 PShortString
PChar 的误区
由于 PChar 往往能够和字符串通用,例如:
P: PChar;
P := PChar(S);
ShowMessage(P)
因此,往往会误理解为 PChar 就是字符串,所以在获取字符时
会使用错误用法:
P^[0] (错误用法)
正确的用法是:
P[0]
P 实际上是指向了第一个字符的指针。
PShortString 的用法
对于 PShortString ,它是一个指向字符串的指针。因此,如果
PStr: PShortString;
则 PStr^ 指的是一个字符串,因此获取字符串的内部字符时使用
PStr^[0] (合法形式,但需要注意此时下标从 0 开始)
这里使用 PShortString 又有一个很奇怪的场景:
PStr: PShortString;
S: string;
PStr := PShortString(S); // (正确用法)
// PStr := @S; // (错误用法)
显然,这个用法相当让人费解,不禁让人怀疑其实 String 内部就是一个指针(编译器魔法)错误用法 PStr := @S 合乎对指针的理解: PStr 是指向字 符串的指针,那么现在就取字符串的地址并赋值给它。但是事实上却不管用。正确用法: PStr := PShortString(S); 包括 P := PChar(S) 都显然不 符合指针的定义的。
此处可见 Delphi 本身语法的不统一性。
后记
本人接触 Delphi 已经不下 5 年,但平时若不仔细推敲 Delphi 语法,仍然会深陷其编译器陷阱中,其实很惭愧,本人以前太浮躁。从某种意义上讲,Delphi 在快速开发的同时隐藏了太多的细节,但这些细节又隐藏的不太好,时常又会暴露给程序员。此处涉及到的字符串相关的类型还比较少,除了上述类型外,Delphi 中的字符串还包括 Ansi 和 Wide 系列。