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在那里呢,是在对记录进行操作的函数或过程内吗?

解决方案 »

  1.   

    以下文字摘自拙作《Delphi精要》:一个变体记录典型的定义如下: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位。————————————————————————————————————
    宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
    ————————————————————————————————————
      

  2.   

    我没看到过这类的说明。
    但是知道case integer of 不一定是integer,只要是有序类型就行,bool,char,byte 都行。而且,也不必一定是 0, 1。我不知道为什么要写成这样,但是知道域的名字不能重复。
      

  3.   

    case integer of
     我想可能是编译器的需要吧,可以这样理解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;
      

  4.   

    做个测试:

    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个字段!
      

  5.   

    lxpbuaa(桂枝香在故国晚秋) 说的很对啊
    我试了下
    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;
      

  6.   

    天啊,都这么多人,说的很详细了,不过一楼的一个地方说的不对记录的变体部分的条件域必须是有序类型,而不是什么可以计算大小的类型,另外这句话表达不对----“如果你给constantList1赋值,那么constantList2-constantListn也就被赋了值”,记录的变体部分的实际类型是Variant类型,所以这里可以定义成任何类型,同时空间大小服从所有变体类型中占用空间最大的那个,任何时刻变体部分的类型都是唯一的,其他类型此时没有意义,也不存在,所以不能说一个被赋值,所有的全部赋予了值!
      

  7.   

    在定义variant part时,可以定义一个标志域,也可以没有。定义标志域时则:
    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但它们都需要自动终结化,如果它们
      

  8.   

    一不小心提交了。继续……Long String、WideString、Dynamic Array、Interface的大小都是指针大小,OleVariant其实就是COM SDK中的VARIANT结构,大小是16字节。但在Object Pascal中它们都需要自动终结化,如果它们出现在variant part中,编译器就无法知道它们是否应该进行终结化——因为不知道当前存储的是哪种类型。如果学过C语言,对variant part应该很好理解,它就相当于union。只是多了些限制:每个记录体只能有一个variant part;只能出现在记录体的结尾;不允许出现自动终结化的类型。
      

  9.   

    plainsong(短歌) :sorry。我当然知道他们都是指针,而且知道指针本身是4个字节,“编译时不能确定大小的类型”想表达的意思是实际数据占用的内存大小不能确定。这里应该是我表示不严密,引起误会。————————————————————————————————————
    宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
    ————————————————————————————————————
      

  10.   

    记录中的变体部分
     记录类型中可以含有变体部分,有点象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位(简单地说,作用就是请求整数参量)。
      

  11.   

    老王, 你的理解有问题。拿:  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;
    来说,应该是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。————————————————————————————————————
    宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
    ————————————————————————————————————
      

  12.   

    lxpbuaa:
      编译时不能确定“实际大小”的不只这几种,还有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的兼容,如果不是出于对效率要求的极端考虑,应该尽可能用对象代码,用多态来处理不同情况时的不同数据存储要求。
      

  13.   

    plainsong:OK。的确是这样的,我原来对这点的理解不深入,在此谢过。————————————————————————————————————
    宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
    ————————————————————————————————————
      

  14.   

    编译器的提示:
    [Error] Unit1.pas(20): Type 'String' needs finalization - not allowed in variant record
    是个比较好的解释。————————————————————————————————————
    宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
    ————————————————————————————————————
      

  15.   

    谢谢各位,确实讲得很清楚了对于变体记录类型的结构已不存任何疑问,我这里在依据一个实例做个简单的分析(类似于jacky_shen(jacky) 所说的)
    假设我现在要用一个记录来存储一个员工薪资状况的记录,包含信息如下:
    雇员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位(简单地说,作用就是请求整数参量)。这跟我提问题的这个消息记录相似。
      对于变体记录类型,不能使用“编译时不能确定‘实际大小’的数据类型,具体短歌、故国有比较深入分析。