Delphi 字符串类型浅析

参考资料

Delphi 5开发人员指南》2Object Pascal语言

PASCAL精要》“第7 字符串操作”

DelphiString类型和Char类型的比较

Object PascalString类型的内幕探讨”

 

 

基本知识

字符串类型

AnsiString这是Pascal缺省的字符串类型,它由AnsiChar字符组成,其长度没有限制,同时与null结束的字符串相兼容。

ShortString保留该类型是为了向后兼容Delphi1.0,它的长度限制在255个字符内。

WideString功能上类似于AnsiString,但它是由WideChar字符(UniCode字符集)组成的。引入这种类型,主要是为了支持OLE编程。

PChar指向null结束的Char字符串的指针,类似于Cchar*lpstr类型。

PAnsiChar指向null结束的AnsiChar字符串的指针。

PWideChar指向null结束的WideChar字符串的指针。

 

字符类型

AnsiChar,以 8 位表示 (共有 256 个不同的符号)

WideChar,以 16 位表示 (共有 64,000 个不同的符号)

 

String类型

注意:以下是指Delphi2007以前的编译器(包括Delphi2007)。

1、缺省情况下,如果用如下的代码来定义字符串,编译器认为是AnsiString字符串:

var

S:string;//编译器认为S的类型是AnsiString

2、编译开关$H

$H编译开关”的值用来决定当变量声明为string时,它是被当作AnsiString类型还是被当作ShortString类型。当“$H”值为负时,string变量是ShortString类型;当“$H”值为正时(缺省情况)string变量是AnsiString类型。下面的代码演示了这种情况:

var

{$H-}

S1:string;//S1ShortString类型

{$H+}

S2:string;//S2AnsiString类型

使用$H规则的一个例外是,如果在定义时特地指定了长度(最大在255个字符内),那么总是ShortString

var

S:string[63];//63个字符的ShortString字符串

 

Char类型

1Delphi2007以前的编译器(包括Delphi2007)缺省情况下认为CharAnsiChar类型,PCharPAnsiChar类型。

2Delphi2009的编译器缺省情况下认为CharWideChar类型,PCharPWideChar类型。

请注意,没有任何方法可变更这个新的编译器预设设定。对于字符串类型而言,会以固定的硬式编码方式将 Char 类型对应到特定的数据类型。

 

AnsiString类型

1AnsiString是生存期自管理类型。

2AnsiString字符串总是以null字符结束的,这使得AnsiString字符串能与Win32API中的字符串兼容。

3AnsiString类型是一个指向在堆栈中的字符串结构的指针。

可以使Sizeof去读取AnsiString类型的大小,不论字符串的实际长度是多少,Sizeof(AString)永远是4

4AnsiString字符串在内存中分配的情况

注意:

Delphi2.0以后版本中,不能再通过字符串的第0个元素来设置或得到字符串的长度,只能通过Length()函数来得到字符串的长度,通过SetLength()过程来设置字符串的长度。

Borland并不保证String的内存结构在以后的Delphi版本中会保持不变。

 

1)、Delphi5内存结构如下:

2)、Delphi7内存结构如下:

AnsiString指向一块内存起始偏移8字节处,前面8字节依次为引用计数和长度计数两个整数。

3)、Delphi2009内存结构如下:

-12

-10

-8

-4

 

最后一位

字码页

字符大小

引用计数

字串长度

字串内容

0

除了字串长度及引用计数外,新的AnsiString格式包括字符大小及字码页。字符大小可用来区分 AnsiString UnicodeString,而字码页特别适用于 AnsiString 类型 (可用于 Delphi 2009)UnicodeString 类型的字码页则固定为 1200,字码包括UTF-8GBK等,例如“$03A8就是936,MSDN  936 - gb2312

5AnsiString字符串的引用计数机制。

1)、AnsiString字符串基于引用计数机制,通过引用计数追踪内存中引用同一字符串的字符串变量。

2)、编译器生成的代码会在操作一个AnsiString字符串前把引用计数+1,操作结束后引用计数-1, 如果引用计数=0时就自动释放整块内存。

3)、AnsiString字符串的复制

