byte)那样来使用 ShortString,比如我们可以用下标来访问 ShortString 中的各个字符,可以用 High 和 Low 函数来获取 ShortString 的上限位置和下限位置。由于字符串的第一个字节存放的是字符串的长度,所以 SStr[
] 存放的是字符串的长度,例如:
SStr:
string
begin
SStr :=
'ABC'
{ 此时:
Ord(SStr[0]); // = 3 字符串长度为 3
SStr[1]; // = 'A' 第一个字符为 A
SStr[2]; // = 'B' 第二个字符为 B
SStr[3]; // = 'C' 第三个字符为 C
SStr[4]; // = #0 其余位置清零 #0
…… // 其余位置清零 #0
SStr[255]; // = #0 其余位置清零 #0
High(SStr); // = 16 上限位置为 16
Low(SStr); // = 0 下限位置为 0 }
接下来,我们来看看 SStr 的指针情况。
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
SStr: ShortString;
pS: Pointer;
pS1: Pointer;
begin
SStr :=
'ABC'
pS := Addr(SStr);
{ 字符串变量 SStr 的地址 }
pS1 := Addr(SStr[
{ 字符串的首地址 }
Memo1.Clear;
Memo1.Lines.Add(IntToStr(Integer(pS)));
{ 在我的电脑中显示为:1242240 }
Memo1.Lines.Add(IntToStr(Integer(pS1)));
{ 在我的电脑中显示为:1242240 }
----------
上面的代码说明变量 SStr 的地址就是“存放字符串的内存块”的地址。这和后面讲到的 AnsiString 和 WideString 不同。
这里为什么不用 Sizeof(AStr); 而用 Length(AStr); 因为 AStr 和 ShortString 不同,变量 AStr 的地址并不是“存放字符串的内存块”的地址,变量 AStr 中存放的只是一个指针,指向“存放字符串的内存块”,我们通过变量 AStr 可以找到“存放字符串的内存块”,请看下面的代码:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
AStr: AnsiString;
pA: Pointer;
pA1: Pointer;
begin
AStr :=
'ABC'
pA := Addr(AStr);
{ 字符串变量 AStr 的地址 }
pA1 := Addr(AStr[
{ “存放字符串的内存块”的地址。Delphi 不允许访问 AStr[0] }
Memo1.Clear;
Memo1.Lines.Add(IntToStr(Integer(pA)));
{ 在我的电脑中显示为:1242504 }
Memo1.Lines.Add(IntToStr(Integer(pA1)));
{ 在我的电脑中显示为:17539780 }
----------
从上面的代码中可以看出,字符串变量 AStr 的地址和“存放字符串的内存块”的地址是不一样的。所以用 Sizeof(AStr) 无法获取字符串的大小,只能获取一个指针的大小,永远为 Sizeof(Pointer);
刚才说了,AnsiString 变量在刚声明的时候是不分配内存的,所以就不能使用 AStr[
],因为 AStr[
] 是指“被分配的内存块”中的第一个字符,而“内存块”根本就没有分配,哪来的第一个字符?所以在使用 AStr[
] 之前首先要判断 Length(AStr) 是否为
,就表示内存块未分配,就不能使用 AStr[
那什么时候 AStr 才分配内存呢?当我们给 AStr 赋值的时候,Delphi 就给 AStr 分配内存,例如:
----------
AStr: AnsiString;
{ 此时 AStr 未分配内存 }
begin
AStr :=
'ABC'
{ 此时 Delphi 给 AStr 分配了三个字节的内存用来存放 'ABC' }
ShowMessage(AStr[
{ 这时就可以使用 AStr[1] 了 }
AStr :=
'ABCDEF'
{ 此时 Delphi 增大字符串的内存空间来存放更多字符 }
AStr :=
{ 此时 AStr 将刚分配的内存全部释放 }
ShowMessage(AStr[
{ 错误,因为此时未分配内存,所以不可以使用 AStr[1] }
----------
那么变量 AStr 和“存放字符串的内存块”之间是什么关系呢?变量 AStr 中存放的其实是一个指针,指向“存放字符串的内存块”。通过下面的代码可以很好的理解这个问题:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
AStr: AnsiString;
pA: Pointer;
{ 变量 AStr 的地址 }
pA1: Pointer;
{ 字符串中第一个字符的地址(即:内存块的地址) }
pAP: Pointer;
{ 变量 AStr 中所存放的指针 }
begin
AStr :=
'ABC'
{ 申请三个字节的内存块用来存放 'ABC' }
pA := Addr(AStr);
{ 获取变量 AStr 的地址 }
pA1 := Addr(AStr[
{ 获取内存块的地址 }
pAP := Pointer(AStr);
{ 获取变量 AStr 中所存放的指针 }
Memo1.Clear;
Memo1.Lines.Add(IntToStr(Integer(pA)));
{ 变量 AStr 的地址 }
Memo1.Lines.Add(IntToStr(Integer(pA1)));
{ 内存块的地址 }
Memo1.Lines.Add(IntToStr(Integer(pAP)));
{ 变量 AStr 中所存放的指针 }
----------
{ 运行结果 }
1242504
{ 变量 AStr 的地址 }
17539780
{ 内存块的地址 }
17539780
{ 变量 AStr 中所存放的指针 }
----------
由此可见 AStr 中存放的只是一个指针,指向“存放字符串的内存块”,我们可以通过 Pointer(AStr) 来得到这个内存块的地址。
上面的代码有一个奇怪的地方,就是将 pA1 := Addr(AStr[
]); 和 pAP := Pointer(AStr); 两行代码的前后位置对调一下,运行结果就不同了(测试环境:Delphi XE2),下面是对调以后的运行结果:
----------
{ 对调以后的运行结果 }
1242504
{ 变量 AStr 的地址 }
17539780
{ 内存块的地址 }
5327792
{ 变量 AStr 中所存放的指针 }
----------
这或许是 Delphi 对字符串优化所造成的结果(Delphi 的 copy-
write
技术),当我们仅仅是读取该字符串时,它的地址就是刚赋初始值时的地址(
5327792
),而当我们要修改字符串时( pA1 := Addr(AStr[
]) 也被认为是用户将要通过指针修改字符串),Delphi 就会把该字符串复制到新的地方(
17539780
)给用户修改并继续使用。如果“旧地址中的字符串”还同时被其它变量引用,比如 AStr :=
'ABC'
之后又 AStr2 := AStr,则保留旧地址,供 AStr2 继续使用,如果旧地址不再被其它变量使用,则将旧地址中的字符串全部释放。 )
为了检验以上说法,我们用下面的代码再测试一次,这次,我们将 AnsiString 定义为常量,不允许修改,看看结果如何:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
const
AStr: AnsiString =
'ABC'
pA: Pointer;
{ 变量 AStr 的地址 }
pA1: Pointer;
{ 字符串中第一个字符的地址(即:内存块的首地址) }
pAP: Pointer;
{ AStr 中所存放的指针 }
begin
pA := Addr(AStr);
{ 获取变量 AStr 的地址 }
pAP := Pointer(AStr);
{ 获取变量 AStr 中所存放的指针(放前面) }
pA1 := Addr(AStr[
{ 获取内存块的地址(放后面) }
Memo1.Clear;
Memo1.Lines.Add(IntToStr(Integer(pA)));
{ 变量 AStr 的地址 }
Memo1.Lines.Add(IntToStr(Integer(pA1)));
{ 内存块的地址 }
Memo1.Lines.Add(IntToStr(Integer(pAP)));
{ 变量 AStr 中所存放的指针 }
----------
{ 此时的运行结果 }
接下来有必要对 Length 函数做一下说明,Length 函数对于 ShortString 和 AnsiString 来说,都是指“字符串中的字节数(而不是字符数)”,请看下面的例子:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
SStr: ShortString;
AStr: AnsiString;
begin
Memo1.Clear;
SStr :=
'123你好吗'
{ 一共 6 个字符 }
AStr :=
'123你好吗'
{ 一共 6 个字符 }
Memo1.Lines.Add(IntToStr(Length(SStr)));
{ 结果 9 }
Memo1.Lines.Add(IntToStr(Length(AStr)));
{ 结果 9 }
----------
因为一个英文字符只占用
个字节的内存空间,而一个汉字要占用
个字节的内存空间,所以 SStr 和 AStr 都用了
个字节的内存空间来存放字符串。如果此时你使用 AStr[
] 来获取汉字“你”,那么只能获取半个汉字,此时的 AStr[
] 是 AnsiChar 类型(单字节)。所以用 AnsiString 处理汉字是很麻烦的,我们一般用 WideString 来处理带有汉字的字符串。
关于 Delphi 不允许访问 AStr[
],其实 AnsiString 和 ShortString 有着类似的结构,也就是说在 AnsiString 字符串的前面也有一个数据区用来保存 AnsiString 的长度(字节数),这个数据区就是字符串之前的
个字节,我们可以通过指针来访问这个区域,而再之前的
个字节中还存放着字符串的引用计数,标识着这个字符串被几个字符串变量所引用。在 Delphi
的 system 单元中可以找到字符串结构的定义:
StrRec =
packed
record
refCnt: Longint;
{ 引用计数 }
length: Longint;
{ 字符串长度 }
请看下面的代码:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
AStr: AnsiString;
P: PCardinal;
{ Sizeof(Cardinal) = 4 字节 }
begin
Memo1.Clear;
SetLength(AStr,
65530
P := PCardinal(AStr);
Dec(P);
{ 向前移动 4 个字节 }
Memo1.Lines.Add(IntToStr(P^));
{ 结果 65530 字符串长度 }
Dec(P);
{ 再向前移动 4 个字节 }
Memo1.Lines.Add(IntToStr(P^));
{ 结果 1 引用计数 }
----------
从上面的结果可以看出,字符串的长度为
65530
,引用计数为
关于 AnsiString 中可以存放的内容,有人说 AnsiString 中存放的是以
结尾的字符串,和 C 语言的字符串类似,这样理解不准确,因为 AnsiString 中可以存放多个
并不一定代表字符串的结尾。例如:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
AStr, AStr2: AnsiString;
I: Integer;
begin
Memo1.Clear;
SetLength(AStr,
{ 在 AStr 中存放 10 个 #0 字符 }
AStr[I] :=
AStr2 := AStr;
{ 看看赋值的过程中会不会发生字符丢失 }
AStr2[
Memo1.Lines.Add(IntToStr(Length(AStr2)));
{ 结果 10,没有丢失字符 }
----------
也就是说 AnsiString 可以直接当内存来使用,它不只可以存放字符,而是可以存放任何东西,你甚至可以将一个图片的数据存入 AnsiString 的内存块中。用 AnsiString 代替内存使用有一个好处,就是 Delpih 会帮你管理这个内存,在你不需要使用该内存块的时候,Delphi 会自动帮你释放。
但是这样的字符串不能和 PAnsiChar 类型一起使用,因为 PAnsiChar 才是真正的以
结尾的字符串(我们刚才不是说了 AnsiString 其实就是一个指针吗,PAnsiChar 也是一个指针,在用法上和 AnsiString 有很多相同的地方),当你把上面的字符串赋值给一个 PAnsiChar 变量时,PAnsiChar 只会读取第一个
之前的内容,而把第一个
之后的所有内容都丢弃掉。请看下面的代码:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
AStr: AnsiString;
pAStr: PAnsiChar;
I: Integer;
begin
Memo1.Clear;
SetLength(AStr,
AStr[I] :=
pAStr :=
'ABC'
Memo1.Lines.Add(IntToStr(Length(pAStr)));
{ 结果 3 }
pAStr := PAnsiChar(AStr);
Memo1.Lines.Add(IntToStr(Length(pAStr)));
{ 结果 0 }
pAStr :=
Memo1.Lines.Add(IntToStr(Length(pAStr)));
{ 结果 0 }
----------
好了,总结一下 AnsiString:
AnsiString 变量只是一个指针,指向一个内存块,这个内存块用来存放实际的字符串。AnsiString 所指向的内存是动态分配的,如果 AnsiString 中未存放任何字符串,则 AnsiString 指向
虽然 AnsiString 变量并不是真正的内存块,只是一个指针,但是我们仍然可以通过下标的方式来访问内存块中的字符。下标必须从
开始,Delphi 不允许 AnsiString 和 WideString 使用下标
我们可以通过如下方式访问到 AStr 所指向的内存块地址:
@AStr[
Pointer(AStr)
我们可以通过如下方式访问到 AStr 所指向的内存块中的第一个字符、第二个字符、第三个字符:
AStr[
]、AStr[
]、AStr[
PAnsiChar(AStr)^ 、(PAnsiChar(AStr)+
)^ 、(PAnsiChar(AStr)+
PAnsiChar 类型和 AnsiString 类型之间可以很容易的相互转换。不过编译器在编译的时候会给出警告(不是错误)。例如:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
AStr: AnsiString;
pAStr: PAnsiChar;
begin
Memo1.Clear;
AStr :=
'ABC'
pAStr := PAnsiChar(AStr);
Memo1.Lines.Add(AStr);
Memo1.Lines.Add(pAStr);
Memo1.Lines.Add(
pAStr :=
'123'
AStr := pAStr;
Memo1.Lines.Add(AStr);
Memo1.Lines.Add(pAStr);
----------
关于 Length 函数,Length 函数对于 ShortString 和 AnsiString 来说返回的是它们所存放的字符串的字节数,而不是字符数。
在 AnsiString 的字符串之前的
个字节中存放着字符串的总长度(总字节数),再之前的
个字节中存放着字符串的引用计数。
AStr[n] 只能返回半个汉字,所以用 AnsiString 处理汉字是很麻烦的,我们一般用 WideString 来处理带有汉字的字符串。
Delphi 对 AnsiString 和 WideString 都使用了 copy-
write
技术,所以当我们不准备修改字符串的内容时,最好将字符串声明为常量,以提高程序的执行效率。
Delphi 中的 AnsiString 可以直接当做内存来使用,如果你愿意这么用的话,Delphi 会帮你管理这块内存。申请内存很方便,直接 SetLength 就可以了。用完了 Delphi 会帮你释放。但此时 AnsiString 不能和 PAnsiChar 一起使用。要得到内存的地址,可以使用 Pointer(AStr)。
之后又加入了一种新的字符串:UniodeString,并且对字符串的结构也做了改动,增加了 codePage 和 elemSize 域。下面是 System 单元中定义的字符串结构:
PStrRec = ^StrRec;
StrRec =
packed
record
codePage: Word;
// 代码页:Unicode、UTF-8、UTF-16、GB2312
elemSize: Word;
// 元素大小:一字符占几个字节
refCnt: Longint;
// 引用计数:字符串被几个字符串变量使用
length: Longint;
// 字符串长度:字节数
这个结构只用于 AnsiString 和 UnicodeString,也就是说在 AnsiString 和 UnicodeString 的字符串之前的
个字节存放的是字符串长度,再之前的
个字节存放的是字符串的引用计数,再之前的
个字节存放的是元素大小,再之前的
个字节存放的是字符串的代码页。而 WideString 依然是原来的样子,没有变化。所以我们以后可以直接使用 UnicodeString 来处理汉字。
在 system 单元中还定义了 UTF8String 和 UCS4String 类型的字符串,定义如下:
UTF8String =
AnsiString(
65001
UCS4String =
array
UCS4Char;
{ UCS4Char = type LongWord; }
除此之外,Delphi 还定义了 RawByteStrng 类型的字符串,定义如下:
RawByteString =
AnsiString(
$ffff
关于RawByteStrng 类型:在将 AnsiString 格式的字符串赋值给 UTF8String 格式的字符串时,Delphi 会自动进行格式转换(还有其它格式的自动转换),所以,如果我们有一个函数的参数需要接收各种类型的字符串时,那么就很难实现,因为在传递参数的时候,Delphi 就会自动进行格式转换,所以,Delphi 定义了 RawByteString 类型,这种类型的变量在接收任何格式的字符串时,都会保持源字符串的内存格式,不做任何改动。
同样的 Char 在 Ansi 版本中就代表 AnsiChar,在 Unicode 版本中就代表 WideChar。PChar 在 Ansi 版本中就代表 PAnsiChar,在 Unicode 版本中就代表 PWideChar。
在 Delphi 的 Unicode 版本中有一个新的函数 ByteLength 用来获取字符串的字节数。但是这个函数只能用于
string
类型的变量,我们看一下它的源代码就知道为什么了。
----------
function
ByteLength(
const
string
): Integer;
begin
Result := Length(S) * SizeOf(Char);
----------
这个函数会根据不同的编译条件产生不同的计算结果,所以只能配合
string
使用。如果你将它用于 AnsiSting 或 UnicodeString 型变量,则在不同的编译条件下,有可能会出现错误。请看下面的代码:
----------
{ 在一个空白窗体上放置一个 TMemo 和一个 TButton }
procedure
TForm1.Button1Click(Sender: TObject);
SStr: ShortString;
AStr: AnsiString;
UStr: UnicodeString;
WStr: WideString;
string
begin
SStr :=
'123你好吗'
AStr :=
'123你好吗'
UStr :=
'123你好吗'
WStr :=
'123你好吗'
Str :=
'123你好吗'
Memo1.Clear;
Memo1.Lines.Add(IntToStr(Length(SStr)));
{ 结果 9 }
Memo1.Lines.Add(IntToStr(Length(AStr)));
{ 结果 9 }
Memo1.Lines.Add(IntToStr(Length(UStr)));
{ 结果 6 }
Memo1.Lines.Add(IntToStr(Length(WStr)));
{ 结果 6 }
Memo1.Lines.Add(IntToStr(Length(Str)));
{ 结果 6 }
Memo1.Lines.Add(IntToStr(ByteLength(SStr)));
{ 结果 12 }
Memo1.Lines.Add(IntToStr(ByteLength(AStr)));
{ 结果 12 }
Memo1.Lines.Add(IntToStr(ByteLength(UStr)));
{ 结果 12 }
Memo1.Lines.Add(IntToStr(ByteLength(WStr)));
{ 结果 12 }
Memo1.Lines.Add(IntToStr(ByteLength(Str)));
{ 结果 12 }
----------
一、Delphi 2009 之前的字符串(不支持 Unicode): Delphi 2009 之前的字符串分为 3 种:ShortString、AnsiString、WideString。【ShortString】 ShortString 是一种比较古老的 Pascal 字符串格式,它最多只能容纳 255 个字节的字符。当我们声明一个 ShortStri
//最常用的 stringvar str: string; {定义}begin str := '万一'; {赋值} ShowMessage(IntToStr(Length(str))); {长度是: 4}end;
AnsiString; 在当前版本(2007)
的默认状态下, String 就是 AnsiStringvar str: AnsiString;begin...
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
TForm1 = cl...
坏坏的海龟
Newtonsoft.Json.JsonSerializationException: Error converting value - Studio - UiPath Community Forum