当前位置:文档之家› pascal语言中字符串操作

pascal语言中字符串操作

字符串类型在Borland公司的Turbo Pascal和16位Delphi中,传统的字符串类型是一个字符序列,序列的头部是一个长度字节,指示当前字符串的长度。

由于只用一个字节来表示字符串的长度,所以字符串不能超过255个字符。

这一长度限制为字符串操作带来不便,因为每个字符串必须定长(确省最大值为255),当然你也可以声明更短的字符串以节约存储空间。

字符串类型与数组类型相似。

实际上一个字符串差不多就是一个字符类型的数组,因为用[]符号,你就能访问字符串中的字符,这一事实充分说明了上述观点。

为克服传统Pascal 字符串的局限性,32位Delphi增加了对长字符串的支持。

这样共有三种字符串类型:∙ShortString 短字符串类型也就是前面所述的传统 Pascal 字符串类型。

这类字符串最多只能有255个字符,与16位Delphi中的字符串相同。

短字符串中的每个字符都属于ANSIChar 类型(标准字符类型)。

∙ANSIString长字符串类型就是新增的可变长字符串类型。

这类字符串的内存动态分配,引用计数,并使用了更新前拷贝(copy-on-write)技术。

这类字符串长度没有限制(可以存储多达20亿个字符!),其字符类型也是ANSIChar 类型。

∙WideString 长字符串类型与ANSIString 类型相似,只是它基于WideChar 字符类型,WideChar 字符为双字节Unicode 字符。

使用长字符串如果只简单地用String定义字符串,那么该字符串可能是短字符串也可能是ANSI长字符串,这取决于$H 编译指令的值,$H+(确省)代表长字符串(ANSIString 类型)。

长字符串是Delphi 库中控件使用的字符串。

Delphi 长字符串基于引用计数机制,通过引用计数追踪内存中引用同一字符串的字符串变量,当字符串不再使用时,也就是说引用计数为零时,释放内存。

如果你要增加字符串的长度,而该字符串邻近又没有空闲的内存,即在同一存储单元字符串已没有扩展的余地,这时字符串必须被完整地拷贝到另一个存储单元。

当这种情况发生时,Delphi运行时间支持程序会以完全透明的方式为字符串重新分配内存。

为了有效地分配所需的存储空间,你可以用SetLength 过程设定字符串的最大长度值:SetLength (String1, 200);SetLength 过程只是完成一个内存请求,并没有实际分配内存。

它只是把将来所需的内存预留出来,实际上并没有使用这段内存。

这一技术源于Windows 操作系统,现被Delphi用来动态分配内存。

例如,当你请求一个很大的数组时,系统会将数组内存预留出来,但并没有把内存分配给数组。

一般不需要设置字符串的长度,不过当需要把长字符串作为参数传递给API 函数时(经过类型转换后),你必须用SetLength 为该字符串预留内存空间,这一点我会在后面进行说明。

看一看内存中的字符串为了帮你更好地理解字符串的内存管理细节,我写了一个简例StrRef 。

在程序中我声明了两个全程字符串:Str1 和 Str2,当按下第一个按钮时,程序把一个字符串常量赋给第一个变量,然后把第一个变量赋给第二个:Str1 := 'Hello';Str2 := Str1;除了字符串操作外,程序还用下面的StringStatus 函数在一个列表框中显示字符串的内部状态:function StringStatus (const Str: string): string;beginResult := 'Address: ' + IntToStr (Integer (Str)) +', Length: ' + IntToStr (Length (Str)) +', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) +', Value: ' + Str;end;在StringStatus 函数中,用常量参数传递字符串至关重要。

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

由于本例不希望字符串被修改,因此选用常量参数。

为获取字符串内存地址(有利于识别串的实际内容也有助于观察两个不同的串变量是否引用了同一内存区),我通过类型映射把字符串类型强行转换为整型。

字符串实际上是引用,也就是指针:字符串变量保存的是字符串的实际内存地址。

为了提取引用计数信息,我利用了一个鲜为人知的事实:即字符串长度和引用计数信息实际上保存在字符串中, 位于实际内容和字符串变量所指的内存位置之前,其负偏移量对字符串长度来说是-4(用Length 函数很容易得到这个值),对引用记数来说是-8。

不过必须记住,以上关于偏移量的内部信息在未来的Delphi版本中可能会变,没有写入正式Delphi文档的特性很难保证将来不变。