在拷贝字符串的时候,Object Pascal并不是把字符串简单的复制一份,而是采取了引用计数的方法,将两个字符串指向同一个内存空间,同时引用计数加1。当字符串变量被清除的时候,引用计数减1通过AnsiString字符串的引用计数机制,几个字符串都能指向相同的物理地址。因此,复制字符串仅仅是复制了指针而不是复制实际的字符串。

4)、AnsiString字符串作为参数

用拷贝方式(值参)传递AnsiString字符串会引起副作用,因为函数执行过程中会产生一个对字符串的额外引用;与此相反,通过引用(var)或常量(const)参数传递不会产生这种情况。

6AnsiString字符串的赋值。

1)、将string赋值给另一个string,只是一个简单的指针赋值,不产生copy动作,只是增加string的引用计数;

2)、将一个PChar变量类型赋值给一个string变量类型会产生真正的Copy动作,即将PChar所指向的字符串整个copy到为string分配的内存中;

3)、将string赋值给一个PChar变量类型,只是简单地将string的指针值赋值给PChar变量类型,而string的引用计数并不因此操作而发生变化。因为这种情况PChar会对string产生依赖,当string的引用计数为零自动释放内存空间后,PChar很可能指向一个无效的内存地址,在你的程序你必须小心对付这种情况。

3)、更新前拷贝机制copy-on-write

在给字符串赋值的时候,Object Pascal首先会检查字符串的引用计数是否为1。如果是,按照一般的方法直接赋值即可;否则,就说明有两个以上字符串指向同一个地址,这种情况就复杂多了。Object Pascal使用的是“更新前拷贝机制copy-on-write)”,为当前字符串另外开辟一个缓冲区,将新内容拷入;同时,原来的字符串引用计数要减1

注意,一个字符串要等到修改结束,才释放一个引用并分配一个物理字符串。

var

    S1,S2:string;

begin

    //S1赋值,S1的引用计数为1

    S1:='Andnowforsomething...';

    S2:=S1;//现在S2S1指向同一个字符串,S1的引用计数为2

    //S2现在改变了,所以它被复制到自己的物理空间,并且S1的引用计数减1

    S2:=S2+'completelydifferent1';

end;

7AnsiString字符串的长度。

1)、AnsiString字符串的最大长度可以到2GB

2)、AnsiString字符串的初始长度

第一次声明AnsiString时,它是没有长度的,因此在字符串中就没有为字符分配空间。为了对字符串分配空间,用一行字母或另一个字符串对它进行赋值,或者用SetLength()过程。

3)、AnsiString字符串的内存分配

如果你要增加字符串的长度,而该字符串邻近又没有空闲的内存,即在同一存储单元字符串已没有扩展的余地,这时字符串必须被完整地拷贝到另一个存储单元。当这种情况发生时,Delphi运行时间支持程序会以完全透明的方式为字符串重新分配内存。

4)、SetLength方法

为了有效地分配所需的存储空间,你可以用SetLength 过程设定字符串的最大长度值“SetLength (String1, 200);”。SetLength 过程只是完成一个内存请求,并没有实际分配内存。它只是把将来所需的内存预留出来,实际上并没有使用这段内存。

注意:代码中用SetLength函数为字符串分配内存,假如内存分配失败,那么程序就会崩溃

8AnsiString字符串的内容访问。

能像数组一样对字符串进行索引,但注意索引不要超出字符串的长度。

var

    S:string;

begin

    SetLength(S,1);

    S[1]:='a';//现在S有足够空间来容纳字符

end;

9AnsiString字符串的类型转换。

1)、将一个字符串转换为PChar类型时要小心,因为字符串在超出其作用范围时有自动回收的功能,因此当进行P:=PChar(Str)的赋值时,P的作用域(生存期)应当大于Str的作用域。

2)、字符串与PChar之间的转换。可以把AnsiString强制类型转换为PChar。如果要把PChar的内容复制到AnsiString,直接用赋值语句:StringVar:=PCharVar;

3)、使用PChar转换AnsiString时,会丢失AnsiString的动态增长和引用计数的功能,所以一定要小心,另外要注意PChar长度的计算和字符串长度一定要同步,否则会出问题。

 

ShortString类型

1ShortString在内存中分配的情况

 

