6.3.7 字符串的内部结构
一般情况下虽然使用字符串不需要了解其太多内部结构,但适当了解一下这种数据类型背后的实际数据结构还是很有趣的。在早期的 Pascal 语言中,字符串最多有 255 个元素,每个字符一个字节,第一个字节(或零字节)用于存储长度。从早期到现在已经过去了很长时间,但将字符串的一些额外信息作为其数据的一部分来存储的概念仍然是 Object Pascal 语言所采用的方法(与许多源自 C 语言并使用字符串结束符概念的语言不同)。
注解:ShortString 是传统 Pascal 字符串类型的名称,它是一个由一个byte字符或一个AnsiChar 字符组成的字符串,长度限制为 255 个字符。在桌面编译器中仍可使用 ShortString 类型,但在移动编译器中则不可用。您可以使用byte动态数组或 TBytes 表示类似的数据结构,也可以使用普通的Byte元素静态数组表示类似的数据结构。
如前所述,字符串变量只是指向堆上分配的数据结构的指针。实际上,存储在字符串中的值并不是对数据结构开头的引用,而是对字符串第一个字符的引用,字符串的元数据数值可从该位置的负偏移量获得。字符串类型数据的内存表示如下:
-12 | -10 | -8 | -4 | String引用地址 |
---|---|---|---|---|
编码页 | 元素大小 | 引用计数 | 长度 | 第一个字符 |
-12 -10 -8 -4 字符串引用地址 代码页 元素大小 引用计数 长度 字符串的第一个字符
第一个元素(从字符串本身开始倒数)是定义字符串长度的整数,第二个元素是引用计数。其他字段(用于桌面编译器)是以字节为单位的元素大小(1 或 2 字节),以及基于 ANSI 的旧字符串类型的编码页。
令人惊讶的是,除了显而易见的长度函数外,大多数字段都可以通过特定的底层字符串元数据函数来访问:
function StringElementSize(const S: string): Word;
function StringCodePage(const S: string): Word;
function StringRefCount(const S: string): LongInt;
例如,您可以创建一个字符串并查询其相关信息,就像我在StringMetaTest示例中所做的那样:
var
Str1: string;
begin
Str1 := 'F' + string.Create('o', 2);
Show('SizeOf: ' + SizeOf(Str1).ToString);
Show('Length: ' + Str1.Length.ToString);
Show('StringElementSize: ' + StringElementSize(Str1).ToString);
Show('StringRefCount: ' + StringRefCount(Str1).ToString);
Show('StringCodePage: ' + StringCodePage(Str1).ToString);
if StringCodePage(Str1) = DefaultUnicodeCodePage then
Show('Is Unicode');
Show('Size in bytes: ' + (Length(Str1) * StringElementSize(Str1)).ToString);
Show('ByteLength: ' + ByteLength(Str1).ToString);
注解:程序动态构建 "Foo "字符串而不是固定为常量有一个特殊原因,那就是常量字符串的引用计数被禁用(或设置为-1)。在演示中,我倾向于为引用计数显示一个适当的值,因此采用了动态字符串结构。
该程序产生类似以下的输出:
SizeOf: 4
Length: 3
StringElementSize: 2
StringRefCount: 1
StringCodePage: 1200
Is Unicode
Size in bytes: 6
ByteLength: 6
UnicodeString类型变量返回的编码页是1200,这个数字存储在全局变量 DefaultUnicodeCodePage 中。在上面的代码(及其输出)中,你可以清楚地看到字符串变量的大小(一律为 4)、逻辑长度和物理长度(以字节为单位)之间的差异。
将每个字符的字节数乘以字符数,或者调用 ByteLength,就可以得到逻辑长度。不过,该函数不支持旧版桌面编译器的某些字符串类型。#### 6.3.7 字符串的内部结构
一般情况下虽然使用字符串不需要了解其太多内部结构,但适当了解一下这种数据类型背后的实际数据结构还是很有趣的。在早期的 Pascal 语言中,字符串最多有 255 个元素,每个字符一个字节,第一个字节(或零字节)用于存储长度。从早期到现在已经过去了很长时间,但将字符串的一些额外信息作为其数据的一部分来存储的概念仍然是 Object Pascal 语言所采用的方法(与许多源自 C 语言并使用字符串结束符概念的语言不同)。
注解:ShortString 是传统 Pascal 字符串类型的名称,它是一个由一个byte字符或一个AnsiChar 字符组成的字符串,长度限制为 255 个字符。在桌面编译器中仍可使用 ShortString 类型,但在移动编译器中则不可用。您可以使用byte动态数组或 TBytes 表示类似的数据结构,也可以使用普通的Byte元素静态数组表示类似的数据结构。
如前所述,字符串变量只是指向堆上分配的数据结构的指针。实际上,存储在字符串中的值并不是对数据结构开头的引用,而是对字符串第一个字符的引用,字符串的元数据数值可从该位置的负偏移量获得。字符串类型数据的内存表示如下:
-12 | -10 | -8 | -4 | String引用地址 |
---|---|---|---|---|
编码页 | 元素大小 | 引用计数 | 长度 | 第一个字符 |
-12 -10 -8 -4 字符串引用地址 代码页 元素大小 引用计数 长度 字符串的第一个字符
第一个元素(从字符串本身开始倒数)是定义字符串长度的整数,第二个元素是引用计数。其他字段(用于桌面编译器)是以字节为单位的元素大小(1 或 2 字节),以及基于 ANSI 的旧字符串类型的编码页。
令人惊讶的是,除了显而易见的长度函数外,大多数字段都可以通过特定的底层字符串元数据函数来访问:
function StringElementSize(const S: string): Word;
function StringCodePage(const S: string): Word;
function StringRefCount(const S: string): LongInt;
例如,您可以创建一个字符串并查询其相关信息,就像我在StringMetaTest示例中所做的那样:
var
Str1: string;
begin
Str1 := 'F' + string.Create('o', 2);
Show('SizeOf: ' + SizeOf(Str1).ToString);
Show('Length: ' + Str1.Length.ToString);
Show('StringElementSize: ' + StringElementSize(Str1).ToString);
Show('StringRefCount: ' + StringRefCount(Str1).ToString);
Show('StringCodePage: ' + StringCodePage(Str1).ToString);
if StringCodePage(Str1) = DefaultUnicodeCodePage then
Show('Is Unicode');
Show('Size in bytes: ' + (Length(Str1) * StringElementSize(Str1)).ToString);
Show('ByteLength: ' + ByteLength(Str1).ToString);
注解:程序动态构建 "Foo "字符串而不是固定为常量有一个特殊原因,那就是常量字符串的引用计数被禁用(或设置为-1)。在演示中,我倾向于为引用计数显示一个适当的值,因此采用了动态字符串结构。
该程序产生类似以下的输出:
SizeOf: 4
Length: 3
StringElementSize: 2
StringRefCount: 1
StringCodePage: 1200
Is Unicode
Size in bytes: 6
ByteLength: 6
UnicodeString类型变量返回的编码页是1200,这个数字存储在全局变量 DefaultUnicodeCodePage 中。在上面的代码(及其输出)中,你可以清楚地看到字符串变量的大小(一律为 4)、逻辑长度和物理长度(以字节为单位)之间的差异。
将每个字符的字节数乘以字符数,或者调用 ByteLength,就可以得到逻辑长度。不过,该函数不支持旧版桌面编译器的某些字符串类型。