Delphi中的字符一直处于懵懵懂懂的状态,不同于我接触到的其它编程语言在Delphi中居然有好几种字符串,今天好好研究一番!!
Delphi中字符串的操作很简单,但幕后情况却相当复杂。Pascal传统的字符串操作方法与Windows不同,Windows吸取了C语言的字符串操作方法。
32位Delphi中增加了长字符串类型,该类型功能强大,是Delphi缺省的字符串类型。
传统的字符串类型是一个字符序列,序列的头部是一个长度字节,指示当前字符串的长度。由于只用一个字节来表示字符串的长度,所以字符串不能超过255个字符。
这一长度限制为字符串操作带来不便,因为每个字符串必须定长(确省最大值为255),当然你也可以声明更短的字符串以节约存储空间。 为克服传统Pascal字符串的局限性,32位Delphi增加了对长字符串的支持。这样共有三种字符串类型:
ShortString
短字符串类型也就是前面所述的传统Pascal字符串类型。短字符串中的每个字符都属于 AnsiChar类型(标准字符类型)
var
S: ShortString; { 255个字符长度,256个字节}
S1: String[255]; { S1和S的字符类型一样}
Len: Integer;
begin
S := 'Hello';
Len := Ord(S[0]); { Len现在包含S的长度为5,Ord函数可以把一个字符类型转换为整数类型}
Len := SizeOf(S); { Len现在包含的是ShortString类型的大小,为256字节}
end;
以上例子通过S[0]可以获得S的字符串长度,当然也可以用Length函数来确定一个短字符串的长度。
可以通过数组的下标来访问ShortString中的一个特定位置的字符,具体使用参看下面例子和注释说明:
var
S: string[8];
i: Integer;
begin
S := 'a_pretty_darn_long_string';
{ 因为S只有8个字符大小,因此s的实际存储的内容为"a_pretty"}
i := 10;
S[i] := 's';
{ 因为S只有8个字符大小,试图改写第10个元素,将会使内存混乱}
end;
AnsiString
这种类型也有人称为长字符串类型(LongString)同时也是Delphi默认的字符串类型,它是一种动态分配的字符串,其大小只受可用内存的限制。声明一个长字符串,只需要用关键字String不加大小参数即可。据说使用了更新前拷贝(copy--on-write)技术。恕我才疏学浅没研究过这个东西。这类字符串长度没有限制(可以存储多达20亿个字符),其字符类型也是AnsiChar类型。
由于是动态分配的,一次可以随意修改字符串,而不用担心对其他的影响,也不用担心越界的问题。String类型没有0元素,试图存取String类型的0元素会产生一个编译错误。
之前在万一老师的博客中看到了以下的代码不太理解,现在可以完全解释为什么我们试图将一个string类型的变量直接赋值给字符数组时报错了
//字符串 < > 字符数组,注意这里说的字符串仅仅是长字符串
var
arr: array[0..5] of Char;
str: string;
begin
{可以把字符串常量直接赋给字符数组; 但超界不行}
arr := 'Delphi';
ShowMessage(arr); {Delphi}
{可以把字符数组直接赋给字符串变量}
str := arr;
ShowMessage(str); {Delphi}
{其实字符串内部也是包含了一个字符数组, 所以能索引访问, 不过它的索引起始于 1}
ShowMessage(str[1]); {D}
ShowMessage(arr[0]); {D}
{但不能把一个字符串变量赋给字符数组}
//arr := str; {错误; 这需要用其他手段实现, 譬如复制或移动内存}
end;
WideString
长字符串类型与AnsiString 类型相似,只是它基于WideChar字符类型,WideChar字符为双字节Unicode字符。
var
S: string;
{ 在Delphi 7中默认string等同于AnsiString}
WS: WideString;
begin
S := '世界你好';
WS := S;
ShowMessage(S[1]); { 此时无任何显示,因为S[1]取出的是‘世’的一半}
ShowMessage(WS[1]); { 显示‘世’}
end;
不过WideString和AnsiString的不同主要在三个方面:
- WideString由WideChar字符组成,而不是由AnsiChar字符组成的,它们跟Unicode字符串兼容。
- WideString用SysAllocStrLen()API函数进行分配,它们跟OLE的BSTR字符串相兼容。
- WideString没有引用计数,所以将一个WideString字符串赋值给另一个WideString字符串时,就需要从内存中的一个位置复制到另一个位置。这使得WideString在速度和内存的利用上不如AnsiString有 效。
Ansi和Unicode
字符编码:为了区分一个字符到底使用了几个字节,就不能将字符的编号直接存储到计算机中,字符编号在存储之前必须要经过转换,在读取时还要再逆向转换一次,这套转换方案就叫做字符编码。它规定了如何将字符的编号存储到计算机中
字符集:为每个字符分配了唯一的编号。可以将字符集理解成一个很大的表格,它列出了所有字符和二进制的对应关系,计算机显示文字或者存储文字,就是一个查表的过程。它定义了字符和二进制的对应关系
ASCII(American Standard Code for Information Interchange,美国信息交换标准码),它使用7 bits来表示一个字符,总共表示128个字符,后来IBM公司在此基础上进行了扩展,用8bit来表示一个字符,总共可以表示256个字符
ANSI(American National Standard Institite)美国国家标准学会(美国的一个非营利组织),首先ANSI编码是一种对ASCII码的拓展,它不是指的一种特定的编码,而是不同地区扩展编码方式的统称,各个国家和地区所独立制定的兼容ASCII但互相不兼容的字符编码,微软统称为ANSI编码
Unicode 是一套字符集,而不是一套字符编码。它固定使用16 bits(两个字节)来表示一个字符,共可以表示65536个字符。标准的Unicode称为UTF-16。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8
个人理解:ANSI是每个国家根据自己的语言在ASCII基础扩展的一些编码。为了实现大一统出现了Unicode,而Unicode为了兼容以前的字符出现了双字节(UTF-16)和单字节(UTF-8)之分
PChar和字符数组
在第二季的视频中我曾经对端口号做过一次转换sin_addr.S_addr := inet_addr(PAnsiChar(AnsiString(EditAddr.Text))); 这段代码,一个将字符串转成PAnsiChar的一个场景,这是个什么玩意儿?其实这样做纯粹是为了兼容winAPI中的数据类型
在C和C++中没有真正的字符串数据类型,都是通过以Null结尾(0)的字符数组来实现的,字符数组没有长度字节,因此只能通过结尾的Null标志来作为字符串的字符结束标志。又因为Windows是用C编写的,很多Windows函数要用到以字符数组作为参数,但Pascal字符串类型不是字符数组,因为为了让Pascal字符串也能与Windows兼容,就需要一个字符串数组,PChar类型正是符合这种需求,在任何需要字符数组的地方都可用PChar。相应的也有PAnsiChar和PWideChar,分别对应于AnsiChar字符和WideChar字符。