2.6 Object Pascal类型
Object Pascal的最大特点是,它的数据类型特别严谨,这表示传递给过程或函数的实参必须和定义过程或函数时的形参的类型相同。不可能在Pascal中看到一些著名编译器例如C编译器所提示的可疑的指针转换等编译警告信息。这是因为Object Pascal编译器不允许用一种类型的指针去调用形参为另一种类型的函数(无类型的指针除外)。
2.6.1 类型的比较
Delphi的基本数据类型跟C和Visual Basic的相同,表2-5对照列出了Object Pascal的基本数据类型以及C/C++和Visual Basic的基本数据类型。下表提供了当在Delphi中调用不是Delphi的动态链接库(DLL)或目标文件(Obj)中的函数时用于匹配类型的最好参考。
Pascal、C/C++、Visual Basic数据类型的对照表
2.6.2 字符
Delphi有三种类型的字符:
1.AnsiChar 这是标准的1字节的ANSI字符,多字符集字符,现在unicode用的较多了,因为xp系统内部就使用unicode,用ansichar还得转换影响性能了。
2.WideChar 这是2字节的Unicode字符。
3.Char在目前相当于AnsiChar,但在Delphi以后版本中相当于WideChar(就是现在了)。
因为一个字符在长度上并不表示一个字节,所以不能在应用程序中对字符长度进行硬编码,而应使用Sizeof()函数。
注意 Sizeof()标准函数返回类型或实例的字节长度。
2.6.3 字符串
字符串是代表一组字符的变量类型,每一种语言都有自己的字符串类型的存储和使用方法。Pascal类型有下列几种不同的字符串类型:
1.AnsiString 这是Pascal以前缺省的字符串类型,它由AnsiChar字符组成,其长度没有限制,同时与null结束的字符串相兼容。
2.ShortString 保留该类型是为了向后兼容Delphi1.0,它的长度限制在255个字符内。
3.WideString 功能上类似于AnsiString,但它是由WideChar字符组成的。
4.PChar 指向null结束的Char字符串的指针,类似于WideChar字符组成的。
5.PAnsiChar 指向null结束的AnsiString字符串的指针。
6.PWideString 指向null结束的WideString字符串的指针。
//缺省情况下,如果用如下的代码来定义字符串,编译器认为是WideString字符串: var S:string;//编译器认为S的类型是WideString //当然,能用编译开关$H来将string类型定义为ShortString,当$H编译开关的值为负时,string变量是ShortString类型; //当$H编译开关的值为正进(缺省情况),字符变量是WideString类型。 var {$H-} S1:string; //S1是ShortString类型 {$H+} S2:string; //S2是WideString类型 //使用$H的规则的一个例外是,如果在定义时特地指定了长度(最大在255个字符内),那么总是ShortString; var S:string[63];//63个字符的WideString字符串
1.AnsiString类型
AnsiString(或长字符串)类型是在Delphi2.0开始引入的,因为Delphi1.0的时候特别需要一个容易使用而且没有255个字符限制的字符串类型,而AnsiString正好能满足这些要求。AnsiString是动态分配的并有自动回收功能。Object Pascal能根据需要为字符串分配空间,所以并不用像在C/C++中所担心的为中间结果分配缓冲区。另外,AnsiString字符串总是以null字符结束的,这使得AnsiString字符能与Win32 API中的字符串兼容。
当两个或更多的AnsiString类型共享一个指向相同物理地址的引用时,Delphi内存管理使用了copy-on-write技术,一个字符串要等到修改结事,才释放一个引用并分配一个物理字符串。
Var S1,S2:string; begin //给S1赋值,S1的引用计数为1 S1 :='And now for something...'; S2 := S1; //现在S2和S1指向同一个字符串,S1的引用计数为2 //S2现在改变了,所以它被复制到自己的物理空间,并且S1的引用计数减1 S2 := S2+'completely different1'; end;
生存期自管理类型
除了AnsiString以外,Delphi还提供了其他几种生存期自管理类型,这些类型包括:
WideString、Variant、OleVariant、Interface、Dispinterface和动态数组。
生存期自管理类型,又被称为自动回收类型,是指那些在被使用时就占用一定的特殊资源,而在它离开作用域时自动释放资源的类型。
对于全局变量,这种情况是相当直观的:作为应用程序结束代码的一部分,编译器自动插入代码来清除每一个生存期自管理类型的全局变量。因为在应用程序在被装入时,全局变量都被初始化为0,每一个全局变量将用初始化时的0、空或其他值来指示它还没有被使用,基于这种方法,终止代码只清除那些确定在应用程序中被使用的全局变量。
对于局变量来讲,这种情况稍微有点复杂:首先,在过程或函数开始运行时,编译器插入的代码保证初始化这些局部变量,接着,编译器产生一个try…finally的异常处理块,它包容整个函数体,最后,编译器在finally块插入代码来清除生存期自管理变量,下面是代码:
procedure Foo;Var S:string;begin //过程体 //在这里用Send; //虽然这个过程看起来简单,如果考虑进由编译器插入的代码,它看起来像下面的代码:procedure Foo;Var S:string;begin S := ''; try //过程体 //在这里用S finally //在这里清除S end;end;
(1)字符串运算符
能用+运算符或Concat()函数来连接两个字符串,推荐使用+运算符,因为Concat()函数主要用来向后兼容.
(2)长度分配
第一次声明AnsiString时,它是没有长度的,因此在字符串中就没有为字符分配空间。为了对字符串分配空间,用一行字母或另一个字符串对它进行赋值,或者用SetLength()过程来分配空间。
var S:string; begin S[1] := 'a'; //不能工作,因为没有被分配空间 SetLength(S,1); S[1] := 'a'; //现在S有足够空间来容纳字符 end
(3)Win32的兼容性
AnsiString字符串总是以Null结束的。因此,它能跟以Null结尾的字符串兼容,这就使得调用Win32API函数变得容易了。只要把一个字符串类型强制转换为PChar类型就行了。如果使用了将AnsiString字符串强制转换为PChar类型的函数和过程,在使用结束后,要手工把它的长度恢复为原来Null的结束长度。STRUTILS单元中的RealizeLength()函数可以实现这一点:
var S:string; begin SetLength(S,256); //分配空间 GetWindowDirectory(PChar(S),256); //API调用 RealizeLength(S); //重置长度 end;
(4)移植性问题
当要移植Delphi1.0的应用程序时,一定要注意几个关于AnsiString的问题:
在使用PString(指向ShortString字符串的指针)的地方,应当替换成String类型是。记住:AnsiString已经是一个指向字符串的指针。
不能再通过字符串的第0个元素来设置或得到字符串的长度,只能通过Length()函数来得到字符串的长度,通过 SetLength()过程来设置字符串的长度。
不再需要通过调用StrPas()和StrPCopy()来进行字符串与PChar之间的转换,可以把AnsiString强制类型转换为PChar。如果要把PChar的内容复制到AnsiString,直接用赋值语句。
注意:对长字符串设置长度时,必须用SetLength()过程,不能使用直接访问字符串第0个元素来设置长度的方法,在应用程序从16位的Delphi1.0升级到32位时会出现问题。
2.ShortString类型
ShortString类型是Delphi1.0中的字符串类型,ShortString类型有时被称为Pascal字符串(Pascal String)或长度-字节字符串(Length-Byte String)。$H编译开关的值用来决定当变量声明为字符串时,它是被当作AnsiString类型还是被当作ShortSting类型。
在内存中字符串的第0个元素存放了字符串的长度,紧跟在后的字符就是字符串本身。ShortString缺省的最大长度为256个字节,这表示在ShortString中不能有大于255个字符。相对于AnsiString来说,用ShortString是相当随意的,因为编译器会根据需要为它分配空间,所以不用担心中间结果是不是要预先分配内存。
// 一个ShortString变量用下面的代码声明和初始化: var S:ShortString; begin S := 'Bob the cat.'; end. //用short类型限定符和一个长度限制来为ShortString分配小于256个字节的空间,示例如下: var s:string[45]; begin s := 'This string must be 45 or fewer characters.'; end.
如上代码,这个字符串肯定是ShortString,而不再受$H编译开关的影响,能指定的短字符串的最大长度是255个字符。
如果存放的字符串比分配的空间大,字符串将被截断。
用下标访问ShortString中的一个特定字符时,如果下标的索引值大于声明时的长度,则会得到不正确的结果或造成内存混乱。
可以在Project Options对话框中选中Range Checking复选框,这样编译器会自动加上特殊的逻辑在运行时捕捉此类错误。
提示:虽然在程序中包括范围检查能发现字符串错误,但范围检查多少都会影响应用程序的性能。通常使用的方法是在开发程序或调试程序的阶段用范围检查,而在确信程序稳定时,去掉范围检查。
和AnsiString类型字符串不一样,ShortString跟以Null结尾的字符串不兼容,正因为这样,用ShortString调用Win32函数时,要做一些工作。下面这个ShortStringAsPChar()函数是在STRUTILS.PAS单元中定义的。
function ShortStringAsPChar(var S:ShortString):PChar; begin if Length(S) = High(S) then Dec(S[0]); //如果S太长,就截取一部分 S[Ord(Length(S))+1] := #0; //把null加到字符串的末尾 Result := @S[1]; //返回PChar化的字符串 end;
注意:Win32API函数需要以NULL结尾的字符串,不要把ShortString字符串传递给API函数,长字符串可以传递给Win32API函数。
3.WideString类型
WideString类型像AnsiString一样是生存期自管理类型,它们都能动态分配、自动回收并且彼此能相互兼容,不过WideString和AnsiString的不同主要在三个方面:
1)WideString由WideChar字符组成,而不是由AnsiChar字符组成的,它们跟Unicode字符串兼容。
2)WideString用SysAllocStrLen()API函数进行分配,它们跟OLE的BSTR字符串相兼容。
3)WideString没有引用计数,所以将一个WideString字符串赋值给另一个WideString字符串时,就需要从内存中的一个位置复制到另一个位置。这使得WideString在速度和内存的利用上不如AnsiString有效。
4.以NULL结束的字符串
Delphi有三种不同的以NULL结束的字符串类型:PChar、PAnsiChar和PWideChar。它们都是由Delphi的三种不同的字符组成的。这三种类型在总体上跟PChar是一致的。PChar之所以保留是为了跟Delphi1.0和Win32 API兼容,而它们需要使用以NULL结束的字符串,PChar被定义成一个指向NULL结束的字符串指针。与AnsiString和WideString类型不同,PChar的内存不是由Object Pascal自动产生和管理的,要用Object Pascal的内存管理函数来为PChar所指向的内存进行分配。PChar字符串的理论最大长度是4GB,PChar变量在内存中的分布如图:
提示:在大多数情况下,AnsiString类型能被用成PChar,应该尽可能地使用AnsiString,因为它对字符串内存的管理是自动的,极大地减少了应用程序中内存混乱的错误代码,因此,要尽可能地避免用PChar类型以及对它相应进行人工分配内存。
内存分配和释放函数表
内存分配函数 |
内存释放函数 |
AllocMem() |
FreeMem() |
GlobaAlloc() |
GlobaFree() |
GetMem() |
FreeMem() |
New() |
Dispose() |
StrAlloc() |
StrDispose() |
StrNew() |
StrDispose() |
VirtualAlloc() |
VirtualFree() |
//下面的例子演示了使用PChar和String类型时的内存分配技术:
var P1,P2:PChar;
S1,S2:String;
begin
P1 := StrAlloc(64*SizeOf(Char)); //对P1分配63个字符的缓冲区
StrPCopy(P1,'Delphi 2010'); //复制一组字母到P1
S1 := 'Developer's Guide'; //在S1中放一个字符串
P2 := StrNew(PChar(S1)); //P2指向S1的备份
StrCat(P1,P2); //连接P1和P2
S2 := P1; //S2现在为'Delphi 2010 Developer's Guide'
StrDispose(P1); //清除P1和P2的缓冲区
StrDispose(P2);
end.
首先要注意到,在为P1分配内存时StrAlloc()中用到的Sizeof(char)。要记住在以后Delphi版本中一个字符的长度要从一个字节变成两个字节(目前新的版本已经改变),因此,不能假定一个字符的长度为一个字节,Sizeof()就保证了不管字符长度是多少都能正确分配内存。
StrCat()用来连接两个PChar字符串。注意,这里不能像对长字符串和ShortString类型那样用+运算符来连接两个字符串。
StrNew()函数用来把字符串S1中的值拷贝到P2中,使用这个函数时要避免出现内存被覆盖的错误,因为StrNew()函数只为字符串分配足够的空间,请看下面的例子:
var P1,P2:PChar begin P1 := StrNew('Hello'); //只分配足够P1的内存 P2 := StrNew('World'); //只分配足够P2的内存 StrCat(P1,P2); //由于P1的内存空间不够,会发生溢出。 end;
2.6.4 变体类型
Delphi2.0 引进了一个功能强大的数据类型,称为变体类型(Variant),主要是为了支持OLE自动化操作。实际上,Delphi的Viriant封装了OLE使用的Variant,但Delphi的Variant在Delphi程序听其他领域也很有用。
Delphi3.0 引进了一个新的被称为OleVariant类型,它跟Variant基本一致,但是它只能表达与OLE自动化操作相兼容的数据类型是。
1.Variant能动态改变类型
有时候变量的类型在编译期间是不确定的,而Variant能够在运行期间动态地改变类型,这就是引入Variant类型的目的。例如,下面的代码在编译期间和运行期间都是正确的:
var V:Variant; begin V := 'Delphi is Great!'; //Variant 此时是一个字符串 V := 1; //Variant 此时是一个整数 V := 123.34; //Variant 此时是一个浮点数 V := True; //Variant 此时是一个布尔值 V := CreateOleObject('Wrod.Basic'); //Variant 此时是一个OLE对象 end;
Variant能支持所有简单的数据类型,例如整型、浮点型、字符串、布尔型、日期和时间、货币以及OLE自动化对象等。不过Variant不能表达Object Pascal对象(也就是实例化的类)。Variant可以表达不均匀的数组(数组的长度是可变的,它的数据元素能表达任何一种类型是,也可以包括另一个Variant数组)。
2.Variant是生存期自管理的
Delphi自动分配和释放Variant类型所需的内存。首先Delphi将Variant初始化为一个不确定的值。在赋值期间,它把VType域设为VarString,并把字符串指针拷贝到它的VString域,然后增加字符串S的引用计数。当这个Variant类型的变量离开其作用域时(过程结束或调用了返回代码),它被清除并减少S的引用计数。
3.Variant的强制类型转换
能显式的把一个表达式强制类型转换成Variant类型,例如,表达式:
Variant(X)
使得Variant中的类型代码跟表达式x的结果的数据类型一致,x的结果的数据类型必须是整数、实数、货币型、字符串、字符或布尔型。也可以把Variant类型的表达式强制类型转换为简单类型,如:
V := 1.6;
S := String(V); //S现在是字符串1.6
I := Integer(V); //I现在取整为整数2
B := Boolean(V); //如果V的值为0,则B为False,否则为True
D := Double(V); //D现在为浮点数1.6
上面的例子也可以直接使用赋值语句。
4.表达式中的Variant
能在表达式中用Variant,并通过=、*、/、div、mod、shl、shr、and、or、xor、not、:=、<>、<、>、<=和>=进行运算。
当Variant变量出现在表达式中时,Delphi知道如何基于Variant中的内容进行运算。
2.6.5 Currency类型