德尔福在深度的字符串
字符串是一个最Delphi开发神秘的位。事实上,几乎在每一个新版本添加的新功能,可能不利于局势。例如,一些开发商都知道字符串,ShortStrings,长串,AnsiStrings,WideStrings,openstrings等之间的差异。
ShortString短
我们要从头开始。在德尔福的直接祖先的Turbo Pascal的日子里,只有一种字符串:ShortString的类型。虽然这是我们如何把它这些天,在Turbo Pascal它是默认的字符串类型,并作为字符串简称。ShortStrings堆栈上的“活”(除非“手动”的分配),而不是在堆(点击这里 讨论堆)。从内存分配点的观点,因此,ShortString的是没有从静态分配的其他类型,如整数,布尔值,记录和枚举类型不同。因为它是静态分配的,这是明显的ShortString短的大小在运行时不能改变。因此,如插入,连接等操作造成问题。为了规避这个问题,预分配的Turbo Pascal和Delphi的最大大小为256字节的每一个字符串实例。ShortString短以来的第一个元素(ASTRING [0])用来存放字符串的实际长度,可能有最多255个字符,其中,交通方便,也是字符长度字节可以索引的最大数量。例如,一个变量声明为:
VAR S:ShortString短;
实际上消耗堆栈上的256个字节。ShortString的变量,消耗256个字节,实际的“有效载荷”字符串恰好是任何长度,无论是局部变量,全局变量或对象或记录内。为了更好地理解ShortString的结构,我们提出了“语义等效”,这是怎样一个用户可能会宣布ShortString的,如果它没有内置到语言。一个ShortString的上述声明是语义上等同于:
VAR S:ARRAY [0 .. 255] 的字符;
和ShortString的行动:
1):= 'ABC' ;
2)S:= S + T;
语义上等同于:
1)[1]:= 'A' ; [2]:= 'B' ; [3]:= 'C' ; [0]:=#3; 2)移动([1],[ORD([0])+1],ORD(T [0])); [0]:= CHR(ORD(S [0])条例“(T [0]));
事实上,你可以实际访问[0]与它的代码和实验。一个ShortString短的内存布局如下所示, 其中 n 是存储在字符串中的有效载字符数
最大尺寸为字符串字符串默认长度,也就是说,宣布为根本,始终为255。决不允许一个ShortString的增长到超过255个字符。施加这种限制的计数字段(1无符号字节)的大小,也由字符串浪费大量未使用的空间(IM)的实用性。有一种方法来解决这个流失的问题有点。的Turbo Pascal和Delphi都允许一个字符串,指定其最大大小时,它被宣布。例如:
VAR S:字符串[20]; / /最大20字符
声明了一个字符串,最多只能20个字符长。它仍然保留一个长度字节,因此实际消耗21个字节。但是这样做提出了新的问题。由于其强大的类型检查,TP不容许声明具有一定的长度作为参数传递到一个函数期待一个不同的字符串长度的字符串类型:
/ /'严格VAR字符串“变成对 类型 string20 =字符串[20]; string30 =字符串[30]; 美孚(VAR ASTRING:String20)的; ... VAR S:String30; 美孚(S); / /错误:类型不兼容
为什么这是一个问题的一个技术上更正确的解释是,总磷,弦就像对待任何其他参数,也就是说,他们被放在栈中。期待一定长度的字符串函数,然后保留在堆栈上适当数量的空间。因此,通过不同长度的字符串,可以提出问题。为了解决这个问题,openstring类型进行了介绍。这是一种“通用”的字符串类型,可以接受一个字符串宣布任何长度:
类型 string20 =字符串[20]; string30 =字符串[30]; 过程酒吧(ASTRING VAR:openstring) ... VAR S:String20; T:String30; 酒吧(S); / / OK 酒吧(T); / /确定
请注意,这仅适用于可变(VAR)参数。值参数,字符串参数的副本必须是在栈上,编译器总是分配一个最大长度的字符串举行的参数。也有“严格的VAR字符串”设置,关闭时,宽松的字符串类型检查。在Delphi中,任何指定的字符串声明被认为是最大长度ShortString短。此外,最大长度规范不得超过255个:
VAR S:字符串[256]; / /错误
PChar类型
ShortString的让步,但仍然没有解决的上限为255个字符,这是一个真实世界的应用非常严重的限制。相比之下,C语言,其中的Windows API是“友好”,字符指针(char *)。它允许任何长度的限制只能由本地指针类型可以访问多少内存,可用内存当然,字符串。然而,它要求用户明确的管理分配/释放所有的字符串,产生了微妙的错误,并与许多运营效率低下,任何长度的功能,因为每次调用遍历整个字符串。
1德尔福介绍PChar类型,这是相当于C语言的char指针。在16位的Delphi 1,它有65,535个字符的限制。这个限制被判处16位Windows的内存分割。在C中,你也有明确的管理PChar类型分配/释放(令人惊讶的是,默认的字符串类型仍然是ShortString的,仅限于255个字符)。PChars与ShortString短,实际上是指针,和所有需要(见赋值语义部分)。许多开发商也误解了他们。一个常见的错误是:
VAR S:PChar类型; ... S:= '你好' ; / /危险:静态数据的指针;分配无记忆 小号:= S + '世界',/ /错误:内存损坏;可能未被发现
在这个例子中,没有分配内存,和s的简单加载的地址字符串的位置'你好',编译器在任何地方选择把它通常与灾难性的后果。最重要的事情要记住的是PChar类型,不是一个简易替换字符串类型的默认。一个PChar变量与ShortString短,只有4个字节的大小(如sizeof(PChar类型)= 4)。这是被指向的字符数组,字符串数据实际上持有。
另外,ShortString的字符索引1(因为长度字节)开始,,而PChars是从零开始的;有没有“长度字节”。本公约直接复制到C。一般,内存必须被分配明确的PChars,使用StrAlloc,/ StrDispose或getmem函数/ FreeMem。明确分配的地方,也可以成为一个字符数组作为缓冲区PChars:
VAR S:PChar类型; ARR:数组 [0 .. 20] 的 char / / 20个字符的空间,加上空 ... S:= @ ARR;
但是,这可能导致微妙的错误:
/ /这个函数是有缺陷的,不使用 的功能组合(S1,S2:PChar类型):PChar类型; (*返回串联字符串,叶源的完整*) BUF VAR:数组 [0 .. 1024 ]字符; 开始 结果:= @ BUF; StrCopy(结果,S1); strcat的(结果,S2); 结束 ; / /错误:将返回随机堆栈数据
根据函数的堆栈占用的内存区域会发生什么情况,这个功能甚至可能出现的工作,而偶尔返回随机数据。虽然这是危险的,明确的(动态)分配,使用静态分配,另一方面,用户可能忘记释放分配的内存。提供的地方,null结尾的字符串例程必须始终与PChars工作时使用。这些函数的名称,一般开始与“STR”的前缀(例如:StrCopy的strcat,strlen的),像他们的C库同名的工作。
再次,我们提出了语义的等值。对于一个PChar和相关业务:
1)无功号码:PChar类型;
2)电话号码:= StrAlloc(4);
3)StrCopy(P,'ABC');
4)strcat的(P,T);
5)StrDispose(P);
具有语义现金等价物:
1)无功号码:^字符; 2)getmem函数P,(4); 3)P ^ [0]:= 'A' ; P ^ [1]:= 'B' ; P ^ [2]:= 'C' ; P ^ [3]:=#0; 4){某种方式找到的第一个零字符指数从开始的p,存储在临时变量_p} {不知何故第一零指数从开始的T,存储在临时变量_T字符} 移动(P ^ _p],T ^ [0],_T); P ^ [_p + _T]:=#0; 5)FreeMem(P);
在这里,我们看到一个PChar变量实际上是一个指针,德尔福只是让我们写p [1],而不是P ^ [1]。注意{某种方式找到的第一个零字符的索引}在第4项业务。这说明严重的性能损失,strlen的每个()调用的程序产生。由于没有简单的方式来“看”一个PChar的长度,整个字符串必须加强通过找到的第一个零字符,其指数将字符串的长度相当于。
在内存中,将奠定了一个PChar(再次, N 是有效载荷的字符数):
通常情况下,阵列的一部分将在堆上分配,使用某种动态分配功能(StrAlloc,getmem函数),但它也可能是在栈上的数组。事实上,德尔福可以让我们摆脱:
VAR ARR:数组 [0 .. 20] 的字符; 电话号码:PChar类型; ... 电话号码:= ARR;
甚至不使用地址操作符或有强制转换。德尔福在某些情况下甚至会允许一个char数组站在一个PChar;模仿,毫无疑问,C的语法放任
长字符串(AnsiString类型)
提供“低维护”,高效和快速的字符串类型,可容纳大量的字符,32位Delphi 2中引入的长字符串。它也被称为AnsiString的,事实上,它的召开只有1个字节的ANSI字符的参考。它还会自动内存管理,这意味着它可以用于像一个ShortString短而不需要明确的分配/释放,。AnsiString的也作为一个长度字段的32位值,其中,由于Win32的平面地址空间,允许字符串增长到了惊人的2 千兆字节长,当然受内存限制。AnsiString类型的优势还不止于此。C的字符串一样,它也是空结尾的,这意味着,Delphi会自动附加在末尾的零字节(实际上2零字节),这意味着它可以传递给函数需要一个PChar用一个简单的演员。此外,它是引用计数的副本上写的语义(稍后)。Java的对象一样,每一个AnsiString的,除了长度字段,包含一个引用计数。这使得德尔福,删除不再需要它时,它的管理字符串的一生。因此,长串的两全其美:短字符串的方便和效率PChars和存储能力,无不良PChar类型回味。ShortString的一样,AnsiString的字符索引从1开始。他们也可能是自由复制,修改,没有任何返回的数据损坏或内存泄漏的危险,从函数返回:
VAR S:AnsiString的; ... S:= '你好' ; S:= S + '世界',/ / OK,内存自动管理。
在当前版本的Delphi, string类型是简单的AnsiString的别名,虽然这是可以改变编译器指令,字符串实际上可能在未来的版本中默认为WideString的。
在内存中,一个AnsiString的布局是(再次, N =有效载荷的字符数):
一个AnsiString变量实际上是一个指针在内存中的字符串的第一个字符。重要的是要强调的变量指向的第一个字符的字符串,而不是整个结构的第一个元素。这似乎有些奇怪,但只要不构成任何问题,运行时可以找到在内存中的结构(“退一步”的第一要素,它可以简单地使用负偏移)。一个空字符串(S:='' ;)实际上是一个零指针的值:
S:=''; |
这就是为什么试图访问一个空字符串的内容,提高访问冲突。因为他们是空终止,转换为PChar类型是非常有效的。德尔福可以简单地通过一个PChar变量本身(虽然德尔福在这样做之前执行一些额外的检查)。
为了更好地理解长字符串,我们再次提出了一些操作和语义等值。如AnsiString的声明:
VAR S:AnsiString的;
可模拟:
类型 TAnsiStrRec = 包装记录 的refcount:Longint型; 长度:Longint型; 结束 ; TAnsiStrChars = 数组 [1 .. MAXINT-8] 的字符; TAnsiString = ^ TAnsiStrChars; VAR S:TAnsiString;
和下面的操作:
1):= '' ; 2):= 'ABC' ; 3)S:= S + T;
可模拟:
1):无; 2)getmem函数(S sizeof(TAnsiStrHeader)的+ 4); (整数。PAnsiStrHeader(S)-8)的refcount:= 1; (整数。PAnsiStrHeader(S)-8)^长度:= 3; [1]:= 'A' ; [2]:= 'B' ; [3]:= 'C' ; [4]:=#0; 3)/ /分配 getmem函数(T SIZEOF(TAnsiStrHeader)+ PAnsiStrHeader(整数(U)-8)^。长度+ PAnsiStrHeader(整数(V)-8)^长度+ 1); / /复制 移动(U ^ T ^ [1],PAnsiStrHeader(整数(U)-8)^长度); 移动(V ^ T ^ [PAnsiStrHeader(整数(U)-8)^。长度+ 1] PAnsiStrHeader(整数(V)-8)^长度); / /头 (整数。PAnsiStrHeader(T)-8)^的refcount:= 1; (整数。PAnsiStrHeader(U)-8)^长度: PAnsiStrHeader(整数(U)-8)^。长度+ PAnsiStrHeader(整数(V)-8)^长度。;
事实上,德尔福运行时非常相似,这种“幕后”的东西。代码被发现于System.Pas。在上面的例子,为了简单起见,我们特意选择的情况下,我们将不得不操纵引用计数。引用计数是AnsiString的,它区别于旧型的最重要的新功能之一。引用计数和写时拷贝语义将在稍后讨论。
(注:2号线以上的模拟已简化;实际上,德尔福分配一个引用计数-1,这是常量字符串的保留价值,我们可以通过指定在运行时建立了一个字符串的例子更准确。 ,即:“StringOfChar(”A“,5)”而不是“,”ABC“,但可能分散插图本澄清是在这里为那些非常精明的读者,不能让这一关,只是单纯的有。指出这一点了:))
WideString的
WideString的是最新的Delphi字符串家庭除了。它的成立是为了解决通过在许多平台上的Unicode增加,专门处理Unicode字符。视窗2000,视窗XP,Java和新的。NET架构都被写入从地上爬起来使用Unicode,而事实上新版本的Windows API转换内部与他们的工作之前,ANSI风格的字符串为Unicode。因此,我们可以合理地预期,在未来,Unicode字符和WideStrings将取代所有其他字符和字符串类型。
语义,WideString的行为完全一样的AnsiString,它是自动管理,null结尾的引用计数。唯一的区别是,每一个WideString的特点是WideChar,一个新的字符类型,保存Unicode字符。也可用于工作的OLE类型WideString的。Unicode是一个原因,为什么你不应该假设sizeof(char)的的= 1在您的代码。总是使用sizeof()的操作,做大小算术时的时候,在未来,你编译你的代码与较新版本的Delphi SIZEOF(字符)= 2,那么你的代码可以做出相应的调整。
赋值语义
这些字符串类型的不同,在一个更重要的方面:他们有不同的赋值语义。了解这些差异是至关重要的,以有效地使用它们。赋值语义处理看似简单的分配结构的基础行为。这是很少给出一个主题,由开发人员认为,这种缺乏了解,经常会导致低效和错误代码。让我们开始与ShortString短。如前所述,ShortStrings是静态分配的,并具有相同的整数,布尔值和记录的赋值语义。有了ShortStrings,总是分配一个字符串变量的全部内容复制到另一个:
VAR S,T:ShortString短; ... S:= '富' T:= / /整个内容复制 [1]:= “B” / / T还包含'富'
对于一个PChar,这是一个(通常是动态分配的)数组中字符的指针,其他指针赋值语义是相同的。赋值只复制指针的内容,而不是字符数组。因此,直接转让后,两个PChars指向相同的阵列:
VAR S,T:PChar类型; ... S:= '富' T:= / /复制指针 [0]:=“B”; / /都包含'嘘'
大部分时间,其实我们想要的字符串的内容,而不是我们上面的代码得到的结果。这是STR *函数。为PChar类型的内容复制中,StrCopy功能提供:
VAR S,T:ARRAY [0 .. 50]的字符; ... StrCopy(S,'富'); StrCopy(T,S); / /内容复制 [0]:= “B” ; / / T仍包含'富'
超过ShortString的PChar类型的优势之一是实例字符串严格读,然后指针复制可以用来提高性能,因为只有四个字节需要不断为每个字符串复制。缺点是程序员的任务是记住字符串只读和精简其代码。
一样的AnsiString和WideString的长串,另一方面,有非常不同的语义。因为PChar类型一样,他们是隐含的指针,他们都非常迅速复制:
VAR S,T:AnsiString的; ... S:= '富' T:= / /复制的唯一指针,引用计数递增
但评论指出,除了从复制缓冲区的地址,AnsiString的也有它的引用计数递增。此计数保持多少只读“客户”一个AnsiString的轨道。然后,当一个客户端需要修改的缓冲区,缓冲区的副本被悄悄地,原来的缓冲区的引用计数递减,变化终于申请到新的缓冲区:
VAR S,T:AnsiString的; ... S:= '富' T:= / /唯一的指针复制引用计数递增 [1]:=“B”; / /缓冲区的refcount> 1:抄造,原字符串的引用计数递减
下面的数字显示一个长字符串的引用计数的作品(简体)的观点。
S:='富' 。新的字符串缓冲区分配, 引用计数设置为1。 长度设置为3。 变量s指向的缓冲区来解决。 |
|
T:=; 变量s T(指针副本)复制到 缓冲区。引用计数递增。 |
|
小号[1]:='乙'; ?是参考计数> 1 。是,新的字符串缓冲区分配 新缓冲区的引用计数设置为1。 旧缓冲区参考计数递减。 指出新的缓冲区变牛逼。 编辑新的缓冲区执行。 |
这就是由“副本上写的语义的引用计数”的意思。由此看来,长字符串ShortString的(方便,无需手动分配,直观的复制),PChar类型(速度非常快的复制,效率高,容量大的存储)获得的优势。