- 中查找“强数据类型与类型转化”更多相关内容
- 中查找“强数据类型与类型转化”更多相关内容
- ·上一篇文章:在Delphi中调试COM+
- ·下一篇文章:对象和接口(通俗讲解)
强数据类型与类型转化
Object Pascal是一种“强数据类型”语言。
所谓“强数据类型”,就是严格区分不同数据类型,并不总是允许不同类型数据直接赋值。例如:
var
B: Boolean;
begin
B := 1;
end;
上面的代码期望将整数直接赋值给一个布尔变量,这在Object Pascal中是行不通的。编译上面的程序时,编译器会生成如下错误信息:
[Error] Unit1.pas(29): Incompatible types: ''Boolean'' and ''Integer''
上述信息表示Boolean和Integer类型不兼容。
但是大家知道在C等语言中,是可以将一个整型数赋值给一个布尔变量的。
Pascal以语法严谨而著称,强类型是其严谨的一个重要体现。但是强类型特性也不是为了体现严谨而刻意加上去的,如果那么搞的话,恐怕今天也不会有这么多人使用Delphi了。强类型是有好处的,那就是:可以避免运行时不同类型数据转化出现错误,因为在程序被编译时,潜在的错误就已经被消灭了。
如果说世界上有绝对完美的东西,大家都是不相信的,就像无法找到满足聪明、漂亮、脾气好等100个条件的女朋友那样,那只能是做梦。强数据类型尽管有它的好处,但是在不同数据类型相互转化时则带来了很大的障碍,显得非常死板。而数据类型转化对于一个程序员来说,就像肉食对我那样重要,每天都不可缺少。为此,Object Pascal又特意提供了一些数据类型转化方法。归纳起来,有以下一些:
typecasting, pointers, variants, variant parts in records, absolute addressing
1. typecasting(类型强制转化)
顾名思义,typecasting是将一个类型的数据强制转化为另一个类型的数据。其典型语法如下:
typeIdentifier(expression)
其中typeIdentifier是结果类型标识符,expression可以是原始数据表达式或者变量。如:
var
B: Boolean;
begin
B := Boolean (1); {将整数1强制转化为布尔类型,结果B=True}
end;
对于对象和接口,除了使用typeIdentifier外,Object Pascal还专门提供了as操作符进行转化。在使用as前应该首先判断源对象和结果类型是否兼容、源对象/源接口是否支持结果接口,可分别使用is操作符和Supports函数来作这种判断。
2. pointers(指针)
首先定义一个结果类型的指针,然后将该指针指向原始表达式(使用操作符@或者函数Addr),然后从指针所指地址中取出结果类型的数据(使用操作符^)。如:
var
I: Integer;
P: PBoolean;
B: Boolean;
begin
I := 1;
P := @I;
B := P^; {此时B=True}
end;
3. Variants(可变类型)
Variant可以存储任何类型的表达式,因此,通过Variant这样一个中转站,可以实现数据类型转化。如:
var
V: Variant;
B: Boolean;
begin
V := 1;
B := V; {通过Variant实现类型转化,此时B=True}
end;
4. variant parts in records(变体记录)
一个变体记录典型的定义如下:
type recordTypeName = record
fieldList1: type1;
……
fieldListn: typen;
case tag: ordinalType of
constantList1: (variant1);
……
constantListn: (variantn);
end;
其中case到结尾部分定义了多个变体字段。所有变体字段共享一段内存,换句话说,如果你给constantList1赋值,那么constantList2-constantListn也就被赋了值。至于这些变体字段返回什么值,则是由它们的类型决定。程序根据tag的值决定应该使用constantList1-constantListn中的哪个字段。例如:
type
TDataConv = record
case T: Boolean of
True: (I: Byte);
False: (B: Boolean;);
end;
var
D: TDataConv;
begin
D.I := 1; {此时D.B=True,因为I和B这两个变体字段共享一段内存}
end;
使用变体记录时要注意:
(1)变体字段不能是long strings、dynamic arrays、variants、interfaces等由编译器自动管理内存的类型,也不能是含有上述类型的构造类型,但可以是这些类型和构造类型的指针。
(2)所有变体字段共享一段内存。而共享内存的大小则由最大变体字段决定。
(3)当tag存在时,它也是记录的一个字段。也可以没有tag。
再看Messages单元定义的消息类型TMessage:
TMessage = packed record
Msg: Cardinal;
case Integer of
0: (
WParam: Longint;
LParam: Longint;
Result: Longint);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;
case部分并没有tag字段,接下来的0和1只是为了给变体字段分组,0部分的三个字段和1部分的六个字段共享一段内存。这段内存大小是4(Longint即Integer,占用4个字节)´3=12个字节。一个Word占用2个字节。我们知道一个32位整数在内存中是高字节在后,低字节在前,因此,WParamLo被对应到WParam的低16位,WParamHi被对应到WParam的高16位,依此类推。换句话说,通过WParamLo可以取得WParam的低16位,而通过WParamHi可以取得WParam的高16位。
absolute addressing(绝对地址)
声明共享一段内存的多个变量,从而实现类型转化。和变体字段的机理相似。
一般都很少使用绝对地址来实现数据共享和类型转化,因为它在声明时就固定了变量的地址,导致灵活性缺乏。
下面一段代码演示了如何使用绝对地址来共享数据:
var
I: Integer;
B: Boolean absolute I; {其中absolute是共享关键字,它让B和I共享一段内存}
begin
I := 1; {此时B=True}
end;
上面所讲的各种类型转化方法,本质上都是通过数据的内存地址操作来实现的,因此,理解这些转化方法的关键还是在于理解地址、指针这些概念。
除了上面列出的几种转化方法以外,Delphi也提供了大量的函数和方法来直接实现特定的数据类型转化。关于这些函数和过程的详细列表和功能介绍,请参看“8.1 数据类型转化类”。
在本小节最后,我想举一个较全面的例子,来加深我们对类型转化的认识。这个例子是说非整数类型数据如何在消息中传递。
对于以下两个常用的发送消息API函数:
function PostMessage(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: LPARAM): BOOL; stdcall;
function SendMessage(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
我们知道其中各个参数含义如下:
hWnd:接受该消息的窗口的句柄;
Msg:消息代号;
wParam、lParam:第一个、第二个消息数据。其类型WPARAM = LongInt = Integer。
如果我们需要通过wParam、lParam来传递非整数类型的数据,如一个记录、字符串甚至对象时,应该怎么办呢?这两个参数只能是Integer类型啊!
很显然,这时候我们要将非整数类型的数据转化为一个整数,然后才能作为参数传递。消息接收方收到消息,再将整数类型的消息参数还原为原始类型。
怎么实现这种数据类型转化呢?那就是传地址。即使用消息参数传递数据变量的地址,而地址是用整数表示的。
比如Windows标准消息WM_GETTEXT用于取得一个窗口的文本,下面我们就利用它来取得一个Edit的Text属性。
WM_GETTEXT需要参数lParam作为存储返回结果的缓冲区,wParam指定要求取得数据的最大长度。于是我们可以写出如下代码:
var
PText: PChar;
begin
GetMem(PText, MAXBYTE);
{注意下面一行的Integer(PText),它取得变量PText的地址作为参数传送}
SendMessage(Edit1.Handle, WM_GETTEXT, MAXBYTE, Integer(PText));
ShowMessage(PText);
FreeMem(PText);
end;
接收消息时需要通过地址取得原始数据。下面我们利用消息WM_COPYDATA举个完整例子:
发送消息:
procedure TForm1.Button2Click(Sender: TObject);
var
Data: CopyDataStruct;
MsgText: PChar;
begin
MsgText := ''lxpbuaa'';
with Data do
begin
dwData := 100;
lpData := MsgText;
cbData := Length(MsgText);
end;
SendMessage(Handle, WM_COPYDATA, Handle, Integer(@Data));
end;
接收消息:
{首先声明一个消息方法映射消息:}
procedure WMCopydata(var Msg: TMessage); message WM_COPYDATA;
{实现该消息方法:}
procedure TForm1.WMCopydata(var Msg: TMessage);
var
Data: CopyDataStruct;
S: String;
begin
{因为传过来是数据的地址,所以我们首先用CopyDataStruct的指针类型PcopyDataStruct
将地址转化为一个指针,再用^操作符取得指针所指实际内容}
Data := PCopyDataStruct(Msg.LParam)^;
with Data do
begin
S := ''数据类别代码:'' + IntToStr(dwData) + #13 +
''消息实际数据:'' + PChar(lpData);
end;
ShowMessage(S);
end;
小结
本小节描述了Pascal强类型特性的优点和缺点,并全面介绍了克服该缺点的几种方法。熟练掌握这些方法,可以大大提升在编写程序时调度各种数据类型的能力。
Tags:
作者:佚名评论内容只代表网友观点,与本站立场无关!
评论摘要(共 0 条,得分 0 分,平均 0 分)
查看完整评论