Delphi编程基础 之 字符串篇
分类: Delphi2009-09-19 08:11 364人阅读 评论(0) 收藏 举报
本文整理了 Delphi字符串格式 ShortString 、AnsiString、WideString。以及PChar、Char数组的使用方法及需要注意的地方,很多例子来自网络。
本文所有例子基于Delphi7,使用例子如有不对之处,欢迎指正。
参考文献:delphi7 开发指南;
(一) ShortString
ShortString 的存储结构如下:
+---------------------+
| 1Byte | 字符串内容 |
+---------------------+
0 1
其第一个字节为字符串的长度,所以ShortString 的长度不能超过255,其存储区为静态分配,大小在编译时确定,这是继承于BP for Dos 的类型。保留该类型是为了向后兼容Delphi 1.0,和为了与Pascal 传统string 相兼容而保留的。但是这个字串数组的最后
是没有一个#0 字符作为结尾的,因此ShortString 和C 语言的字符串是不兼容的。
(二)AnsiString
Delphi2.0 以后引入了AnsiString,,它是一个容易使用而且没有2 5 5 个字符限制的字符串类型。
而且它并不需要以NULL 结尾,因为有长度信息,同样因为这个字符串内可以包含任意的NULL 而不会被认为是字符串结尾。并且AnsiString 与n u l l 结束的字符串相兼容。其存储结构如下:
+-----------------------------------+
| 4B | 4B | 4B | 字符串内容 |
+----------------------------------+
-12 -8 -4 0 ... ...
内存偏移-12 处为该字符串分配的内存大小、内存偏移-8 处为该字符串的引用计数,内
存偏移-4 处为该字符串长度。AnsiString 字符串最大长度2G
AnsiString 是Delphi 独有的,其存储区在运行时动态分配的并有自动回收功能,因为这个功能A ns i S t r i n g 有时被称为生存期自管理类型。
当两个或更多的A n s i S t r i n g 类型共享一个指向相同物理地址的引用时,Delphi 使用
了Copy – On - Wr i t e 的技术,即一个字符串要等到修改结束,才释放一个引用并分配一个物理字符串。下面的例子有助于理解这些概念:
[c-sharp] view plaincopy
1. v a r
2. S 1 , S 2 : s t r i n g ;
3. b e g i n
4. S1:='Example for Copy-On-Write...'; / /给S 1 赋值,S 1 的引用计数为1
5. S2:=S1; //现在S 2 与S 1 指向同一个字符串, S 1 的引用计数为2
6. S2:=S2+'Changed ' ; / / S 2 现在改变了,所以它被复制到自己的物理空间,并且S 1 的引用计数减1。
7. e n d ;
我们平常使用的String 类型,在默认的情况下就是AnsiString;我们可以通过Huge Strings 的编译选项来将String 类型定义为ShortString,即将编译选项$H 设为负值。在缺省情况下编译选项$H 为正值,即String 类型为AnsiString。
在使用AnsiString 是要注意以下几点:
一、$H 有一个例外。
使用$ H 规则的一个例外是,如果在定义时特地指定了长度(最大在2 5 5 个字符内),那么总是S ho r t S t r i n g。
[delphi] view plaincopy
1. v a r
2. S: string[63]; //63 个字符的S h o r t S t r i n g 字符串
二、免重复初始化。
由于AnsiString 会自动初始化为空。如下代码中的初始化就纯属多余了。
[delphi] view plaincopy
1. var
2. s:string;
3. begin
4. s:='';
5. ...
6. end;
s:='';就完全没有必要。但是值得注意的是这对函数返回值Result 无效。而一般说来,用var 实参传递比返回字符串值要更快一些。
三、用SetLength 预分配长字符串(AnsiString)。
虽然动态分配内存是AnsiString 的一大长项,但有时候反而会降低效率,还是要用到SetLength 来为AnsiString 类型的字符串来分配内存。请看如下例子。
[delphi] view plaincopy
1. Var
2. s1,s2 : string;
3. begin
4. s2:=' ';
5. for i:=2 to length(s1) do
6. s2:=s2+s1[i];
7. end;
当然此例可用Delete 取代之,这里主要说明在上例的循环中s2 的内存区域被不停地重复分配,降低效率。一个简单有效的办法如下:
[delphi] view plaincopy
1. SetLength(s2,length(s1)-1);
2. for i:=2 to length(s1) do s2[i-1]:=s1[i];
这样s2 内存只会重新分配一次。
四、量避免使用ShortString
因为再Delphi 中很多字符串操作会先把ShortString 转换为AnsiString,从而减慢了执行速度,因此还是少使用短字符串为妙。
五、避免使用Copy 函数
这也和滥用内存管理有关。一个典型的情形如下:
if Copy(s1,23,64)=Copy(s2,15,64) then ……
这样导致分配了两块临时内存,因而降低了效率。应当替换为如下代码:
[delphi] view plaincopy
1. i:=0;
2. f:=false;
3. repeat
4. f:=s1[i+23]<>s2[i+15];
5. inc(i);
6. until f or (I>63);
7. if not f then ……
同样的,如下语句就显得相当低效:
s:=Copy(s,1,length(s)-10);
应改为
Delete(s,length(s)-10,10);
六、尽量使用AnsiString,必要时转换为Pchar
先看看AnsiString 的定义:
[delphi] view plaincopy
1. type
2. Astring = packed record
3. allocSiz: Longint; //动态分配大小
4. refCnt: Longint; //引用计数
5. length: Longint; //实际长度
6. ChrArr:array[1..allocsiz-6]of char; //字节序列
7. end;
其中Astring[1]将返回Astring.ChrArr[1]的内容。
很多人认为AnsiString 是天生低效的。其实这在很大程度上是由代码编写不良、内存管理乱用和缺乏支持的函数所致。如上所述,一旦被动态分配了一块内存,长字符串就成了一个线性的字节序列,并无所谓的效率问题。当然,若有更多有效的函数支持那就更好了。
说到AnsiString 到PChar 的转换,本质上有三个办法:
(1) P:=@s[1];这会引发UniqueString 调用,UniqueString 使字符串拥有唯一的存储区域。
(2) P:=PChar (s);这会先检查s 是否为空,若是,则返回nil,否则即返回s[1]的地址。
(3) P:=Pointer(s);这不会引发任何隐含调用,因而是在确定s 非空情况下的最佳选择。
(三)Pchar
Pchar 就是纯指向字符串(#0 字符结尾)的指针,与C 语言中的char *是一样的。定义时由Delphi
自动填0。PChar 主要是为了兼容各类API,在Delphi 中更加广泛应用,其存储区可以用字符数组静态分配,也可用GetMem 手动分配。
(四)Char 数组
Char 数组也是指向字符串的指针,它与Pchar 的区别在于:
1. Char 数组(均指非动态数组)一旦定义好,它的长度就固定了;
2. Char 数组的地址是常量,不能另赋其它值,不能像Pchar 一样
(五)WideString
WideString 长字符串类型与ANSIString 类型相似,只是它基于WideChar 字符类型,
WideChar
字符为双字节Unicode 字符。但是Delphi 的vcl 是使用AnsiString 实现的,所以使用WideString 在Delphi 中实际使用会很不方便,这里不详细介绍了。
(六)一些例程
下面举一些具体的例子,有助于帮助我们理解Object Pascal 关于字符串的一些基础概念。
最好先不要用Delphi 调试。看一下个过程中ShowMessage()中的返回值。
//题1
[delphi] view plaincopy
1. procedure TForm1.Button1Click(Sender: TObject);
2. var
3. s: String;
4. begin
5. s := 'abcde';
6. PChar(s)[0] := 'm';
7. ShowMessage(s);
8. end;
分析:答案为PChar(s)[0] := 'm';引起内存访问冲突。因为对于像 s1:='abcde' 或者
ShowMessage('wxyz') 这样的语句,常数字符串是放在代码段(CODE Segment)中的,在32 位保护模式下代码段是不能直接写入的。
//题2
[delphi] view plaincopy
1. procedure TForm1.Button2Click(Sender: TObject);
2. var
3. s: String;
4. begin
5. s := 'abcde';
6. s[1] := 'v';
7. PChar(s)[0] := 'm';
8. ShowMessage(s);
9. end;
分析:答案为mbcde。代码和1 题差不多,但是s[1] := 'v';引起了字符串的Copy-On-Write 的机制为S分配了新的空间,所以可以安全的运行。
//题3
[delphi] view plaincopy
1. procedure TForm1.Button3Click(Sender: TObject);
2. var
3. s1, s2: String;
4. begin
5. s1 := 'abcde';
6. s1[1] := 'v';
7. s2 := s1;
8. PChar(s2)[0] := 'm';
9. ShowMessage(s1);
10. end;
分析:答案为mbcde。由于字符串的Copy-On-Write 的机制,字符串刚赋值后,实际上是指向同一块内存只是增加了引用计数,所以用这种方式修改字符串2,也就是修改了字符串1。
//题4
[delphi] view plaincopy
1. procedure TForm1.Button4Click(Sender: TObject);
2. var
3. s1, s2: String;
4. begin
5. s1 := StringOfChar('A', 20) + #0 + 'abcde';
6. s2 := s1;
7. ShowMessage(IntToStr(Length(s2)));
8. end;
分析:答案为26。AnsiString 的定义可以知道,AnsiString 并不需要以NULL 结尾,因为有长度信息,同样AnsiString 内可以包含任意的NULL 而不会被认为是字符串结尾。
//题5
[delphi] view plaincopy
1. procedure TForm1.Button5Click(Sender: TObject);
2. var
3. s1, s2: String;
4. begin
5. s1 := StringOfChar('A', 20) + #0 + 'abcde';
6. s2 := PChar(s1);
7. ShowMessage(IntToStr(Length(s2)));
8. end;
分析:答案20。注意,这种操作在编程中和4 题的结果是不同的。Pchar 是以“#0”作为字符结尾的。
//题6
[delphi] view plaincopy
1. procedure TForm1.Button6Click(Sender: TObject);
2. var
3. s1: String[100];//ShortString
4. s2: String;
5. begin
6. s1 := 'hello world.'+#0#0#0;
7. s2 := s1;
8. Dec(Byte(s1[0]), 3);
9. PChar(s2)[0] := 'm';
10. ShowMessage(s1 + IntToStr(Length(s2)));
11. end;
分析:答案为hello world.15。这里要注意的是:字符串赋值时复制的字符数只和字符串的长度有关,同时注意ShortString 和AnsiString 在0 位置元素上的区别,ShortString 的实际长度由0 位置的字符决定。
//题7
[delphi] view plaincopy
1. procedure TForm1.Button7Click(Sender: TObject);
2. var
3. s: String[200];
4. s1, s2: String;
5. begin
6. s := 'abcdefghijklmn';
7. SetLength(s1, Length(s));
8. s2 := s1; // s2 := s1 = ‘’
9. Move(s, PChar(s2)^, Length(s));
10. s1[1] := 'z';
11. s2[8] := #0;
12. ShowMessage(s1 + ':' +
13. IntToStr(Length(PChar(s2))) + IntToStr(Length(s2)));
14. end;
分析:答案为zabcdefghijklm:714。这里要注意字符串在传递给无类型可变参数时的区别,
ShortString 变量的地址实际表示0 元素的地址,传递给无类型可变参数时应写为S[1], 而AnsiString是一个指针,指向一个字符串变量的数据区,传递给无类型参数时,应为PChar(s)^ 或者 s[1]
//题8
[delphi] view plaincopy
1. procedure TForm1.Button8Click(Sender: TObject);
2. var
3. s1: array[1..10] of Char;
4. s2, s3: array of Char;
5. begin
6. FillChar( s1, 10, 'M');
7. SetLength(s2, 10);
8. SetLength(s3, 10);
9. Move(s1, s2[0], 10);
10. s3 := s2;
11. s3[0] := 'B';
12. ShowMessage(PChar(s2));
13. end;
分析:答案为BMMMMMMMMM,后面可能会有不可预料的内容出现,因为没有#0 结尾,同时注意Move(s1, s2[0], 10); s1 是短字符串,引用s1 会发生内存溢出,修改s3 也相当于修改s2。
因篇幅问题不能全部显示,请点此查看更多更全内容