Delphi - Record记录和变体记录

2023-09-21 10:16:18
//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 位值(比如,把

它传给一个需要整数参数的函数)。

更多推荐

Linux学习第16天:Linux设备树下的LED驱动开发:举一反三 专注专心专业

Linux版本号4.1.15芯片I.MX6ULL大叔学Linux品人间百味思文短情长在开题之前,先说一下这次的题目,尤其是后面的“举一反三专注专心专业”到底想给大家传递什么信息。LED驱动开发,目前为止已经学了好几种方法,包括裸机开发、嵌入式LinuxLED驱动开发以及基于API函数的LED驱动开发,再加上今天要学习的

基于Java的养老院管理系统的设计与实现(亮点:多角色、登录验证码、留言反馈)

养老院管理系统一、前言二、我的优势2.1自己的网站2.2自己的小程序(小蔡coding)2.3有保障的售后2.4福利三、开发环境与技术3.1MySQL数据库3.2Vue前端技术3.3SpringBoot框架3.4微信小程序四、功能设计4.1主要功能描述五、系统实现5.1养老院老人功能5.1.1饮食喜好5.1.2体检结果

为何学linux及用处

目前企业使用的操作系统无非就是国产类的,windows和linux类。我们要提升自己的技能,需要学习这两款。我记得在大学时期,学习过windows以及linux,但当时觉得又不常用,就学的模棱两可。毕业之后,你会发现,其实这两种操作系统是很主流的。为什么学?下面就是一些工作中遇到的例子分享一下。我记得在企业中有次遇到数

jvm深入研究文档--整体概念

阿丹:精通JVM对于一个java工程师非常重要,要是深入了解了jvm就可以有效的面对下面的问题程序调优:JVM的配置和调优对于程序的运行有着至关重要的影响。不同的业务场景需要不同的JVM配置,比如设置不同的垃圾收集器、调整新生代和老生代的内存配置和占比等。只有深入理解JVM,才能针对不同情况进行有效的调优,以满足程序高

C++面经之多态|多态的原理|虚函数

文章目录目录一、多态的概念1.概念二、多态的定义及实现1.多态的构成条件2.虚函数3.虚函数的重写虚函数重写的两个例外:4.c++11中的override和final5.重载、覆盖(重写)、隐藏(重定义)对比三、抽象类1.概念2.接口继承和实现继承四、多态的原理1.虚函数表2.多态的原理3.动态绑定与静态绑定五、单继承

Python 中对IMU进行积分得到位姿

从数据集中收集到一些IMU传感器输出的测量值和参考位姿,现需要对他们进行积分得到位姿,并与位姿真值进行对比数据读取准备的数据以txt格式保存,每行表示一组测量,其存放格式为#时间,真实位移x,真实位移x,真实位移x,真实四元数qx,真实四元数qx,真实四元数qx,真实四元数qx,测量加速度x,测量加速度y,测量加速度z

零售超市如何应对消费者需求?非常全面!

随着科技的飞速发展和消费者期望的不断演变,零售行业正经历着一场深刻的革命。传统零售模式逐渐被新零售模式所取代,而其中一个备受关注的元素是自动售货机。自动售货机不仅在商场、车站和办公楼等高流量地点迅速扩张,还在重新定义我们如何购物、何时购物以及购物的方式。客户案例哈尔滨零售超市多年来一直面临着竞争激烈的零售市场和日益挑战

实用的嵌入式编码技巧:第三部分

每个触发器都有两个我们在风险方面违反的关键规格。“建立时间”是时钟到来之前输入数据必须稳定的最小纳秒数。“保持时间”告诉我们在时钟转换后保持数据存在多长时间。这些规格因逻辑设备而异。有些可能需要数十纳秒的设置和/或保持时间;其他人则需要少一个数量级。图9.1:建立和保持时间如果我们倾向于编织,我们将尊重这些参数,并且触

《思维与智慧》简介及投稿邮箱

《思维与智慧》自1982年创刊,经国家新闻出版署批准,由河北省教育厅主管,河北行知文化传媒有限责任公司主办的益智励志类大众文化期刊。《思维与智慧》办刊宗旨是:“开发思维,启迪智慧,滋润心灵”,以通俗形式、简明语言介绍创新思维学的基本知识,展示创新思维的哲理性、深刻性,但力戒抽象议论,而应深入浅出,夹叙夹议,寓理于事。主

Nginx之会话管理解读

目录session概念问题引进Nginx_hash高级负载均衡ip_hashhash$cookie_xxxhash$request_uri补充知识点:服务器扩容session概念Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失

我的安卓AOSP开发使用到的教程汇总【安卓12】

目录投屏软件日志打印脚本bat【gpt生成的】摄像头定位静默安装APP系统签名多线程使用APK打包APKOTA差分包制作服务和主线程通信代码注释模板阿里云ClassAOSP教程添加默认APN关闭双击电源键打开相机ubuntu安装遇到的bug投屏软件scrcpy【设置为系统变量后可以投屏的情况下使用adb并且查看log】

热文推荐