在内存中,字符串就像是一个字符数组,在字符串的第0个元素中存放了字符串的长度,紧跟在后的字符就是字符串本身。

2ShortString字符串的最大长度

1)、ShortString缺省的最大长度为256个字节,这表示在ShortString中不能有大于255个字符(1个长度字节+255个字符=256)。相对于AnsiString来说,用ShortString是相当随意的,因为编译器会根据需要为它分配空间。

2)、能用short类型限定符和一个长度限制来为ShortString分配小于256个字节的空间。

var

S:string[45];//45个字符的ShortString字符串

3)、不要存放比分配给字符串的空间长度更长的字符。

如果声明了一个变量是string[8],并试图对这个变量赋值为‘a_pretty_darn_long_string’,这个字符串将被截取为仅有8个字符,就要丢失数据。

5)、ShortString字符串的内容访问。

可以用数组的下标来访问ShortString中的字符。

注意:

当用数组的下标来访问ShortString中的一个特定字符时,如果下标的索引值大于ShortString的实际长度,则会得到假的结果或造成内存混乱。

可以在Project Options 对话框中选中Range Checking复选框,这样编译器会自动加上特殊的逻辑在运行时捕捉此类错误。提示,虽然在程序中包括范围检查能发现字符串错误,但范围检查多少都影响应用程序的性能。通常使用的方法是在开发程序或调试程序的阶段用范围检查,而在确信程序稳定时,去掉范围检查。

3ShortString跟以null结尾的字符串不兼容,因此使用ShortString调用Win32函数时,必须进行类型转换。

 

WideString类型

1WideString类型是生存期自管理类型。

2WideStringAnsiString的不同主要在三个方面:

WideStringWideChar字符组成,而不是由AnsiChar字符组成的,它们跟Unicode字符串兼容。

WideStringSysAllocStrLen()API函数进行分配,它们跟OLEBSTR字符串相兼容。

WideString没有引用计数,所以将一个WideString字符串赋值给另一个WideString字符串时,就需要从内存中的一个位置复制到另一个位置。这使得WideString在速度和内存的利用上不如AnsiString有效。

3WideString在内存中分配的情况

WideString同样是用4个字节来存储字符串长度,但是它每个元素的大小是2个字节

4WideString字符串的类型转换

编译器自动在AnsiString类型和WideString类型的变量间进行转换。

5WideString字符串的内容访问。

能用数组的下标来访问WideString中的字符。

 

PAnsiChar类型

1PAnsiChar类型

PAnsiChar被定义成一个指向以null()结束的字符串的指针, 它的大小只有32定义时由Delphi自动填0因为PAnsiChar是指针,所以它能指向任何地方(也就是说它不一定非要指向字符串不可)

2PAnsiChar字符串

PAnsiChar型字符串由#0表示字符串结尾。Delphi所提供的相关PChar字符串的操作都是判断#0来决定字符串的结尾的。

3PAnsiChar字符串的内存

要将PChar作为字符串使用的话必须自己分配内存用完必须自己释放

4PAnsiChar字符串的长度

PChar字符串的理论最大长度是4GB

5PAnsiChar字符串在内存中的分布情况。

6PAnsiChar类型转换

1)、在大多数情况下,AnsiString类型能被用成PAnsiChar

2)、把一个String赋值给PAnsiChar只是将String中保存具体字符串的内存的地址给PAnsiChar变量

3)、可以把AnsiChar数组第一个元素的地址给PAnsiChar

 

char数组

1char数组也是指向字符串的指针

2char数组与pchar的区别

1)、char数组(均指非动态数组)一旦定义好,它的长度就固定了

2)、char数组的地址是常量,不能另赋其它值

3char数组使用

char数组使用的比较少了,因为多数可以用char数组的地方,现在比较流行的作法是定义一个ansistring, 再用setlength来设定它的长度。

 

null结束的字符串

1一个零终止串是一个字符序列,该序列以一个零字节(null)结尾。零终止串在Delphi中可用下标从零开始的字符数组表示,C语言就是用这种数组类型定义字符串,因此零终止字符数组在WindowsAPI函数(基于C语言)中很常见。由于Pascal长字符串与C语言的零终止字符串完全兼容,因此当需要把字符串传递给WindowsAPI函数时,你可以直接把长字符串映射为PChar类型。