通过运行这个例子,你会看到两个串内容相同、内存位置相同、引用记数为2,如图7.1中列表框上部所示。

现在,如果你改变其中一个字符串的值,那么更新后字符串的内存地址将会改变。

这是copy-on-write技术的结果。

图 7.1: 例StrRef显示两个串的内部状态,包括当前引用计数第二个按钮(Change)的OnClick 事件代码如下,结果如图7.1列表框第二部分所示:procedure TFormStrRef.BtnChangeClick(Sender: TObject);beginStr1 [2] := 'a';ListBox1.Items.Add ('Str1 [2] := ''a''');ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1));ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2));end;注意,BtnChangeClick 只能在执行完BtnAssignClick 后才能执行。

为此,程序启动后第二个按钮不能用(按钮的Enabled 属性设成False);第一个方法结束后激活第二个按钮。

你可以自由地扩展这个例子,用StringStatus 函数探究其它情况下长字符串的特性。

Delphi 字符串与 Windows PChar字符串长字符串为零终止串,这意味着长字符串完全与Windows使用的C语言零终止串兼容,这给长字符串使用带来了便利。

一个零终止串是一个字符序列,该序列以一个零字节(或null)结尾。

零终止串在Delphi中可用下标从零开始的字符数组表示,C语言就是用这种数组类型定义字符串,因此零终止字符数组在Windows API 函数(基于C语言)中很常见。

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

下例把一个窗体的标题拷贝给PChar 字符串(用API 函数GetWindowText),然后再把它拷贝给按钮的Caption 属性,代码如下:procedure TForm1.Button1Click (Sender: TObject);varS1: String;beginSetLength (S1, 100);GetWindowText (Handle, PChar (S1), Length (S1));Button1.Caption := S1;end;你可以在例LongStr 中找到这段代码。

注意:代码中用SetLength函数为字符串分配内存,假如内存分配失败,那么程序就会崩溃;如果你直接用PChar 类型传递值(而不是象以以上代码那样接受一个值),那么代码会很简单,因为不需要定义临时字符串,也不需要初始化串。

下面代码把一个Label(标签)控件的Caption 属性作为参数传递给了API函数,只需要简单地把属性值映射为PChar 类型:SetWindowText (Handle, PChar (Label1.Caption));当需要把WideString 映射为Windows兼容类型时,你必须用PWideChar 代替PChar进行转换,WideString常用于OLE和 COM 程序。

刚才展现了长字符串的优点,现在谈谈它的弊端。

当你把长字符串转换为PChar 类型时可能会引发一些问题,问题根本在于:转换以后字符串及其内容将由你来负责,Delphi 不再管了。

现在把上面Button1Click代码稍作修改:procedure TForm1.Button2Click(Sender: TObject);varS1: String;beginSetLength (S1, 100);GetWindowText (Handle, PChar (S1), Length (S1));S1 := S1 + ' is the title'; // this won't workButton1.Caption := S1;end;程序编译通过,但执行结果会令你惊讶,因为按钮的标题并没变,所加的常量字符串没有添加到按钮标题中。

问题原因是Windows写字符串时(在GetWindowText API调用中),Windows 没有正确设置Pascal 长字符串的长度。

Delphi 仍可以输出该字符串,并能通过零终止符判断字符串何时结束,但是如果你在零终止符后添加更多的字符,那么这些字符将被忽略。

怎么解决这个问题呢?解决方法是告诉系统把GetWindowText API函数返回的字符串再转换成Pascal字符串。

然而,如果你用以下代码:S1 := String (S1);Delphi 系统将不予理睬,因为把一种类型转换为它自己的类型是无用的操作。

为获得正确的Pascal 长字符串,需要你把字符串重新映射为一个PChar 字符串,然后让Delphi 再把它转回到字符串:S1 := String (PChar (S1));实际上,你可以跳过字符串转换(S1 := PChar (S1));,因为在Delphi中Pchar转换到string是自动执行的,最终代码如下:procedure TForm1.Button3Click(Sender: TObject);varS1: String;beginSetLength (S1, 100);GetWindowText (Handle, PChar (S1), Length (S1));S1 := String (PChar (S1));S1 := S1 + ' is the title';Button3.Caption := S1;end;另一个办法是用PChar 字符串的长度重新设定Delphi 字符串长度,可以这样写:SetLength (S1, StrLen (PChar (S1)));在例LongStr中你可以看到三种方法的结果,分别由三个按钮执行。

相关主题