• delphi中的record记录和变体记录 转载 https://blog.csdn.net/yt_maomao/article/details/36631133


    //Integer类型刚好是4个字节,ShortInt类型是1个字节,但是Windows中内存是4字节分配,
    //所以这里其实还是4个字节,用SizeOf可以看到这个record的大小是8字节,这样虽然浪
    //费了空间,但是加快了速度(Windows内存分配中的边界对齐原理)
    TPerson = record
    Age: Integer;
    Sex: ShortInt;
    end;
    TPackedPerson = packed record
    Age: Integer;
    Sex: ShortInt; //使用packed record,用Sizeof可以看到这个record的大小是5字节;
    end;

    TEmployee = record
    ID: Integer; //Integer是4字节
    case Integer of
    0:(YearMoney: Integer); //YearMoney和MonthMoney共用内存,按最大内存分配
    1:(MonthMoney: ShortInt); //该record的大小是8字节
    end;

    TTagEmployee = record
    ID: Integer;
    case Grade: Integer of //这里加入了Grade变量
    0:(YearMoney: Integer); //YearMoney和MonthMoney共用内存,按最大内存分配
    1:(MonthMoney: ShortInt); //该record的大小是12字节(ID+Grade+YearMoney)
    end;

    1.在DELPHI中,我们用record关键字来表明一个记录,有时候,我们还会看到用packed record来声明的记录,这二者的区别就在于存储方式的不同;在windows中,内存的分配一次是4个字节的,而Packed按字节进行内存的申请和分配,这样速度要慢一些,因为需要额外的时间来进行指针的定位。因此如果不用Packed的话,Delphi将按一次4个字节的方式申请内存,因此如果一个变量没有4个字节宽的话也要占4个字节!这样浪费了一些空间,但提高了效率。

    2.变体记录的规则:

           (1)Long  String、WideString、Dynamic  Array、Interface的大小都是指针大小,OleVariant其实就是COM  SDK中的VARIANT结构,大小是16字节。但在Object  Pascal中它们都需要自动终结化,如果它们出现在variant  part中,编译器就无法知道它们是否应该进行终结化――因为不知道当前存储的是哪种类型,因此他们不能出现在变体记录类型中,或者用类似String[10]来定义;

    (2)所有变体字段共享一段内存,而共享内存的大小则由最大变体字段决定—“最长”的变量决定;

    (3)当tag存在时,它也是记录的一个字段。也可以没有tag。 

    (4)记录的变体部分的条件域必须是有序类型——Case后面跟的类型必须是Boolean,Integer等等有序类型。

    (5)记录类型中可以含有变体部分,有点象case语句,但没有最后的end,变体部分必需在记录中其他字段的声明之后。

    3.最经典的变体记录,Delphi中的TMessage结构:

    变体结构也就是变体记录, 是一个比较复杂的概念. 专家不提倡使用.

    一个最大的无符号整数(Cardinal)是 4294967295, 它的大小是 4 字节, 它的二进制表示是: 
    11111111 11111111 11111111 11111111
    它的低字节的值是 11111111, 也就是十进制的 255

    //测试:
    var
    c: Cardinal;
    begin
    c := 4294967295;
    ShowMessage(IntToStr(Lo(c))); {会显示: 255; Lo 是获取低字节值的函数}
    end;
    一个 Byte 类型的最大值是 255, 它的大小是 1 个字节, 用二进制表示是:11111111
    假如把一个 Cardinal 类型的值赋给一个 Byte 类型的值, Byte 将只取 Cardinal 的最低字节.
    //测试:
    var
    c: Cardinal;
    b: Byte;
    begin
    c := 4294967295;
    b := c;
    ShowMessage(IntToStr(b)); {255}

    c := 258; {二进制表示: 00000000 00000000 00000001 00000010}
    b := c; {b 将只获取: 00000010}
    ShowMessage(IntToStr(b)); {2}
    end;
    这是我们能否会想到, 在结构会储存时, 一个可以储存 Cardinal 的空间, 当然也能得放下一个 Byte 值;
    如果这个值非此即彼, 我们完全不需要两个储存空间.
    我猜测, 这应该是 Delphi 设计变体记录的初衷.
    //假如有这样一个员工登记表
    type
    TpersonRec = record
    ID: Integer; {员工编号}
    case Boolean of {根据分类}
    True: (A: Cardinal); {如果是股东, 登记年薪}
    False: (B: Word); {如果不是, 登记日薪}
    end;
    var
    personRec: TpersonRec;
    begin
    {先算一算这个结构的大小:
    ID 是 Integer 类型, 应该是 4 字节大小;
    A 是 Cardinal 类型, 也应该是 4 字节大小;
    B 是 Word 类型, 应该是 2 字节大小;
    合计为 10 个字节.
    }
    {可事实, TpersonRec 只有 8 个字节}
    ShowMessage(IntToStr(SizeOf(TpersonRec))); {8}
    {
    原因是: 字段 A 和 字段 B 公用了一个储存空间;
    当然这个储存空间得依着大的, 是 Cardinal 的尺寸 4 个字节.
    }
    //赋值测试:
    personRec.ID := 110;
    personRec.A := 100000; {一看就知道是个股东}
    //取值:
    ShowMessage(IntToStr(personRec.A)); {100000; 这不可能有错, 十万大洋}
    //但是:
    ShowMessage(IntToStr(personRec.B)); {34464 ?! 难道这是工人的日薪吗?}
    {
    首先, A 和 B 两个字段占用同一个空间, 给其中一个赋值, 另一个当然也就有值了;
    但因为数据类型的容量不同, 它们的值有可能是不一样的.
    在很多情况下, 我们可能根本不去理会另一个值, 但如果的确需要呢?
    看下一个例子:
    }
    end;

    type
    TpersonRec = record
    ID: Integer;
    case tag: Boolean of {在这里加了一个 tag 变量}
    True: (A: Cardinal);
    False: (B: Word);
    end;
    var
    personRec: TpersonRec;
    begin
    {我们可以用 tag 变量来区分, 记录中变体部分的值到底是谁的, 譬如:}
    personRec.ID := 110;
    personRec.tag := True;
    personRec.A := 100000; {股东的的年薪}
    personRec.ID := 111;
    personRec.tag := False;
    personRec.B := 100; {工人的日薪}
    end;

    //最经典的变体结构莫过于 Delphi 定义的 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;

    Records(记录)
    记录(类似于其它语言中的结构)表示不同种类的元素的集合,每个元素称为“字段”,声明记录类型时
    要为每个字段指定名称和类型。声明记录的语法是
    type recordTypeName = record
    fieldList1: type1;
    ...
    fieldListn: typen;
    end
    这里,recordTypeName 是一个有效标志符,每个type 表示一种类型,每个fieldList 是一个有效标志符或
    用逗号隔开的标志符序列,最后的分号是可选的。(哪个分号?是最后一个字段的,还是end 后面的?)
    比如,下面的语句声明了一个记录类型TDateRec:
    type
    TDateRec = record
    Year: Integer;
    Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
    Day: 1..31;
    end;
    TDateRec 包含3 个字段:一个整数类型的Year,一个枚举类型的Month,和另一个子界类型的Day。标
    志符Year、Month 和Day 是TDateRec 的字段,它们的行为就像变量。声明并不会为Year、Month 和Day
    分配内存,只有在实例化时才进行分配,像下面的样子:
    var Record1, Record2: TDateRec;
    上面的变量声明创建了两个TDateRec 实例,分别叫做Record1 和Record2。
    你可以用记录名作限定符、通过字段名来访问字段:
    Record1.Year := 1904;
    Record1.Month := Jun;
    Record1.Day := 16;
    或使用with 语句:
    with Record1 do
    begin
    Year := 1904;
    Month := Jun;
    Day := 16;
    end;
    现在,你可以把Record1 的值拷贝给Record2:
    Record2 := Record1;
    因为字段名的范围被限定在记录本身,你不必担心字段名和其它变量发生冲突。
    Instead of defining record types, you can use the record ... construction directly in variable declarations:
    除了定义记录类型,你也可以使用record ...构造直接声明变量:
    var S: record
    Name: string;
    Age: Integer;
    end;
    但是,这样不能让你重复使用类型声明,并且,这样声明的类型不是赋值兼容的,即使它们(记录)的
    结构完全相同。

    Variant parts in records(记录中的变体部分,变体记录)
    一个记录类型能拥有变体部分,它看起来就像case 语句,在声明中,变体部分必须跟在其它字段的后面。
    要声明一个变体记录,使用下面的语法:

    type recordTypeName = record
    fieldList1: type1;
    ...
    fieldListn: typen;
    case tag: ordinalType of
    constantList1: (Variant1);
    ...
    constantListn: (Variantn);
    end;
    声明的前面部分(直到关键字case)和标准记录类型一样,声明的其余部分(从case 到最后一个可选的
    分号,)称为变体部分,在变体部分
     tag 是可选的,它可以是任何有效标志符。如果省略了tag,也要省略它后面的冒号(:)。
     ordinalType 表示一种有序类型。
     每个constantList 表示一个ordinalType 类型的常量,或者用逗号隔开的常量序列。在所有的常量
    中,一个值不能出现多次。
     每个Variant 是一个由逗号隔开的、类似于fieldList: type 的声明列表,也就是说,Variant 有下面
    的形式:
    fieldList1: type1;
    ...
    fieldListn: typen;
    这里,每个fieldList 是一个有效标志符,或是由逗号隔开的标志符列表,每个type 表示一种类型,
    最后一个分号是可选的。这些类型不能是长字符串、动态数组、变体类型或接口(都属于动态管
    理类型),也不能是包含上述类型的结构类型,但它们可以是指向这些类型的指针。
    变体记录类型语法复杂,但语义却很简单:记录的变体部分包含几个变体类型,它们共享同一个内存区
    域。你能在任何时候,对任何一个变体类型的任何字段读取或写入,但是,当你改变了一个变体的一个
    字段,又改变了另一个变体的一个字段时,你可能覆盖了自己的数据。如果使用了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 位实数,在另一个变体类型中,第一个
    字段是32 位整数,你可以把一个值赋给实数(字段),然后再当作整数来读取它的前32 位值(比如,把
    它传给一个需要整数参数的函数)。
    ————————————————
    版权声明:本文为CSDN博主「_毛毛_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/yt_maomao/article/details/36631133

  • 相关阅读:
    杂记
    [POI2015]PUS
    CF786B Legacy(线段树优化建图)
    SP11470 TTM
    [WC2010]重建计划
    [HNOI2014]世界树
    luogu P4842 城市旅行
    [SDOI2016]征途
    [APIO2014]序列分割
    上下界网络流构图证明
  • 原文地址:https://www.cnblogs.com/QuincyYi/p/12730132.html
Copyright © 2020-2023  润新知