2Delphi有三种不同的以null结束的字符串类型:PCharPAnsiCharPWideChar。它们都是由Delphi的三种不同字符组成的。这三种类型在总体上跟PChar是一致的。

警告,Win32API函数需要以null结尾的字符串,不要把ShortString字符串传递给API函数,因为编译器将报错,长字符串可以传递给Win32API函数。

 

string PChar Char数组比较

1占用内存大小

Char数组<PChar(指分配过字符串的)string(除了具体字符串外还包含字符串长度);如果空字符串那么PCharStringarray [0..n] of Char

2、处理效率

从速度来说毫无疑问string最慢, 例如:作为参数传递(var调用时)给过程时string将整个字串的副本传递过去, PChar将指针本身的副本传递过去(32), Char数组和PChar一样, 传递的是第一个元素的地址副本。

不过就灵活性来说string最高, 而且Delphi支持的函数最多. 另外可以将String作为Buffer使用(因为它当中可以包含字符0)

如果涉及到Windows API或混合编程等,接口部分一般使用pchar

 

附件资料

示例:string类型

说明:

可以使用编译开关$H来将string类型定义为ShortString,当$H编译开关的值为负时,string变量是ShortString类型;当$H编译开关的值为正时(缺省情况),字符串变量是AnsiString类型。

使用$H规则的一个例外是,如果在定义时特地指定了长度(最大在255个字符内),那么总是ShortString

代码:

procedure TForm1.Button1Click(Sender: TObject);

var

{$H-}

    S1: string; //S1ShortString类型

{$H+}

    S2: string; //S2AnsiString类型

begin

    showmessage(inttostr(Sizeof(S1))); //256字节

    showmessage(inttostr(Sizeof(S2))); //4字节

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

    S: string[63]; //63个字符的ShortString字符串

begin

    showmessage(inttostr(Sizeof(S)));

end;

 

示例:ShortString的长度

说明:

能用short类型限定符和一个长度限制来为ShortString分配小于256个字节的空间。不要存放比分配给字符串的空间长度更长的字符,如果声明了一个变量是string[8],并试图对这个变量赋值为‘a_pretty_darn_long_string’,这个字符串将被截取为仅有8个字符,就要丢失数据。

代码:

procedure TForm1.Button1Click(Sender: TObject);

var

    S: string[4]; //4个字符的ShortString字符串

begin

S := '123456';

    showmessage(inttostr(Ord(S[0]))); {长度4}

    showmessage(inttostr(length(S))); {长度4}

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

    S: string[4];

begin

    S := '12';

    showmessage(inttostr(length(S))); {长度2}

    //---

    S := '12345678';

    showmessage(inttostr(length(S))); {长度4}

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

    S: string[45];

begin

    S := '123456';

    showmessage(inttostr(length(S))); {长度6}

    //---

    S := '12345678';

    showmessage(inttostr(length(S)));  {长度8}

end;

 

示例:ShortString越界访问

说明:

当用数组的下标来访问ShortString中的一个特定字符时,如果下标的索引值大于声明时ShortString的长度,则会得到假的结果或造成内存混乱。

代码:

procedure TForm1.Button1Click(Sender: TObject);

var

    s:ShortString;

begin

    s:= '123';

    showmessage(s[4]);

end;

 

示例:WideString类型转换

说明:

编译器自动在AnsiString类型和WideString类型的变量间进行转换。

代码:

procedure TForm1.Button1Click(Sender: TObject);

var

    W: wideString;

    S: string;

begin

    W := 'Margaritaville';

    S := W; //wideString转换成AnsiString

    S := 'ComeMonday';

    W := S; //AnsiString转换成WideString

end;

 

示例:Windows API 函数调用

说明:由于stringC语言的零终止字符串完全兼容,因此当需要把字符串传递给Windows API 函数时,你可以直接把长字符串映射为PChar 类型。

代码:

procedure TForm1.Button1Click(Sender: TObject);

var

    S1: string;

begin

    SetLength(S1,100);

    GetWindowText(Handle,PChar(S1),Length(S1));

    Button1.Caption := S1;

end;

 

示例:WinAPI函数调用

说明:

使用PChar转换String的同时也就丢失了String的动态增长和引用计数的功能,所以一定要小心,另外要注意PChar长度的计算和字符串长度一定要同步,否则会出问题。

代码:

程序编译通过,但执行结果会令你惊讶,因为按钮的标题并没变,所加的常量字符串没有添加到按钮标题中。问题原因是SetLength将字符串长度设成了256,而PChar计算的长度只到第一个$0为止,此时Delphi 可以正常输出字符串,并能通过零终止符判断字符串何时结束,但是如果你在零终止符后添加更多的字符,那么这些字符将被忽略。解决的办法是重新设置字符串的长度。

procedure TForm1.Button1Click(Sender: TObject);

var

    S1: string;

begin

    SetLength(S1,100);

GetWindowText(Handle,PChar(S1),Length(S1));

    S1 := S1 + ' is the title'; // this won't work

    Button1.Caption := S1;

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

    S1: string;

begin

    SetLength(S1,100);

    GetWindowText(Handle,PChar(S1),Length(S1));

S1 := PChar(S1);

    //SetLength (S1, StrLen (PChar (S1)));

    S1 := S1 + ' is the title';

    Button1.Caption := S1;

end;

 

示例:shortstringpchar的类型转换

说明:

使用指向字符串的指针,如果不是以0结尾,运行时就会出现错误。为了避免这种错误,需要在字符串结尾人工加入0 char(0),或用strpcopy函数在字符串结尾自动加0

代码:

procedure TForm1.Button1Click(Sender: TObject);

var

    s: shortstring;

    p: pchar;

begin

    s := 'new';  {s[0]=3 s[1]='n' s[2]='e' s[3]='w'}

    p := @s[1]; {不是以0结尾,莫用pchar型指针}

    Button1.caption := strpas(p); {运行时出现错误}

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

    s: shortstring;

    p: pchar;

begin

    s := 'new' + char(0); {0结尾,可用pchar型指针} {s[0]=4 s[1]='n' s[2]='e' s[3]='w' s[4]=0;}

    p := @s[1];

    Button1.caption := strpas(p);

end;

 

示例:delphi5字符串内存结构

代码:

procedure TForm1.Button1Click(Sender: TObject);

type

    PStrRec = ^StrRec;

    StrRec = packed record

        allocSiz: Longint;

        refCnt: Longint;

        length: Longint;

    end;

var

    str: string;

    P: PStrRec;

begin

    str := '123';

    P := Pointer(Integer(str) - sizeof(StrRec));

    showmessage (Format('分配大小:%d  引用计数:%d  字串长度:%d', [P.allocSiz,P.refCnt,P.length]));

end;

 

示例:delphi7字符串内存结构

代码:

procedure TForm1.Button1Click(Sender: TObject);

type

    PStrRec = ^StrRec;

    StrRec = packed record

        refCnt: Longint;

        length: Longint;

    end;

var

    str: string;

    P: PStrRec;

begin

    str:='123';

    P := Pointer(Integer(str) - sizeof(StrRec));

    showmessage (Format('引用计数:%d  字串长度:%d', [P.refCnt,P.length]));

end;

 

示例:delphi2009字符串内存结构

代码:

procedure TForm1.Button3Click(Sender: TObject);

type

    PStrRec = ^StrRec;

    StrRec = packed record

        codePage: Word;

        elemSize: Word;

        refCnt: Longint;

        length: Longint;

    end;

var

    str: string;

    P: PStrRec;

begin

    str := '123';

    P := Pointer(Integer(str) - sizeof(StrRec));

    showmessage(Format('字码页:%d 字符大小:%d 引用计数:%d  字串长度:%d', [P.codePage,P.elemSize,P.refCnt,P.length]));

end;

 

示例:widestring的长度

代码:

procedure TForm1.Button1Click(Sender: TObject);

    //---

    function _WStrLen1(const S: WideString): Integer;

    begin

        if Pointer(S) = nil then

            Result := 0

        else

            Result := PInteger(Integer(S) - 4)^ div sizeof(WideChar);

    end;

var

    str: widestring;

begin

    str := '测试123';

    showmessage (Format('字串长度:%d', [_WStrLen1(str)]));

end;

 

 

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值