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;
上面这个记录类型里的Integer是从哪里来的?
想想总觉得有些奇怪,对于一个记录类型来说,它的字段是可以为任意数据类型的,对记录类型的数据进行操作都是通过这样来进行的:MyRecord.Field,好像不能传递参数,那么这个Integer在那里呢,是在对记录进行操作的函数或过程内吗?
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;
上面这个记录类型里的Integer是从哪里来的?
想想总觉得有些奇怪,对于一个记录类型来说,它的字段是可以为任意数据类型的,对记录类型的数据进行操作都是通过这样来进行的:MyRecord.Field,好像不能传递参数,那么这个Integer在那里呢,是在对记录进行操作的函数或过程内吗?
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位。————————————————————————————————————
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
但是知道case integer of 不一定是integer,只要是有序类型就行,bool,char,byte 都行。而且,也不必一定是 0, 1。我不知道为什么要写成这样,但是知道域的名字不能重复。
我想可能是编译器的需要吧,可以这样理解type
trecord=record
a:Double;
case integer of //只是用于说明下面是变体了,同时指明可以有多少种变体
constant1:( 变体1)
constant2:( 变体2)
.....
end;如果用 case ATag:Integer of,则在程序中可以根据 ATag 的值取不同的 field。 case XXX.ATag of
constant1:
begin
//取 变体1 中的 field
end;
constant1:
begin
//取 变体2 中的 field
end;
...
end;
type
{**********************
声明变体记录类型
**********************}
TMyRecord = Record
Field1:string;
Field2:integer;
case Field3:Integer of
0:(
D1:integer;
D2:boolean);
1:(
S1:integer;
S2:array[1..5] of integer);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyRecord:TMyRecord;
begin
{**********************
给变体记录类型赋值
**********************}
MyRecord.Field1:='abc';
MyRecord.Field2:=5;
MyRecord.Field3:=0;
MyRecord.D1:=5;
MyRecord.D2:=true;
{********************** *****
看一下未被赋值的部分是什么
****************************}
showmessage(inttostr(MyRecord.S1));
{********************** *****
S1 还是5!
****************************}
end;在测试一下:
procedure TForm1.Button1Click(Sender: TObject);
var
MyRecord:TMyRecord;
begin
{**********************
给变体记录类型赋值
**********************}
MyRecord.Field1:='abc';
MyRecord.Field2:=5;
MyRecord.Field3:=0;
MyRecord.D1:=5;
MyRecord.D2:=true;
MyRecord.S1:=7;
{********************** *****
看一下未被赋值的部分是什么
****************************}
showmessage(inttostr(MyRecord.S1));
{********************** *****
S1 是7!
****************************}
end;这就是说,这个变体记录类型确实有7个字段!
我试了下
type TMyRecord = Record
case Integer of
0:
(A1: Integer; A2: Integer);
1:
(B1: Word; B2: Word; B3: Word; B4: Word);
end;procedure TForm1.Button1Click(Sender: TObject);
var
MyR: TMyRecord;
begin
MyR.A1 := 70000;
ShowMessage('B1 is ' + IntToStr(MyR.B1) + #13#10 +
'B2 is ' + IntToStr(MyR.B2) );
//结果就是B1 is 4464, B2 is 1
end;
case Flag:Integer of
...
否则就是
case Integer of
...
前一种方式会导致Record中多一个Flag的整形域。注意它也是给你的代码用的,编译器不会自动检查Flag和variant part中的类型的匹配。也就是说,前一种方式相当于:
Flag:Integer;
case Integer of
...lxpbuaa讲的基本上很全面了,不过有点小错误:
并不是“编译时不能确定大小的类型不能放在variant part”中(我怀疑根本没有这种东东),而是“需要自动终结化类型不能出现在variant part”中。Long String、WideString、Dynamic Array、Interface的大小都是指针大小,OleVariant其实就是COM SDK中的VARIANT但它们都需要自动终结化,如果它们
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
记录类型中可以含有变体部分,有点象case语句。变体部分必需在记录中其他字段的声明之后。声明含有变体部分的记录类型,语法如下:type recordTypeName = record fieldList1: type1; ... fieldListn: typen;case tag: ordinalType of constantList1: (variant1); ... constantListn: (variantn);end;在保留字case之前的声明部分与标准记录类型中的声明相同。剩余从case到end之前最后一个可选的分号(;)之间叫做变体部分。在变体部分中:· tag是可选的,并且可以是任意有效的标识符。如果忽略tag,那么也要同时忽略紧随其后的冒号(:)。· ordinalType表示一个序数类型。· 每个constantList都是一个表示ordinalType类型的常量,或者逗号隔开的此类常量列表。在当前变体部分声明中,对于所有的constantLists,同一个值最多只能出现一次。· 每个variant都是一个逗号隔开的声明列表,这与记录类型主要部分中的fieldList: type结构相似。也就是说,variant具有如下形式fieldList1: type1; ...fieldListn: typen;这里,每个fieldList是一个有效标识符或者逗号隔开的标识符列表,每个type代表一个类型,最后的分号是可选的。所有的类型(types)都不能是长串、动态数组、变体(即Variant类型)或接口,也不能是包含长串、动态数组、变体(即Variant类型)或接口的结构类型;但可以是指向这些类型的指针。含有变体部分的记录在语句构成上比较复杂,但在语义上比较简单,具有欺骗性。记录中的变体部分包含几个变体,在内存中它们共享相同的空间。可以在任何时候对记录中任何变体的任何字段读或写;但如果在一个变体中写一个字段并且在另一个变体中写另一个字段,那么后面的写操作可能覆盖前面写入的数据。对于Tag,如果存在,那么将作为记录中的非变体部分,被视为额外的字段(其类型是ordinalType)。 变体部分有两种用途。首先,假设要创建一个记录类型,它有不同数据种类的字段,但不需要在单个记录实例中使用全部数据。例如,type TEmployee = record FirstName, LastName: string[40]; BirthDate: TDate; case Salaried: Boolean of True: (AnnualSalary: Currency); False: (HourlyWage: Currency);end;这里的意思是,每个雇员都有年薪或计时工资,但不能都有。因此,在创建TEmployee时,没有理由为两个字段都分配足够的内存。这种情况下,变体之间只有名字不同,而要想让字段的类型不同也是很容易的。考虑相对复杂的例子:type TPerson = record FirstName, LastName: string[40]; BirthDate: TDate; case Citizen: Boolean of True: (Birthplace: string[40]); False: (Country: string[20]; EntryPort: string[20]; EntryDate, ExitDate: TDate); end;type TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other); TFigure = record case TShapeList of Rectangle: (Height, Width: Real); Triangle: (Side1, Side2, Angle: Real); Circle: (Radius: Real); Ellipse, Other: (); end;对上面的每个记录实例,编译器分配足够的内存以在最大的变体中能够保存所有的字段。可选的Tag和常量列表constantLists(如上面最后一个例子中Rectangle、Triangle等)在编译器管理字段的途径中不扮演任何角色,它们仅为程序员提供方便。 变体部分的第二个用途是,可以将相同的数据视为不同的类型,尤其在编译器不允许类型转换的情况。例如,如果有一个64位的实数Real类型作为变体的第一个字段,一个32位的整数Integer类型作为另一个变体的第一个字段,那么可以向实数Real字段赋值然后以整数Integer字段读出其前32位(简单地说,作用就是请求整数参量)。
Field1:string;
Field2:integer;
case Field3:Integer of
0:(
D1:integer;
D2:boolean);
1:(
S1:integer;
S2:array[1..5] of integer);
end;
来说,应该是D1和S1共享数据,D2和S2[1]共享数据。所以:
MyRecord.D1:=5;
MyRecord.D2:=true;
后 MyRecord.S1 = 5
而:
MyRecord.D1:=5;
MyRecord.D2:=true;
MyRecord.S1:=7;
后,D1的值5会被替换为7。————————————————————————————————————
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
编译时不能确定“实际大小”的不只这几种,还有Object(如TStringList),但Object是可以放到variant part中的,因为它需要你手工去Free,而不是自动终结。其实关键在于我们声明一个变量时:
type
TFoo = recort
Intf: IIterface;
end;
var
Foo: TFoo
begin
...
//编译器自动插入的代码:
if Foo.Intf <> nil then
Foo.Intf._Release;
end;而当IInterface出现在variant part域时:
type
TFoo = recort
case Integer of
1: (Intf: IIterface);
2: (Int: Integer);
end;
var
Foo: TFoo
begin
Foo.Int := 12345;
...
//编译器如何确定是否调用Foo.Intf._Release??
end;其余Dynamic Array、Long String等类型同理。事实的根本在于,在一个variant part中,同一时刻只有一个part是有效的,其余都是无效的。无效并不是说=nil或其它,而是连对值的检查都是不可靠的。这时我们可以不去访问 它,但对需要自动终结化的类型来说,编译器将会无所适从。事实上,variant part的存在应该理解为是对Pascal的兼容,如果不是出于对效率要求的极端考虑,应该尽可能用对象代码,用多态来处理不同情况时的不同数据存储要求。
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
[Error] Unit1.pas(20): Type 'String' needs finalization - not allowed in variant record
是个比较好的解释。————————————————————————————————————
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
假设我现在要用一个记录来存储一个员工薪资状况的记录,包含信息如下:
雇员ID:ID
雇员职务:Duty:1为职员、2为员工,
薪资状况:
1:职员
薪资单位:Unit
薪资:SALE
2:员工
薪资单位:Unit
薪资:SALE
出勤时数:TOTAL如上,根据职务的不同雇员的薪资核算方式也会不一样
当然,如果在没有变体记录的情况下,我门是否需要声明两个记录类型,一个保存职员、一个保存员工信息
如下:
职员:
Type
TOfficeRecord = Record
id:string;
Duty:integer;
Unit:integer;//1为月,2为小时
Sale:integer;
end;
员工:
Type
TEmpRecord = Record
id:string;
Duty:integer;
Unit:integer;//1为月,2为小时
Sale:integer;
TOTAL:integer;
end;
那么,在实际的运用中我们就要先声明这两个记录类型的变量,然后根据不同的雇员职务操作不同变量,如下:
var
OfficeRecord:TOfficeRecord;
EmpRecord:TEmpRecord;
begin
....
case Duty of
1:(OfficeRecord....);
2:(EmpRecord....);
end;
....
end;
那么变体记录类型实际上是将上面两个记录进行了类似合并的处理,让这两个记录中不相同的字段共享同一内存区域(这个区域根据最大变体确认)
那么,我们上面的问题就是这样来处理:
Type
TPersonlRecord = Record
id:string;
case Duty:integer of
1: (Unit:integer;
Sale:integer);
2: (Unit:integer;
Sale:integer;
TOTAL:integer);
end; 我想这也就与短歌所说:‘variant part的存在应该理解为是对Pascal的兼容,如果不是出于对效率要求的极端考虑,应该尽可能用对象代码,用多态来处理不同情况时的不同数据存储要求。‘一致。
当然,使用变体记录类型还有jacky_shen(jacky) 所说的的第二个用途:可以将相同的数据视为不同的类型,尤其在编译器不允许类型转换的情况。例如,如果有一个64位的实数Real类型作为变体的第一个字段,一个32位的整数Integer类型作为另一个变体的第一个字段,那么可以向实数Real字段赋值然后以整数Integer字段读出其前32位(简单地说,作用就是请求整数参量)。这跟我提问题的这个消息记录相似。
对于变体记录类型,不能使用“编译时不能确定‘实际大小’的数据类型,具体短歌、故国有比较深入分析。