Delphi中的记录

16

关于 Delphi 中记录的一些问题:

  1. 既然记录(records)几乎就像类(classes),为什么不仅使用类而不是记录呢?
  2. 从理论上讲,内存在变量声明时被分配给记录,但是内存是如何在之后释放的呢?
  3. 我可以理解将指针用于记录到列表对象中的实用性,但是通过泛型容器 (TList<T>),是否还需要使用指针?如果不需要,如何删除/释放泛型容器中的每个记录?如果我想要删除泛型容器中的特定记录,该怎么办?
5个回答

21

记录与类之间有很多区别;并且没有"指向记录的指针" <> "类"。每种方式都有其优缺点;软件开发中重要的一点是了解这些差异,以便更轻松地选择最适合特定情况的方法。

  1. 这个问题基于错误的前提。记录并不像类那样相似,就像整数并不像双精度浮点数那样相似。
    • 类必须始终动态实例化,而这对于记录来说可能是一个选择,但并非必需品。
    • 类的实例(我们称之为对象)总是通过引用传递,这意味着多个代码段将共享并操作同一实例。这是需要记住的重要事情,因为您可能会无意中修改对象作为副作用;虽然在有意时它是一个强大的功能。另一方面,记录是按值传递的;您需要明确指示是否通过引用传递它们。
    • 类不像记录那样“容易复制”。当我说复制时,我的意思是一个单独的实例复制源。(鉴于上面关于值/引用的评论,这应该是显而易见的)。
    • 记录倾向于与类型文件非常配合使用(因为它们非常容易复制)。
    • 记录可以覆盖其他字段的字段(case x of / unions)
    • 这是关于记录某些情境优势的评论;相反,对于类也有情境优势,我不会详细阐述。
  2. 也许理解这一点最简单的方法是要有点学究。让我们澄清一下;内存并没有在“声明时”分配,它在变量在作用域内时分配,在作用域外时释放。因此,对于局部变量,它会在例程开始之前分配,并在结束之后立即释放。对于类字段,它在对象创建时分配,并在销毁时释放。
  3. 同样,存在优缺点...
    • 复制整个记录(如泛型)可能比仅复制引用更慢且需要更多内存。
    • 通过引用(使用指针)传递记录是一种强大的技术,可以轻松地使其他内容修改您的记录副本。如果没有这个,您必须通过值(即复制它)传递您的记录,在结果中接收更改后的记录,再将其复制到您自己的结构中。
  4. 记录的指针像类吗?不,完全不是。只有两个区别:
    • 类支持多态继承。
    • 类可以实现接口。

感谢Marco添加关于变体记录(使用case x of ...)的注释。 - Disillusioned
配菜:FastMM 为您的程序预分配了一些内存。如果您创建了一堆小记录/对象/任何东西,那么您的程序很可能不会从操作系统请求内存。因此,我们可以说对于小结构(变量、字符串、记录、基本对象),无论将结构放在堆栈上还是堆上,都没有显着的速度差异。 - Gabriel

12

对于问题1和2:记录是值类型,而类是引用类型。它们分配在堆栈上或直接在包含它们的任何较大变量的内存空间中,而不是通过指针,并且当它们超出范围时,编译器会自动清除它们。

至于第三个问题,TList<TMyRecord> 内部声明了一个 array of TMyRecord 以存储空间。当列表被销毁时,其中所有的记录都会被清除。如果您想删除特定的记录,请使用 Delete 方法按索引删除,或使用 Remove 方法查找并删除。但请注意,由于它是值类型,您所做的一切都将是复制记录,而不是复制对其的引用。


感谢Wheeler先生,还有一个问题: 指向记录的指针=类? - Billiardo Aragorn
1
不,指向记录的指针=指向记录的指针。对象(类实例)在几个方面与记录不同。 - Mason Wheeler

9
记录的主要好处之一是,当您拥有一个大的“记录数组”时。这是通过为所有记录在一个连续的RAM空间中分配空间来在内存中创建的,这非常快速。如果您使用了“TClass数组”,则数组中的每个对象都必须单独分配,这会很慢。
为了提高字符串和对象的速度,已经进行了大量的工作来改善分配内存的速度,但它永远不会像用1个内存分配替换100,000个内存分配那样快。
然而,如果您使用记录数组,请不要在本地变量中复制记录。那可能很容易消耗速度优势。

请接受我对这个块分配的抱怨。确实,在一个连续的RAM空间中为所有记录分配空间会非常快,然而,如果您的数组很大,您的程序(特别是在32位编译器下)可能会因为找不到足够大的连续块来适应数组而耗尽内存,即使有足够的内存可用。另一方面,如果您使用对象数组,则对象将分散在RAM中,这样就会减少碎片化问题。 - Gabriel
我们的一个使用案例是:在图表模块中,对屏幕上每个像素水平做什么的数组。目前这个数组限制在大约8000个像素,即8000个数组索引,算法会多次遍历它。这可以在几毫秒内分配、处理和释放,非常快速。你不能用类以同样的速度实现。它分配少于1MB的RAM。 - Lars D

4

1) 为了实现继承和多态,类具有一些开销。记录不允许它们,并且在某些情况下可能更快且更简单易用。与始终分配在堆中并通过引用进行管理的类不同,记录也可以分配在栈上,直接访问,并互相赋值而无需调用“Assign”方法。此外,记录对于访问具有给定结构的内存块非常有用,因为它们的内存布局恰好是您定义的方式。类实例的内存布局由编译器控制,并具有使对象工作所需的附加数据(即指向虚拟方法表的指针)。

2) 除非您动态分配记录(使用New()或GetMem()),否则记录的内存由编译器作为序数、浮点数或静态数组来管理:全局变量内存在启动时分配并在程序终止时释放,而局部变量在进入函数/过程/方法时分配在堆栈上并在退出时释放。在堆栈上分配/释放内存更快,因为它不需要调用内存管理器,只需要很少的汇编指令来更改堆栈寄存器。但请注意,在堆栈上分配大型结构可能会导致堆栈溢出,因为最大堆栈大小是固定的且不是很大(请参见链接器选项)。如果记录是类的字段,则在创建类时分配它们,并在释放类时释放。

3) 泛型的优点之一是消除了低级指针管理的需要 - 但请注意其内部运作。


在堆栈中分配/释放内存更快,因为它不需要调用内存管理器。但是,我们不要忘记FastMM为您的程序预分配了一些内存。如果您创建了一堆小记录/对象/任何东西,非常有可能您的程序不会从操作系统请求内存。因此,我们可以说对于小结构(字符串、记录、基本对象),无论结构体放在堆还是栈中,都没有显着的区别。 - Gabriel
在堆栈中分配/释放内存更快。提示:由于FastMM预先分配内存,因此在堆中创建小记录/变量与在堆栈上创建它们的速度差别不大。这也导致了一些浪费:如果您声明一个1字节(布尔)全局变量,则会为其分配32个字节。https://ebookreading.net/view/book/EB9781788625456_75.html - Gabriel

3

类和记录之间还有一些区别。 类可以使用多态,并公开接口。 记录不能实现析构函数(尽管自Delphi 2006以来,它们现在可以实现构造函数和方法)。

记录非常有用,可以将内存分段为更逻辑的结构,因为记录中第一个数据项位于指向记录本身的指针的相同地址点。 对于类来说,情况并非如此。


此外,记录支持运算符重载,在某些情况下非常有用。 - Incredulous Monk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接