Delphi(Win32)中的相互引用记录

9
有没有什么方法可以在Delphi中创建相互引用的记录?以下是简化版本的代码:
MyRec1 = record
  arr: MyRec2Array;
end;

MyRec2 = record
  mr: MyRec1;
end;

MyRec2Array = array of MyRec2;

显然,记录类型的前向声明
MyRec2 = record;

在Delphi for Win32中不起作用。

改用类而不是记录不好,因为这会增加内存消耗和代码复杂性,所以我宁愿使用记录。

有什么建议吗?


3
可能是重复的问题:https://dev59.com/f0zSa4cB1Zd3GeqPnH0u - Sertac Akyuz
1
这没有任何意义。如果我们假设每个MyRec2Array的长度是固定且非零的,那么你正在尝试创建一个将占用无限多字节的数据结构... - Andreas Rejbrand
1
@Andreas Rejbrand - MyRec2Array 是一个动态数组。 - Alex
@Alexander:我知道。这就是为什么我写了“如果我们假设...” - Andreas Rejbrand
@Andreas Rejbrand 这个假设有什么意义?用类替换记录 - 除了编译器允许之外,没有任何变化。在实践中,许多 mr 将是 nil。 - Alex
@Alexander:嗯,就像Serg所说的那样,我认为它看起来非常奇怪。 - Andreas Rejbrand
3个回答

14

记录是值类型,而不是引用类型。这意味着作为更大数据结构的成员使用的所有记录都被内联放置在结构本身中,而不是作为指针。试图创建相互包含的两个记录会使编译器陷入无限循环,因为它试图确定记录的结构。这可能是您无法向前声明记录的原因,即使在此处尝试插入引用类型(动态数组),您仍然不能违反语言规则。

但是,您可以声明一个指向记录类型的指针作为前向声明,如下所示:

PMyRec2 = ^MyRec2
...
MyRec2 = record
  ...
end;

当然,如果你开始使用记录指针,就必须要考虑内存的分配和释放,而且代码复杂度也会出现在项目中,这正是你试图通过不使用类来避免的。底线是:用类来做这件事情。将其中一个或两个记录变成类是最简单的方法。

而且额外的内存开销是可以忽略不计的。每个引用都需要一个指向对象的指针,无论是指向记录还是类的指针,再加上每个实例之前的一个隐藏字段(4字节)在D2009之前,或者在D2009之后两个(8字节),这并不是很多。


2
+1,提到值类型的后果以及使用指针的解决方法。 - Jeroen Wiert Pluimers
指针的解决方法正是我一直在寻找的。此外,我曾认为每个TObject实例占用约30个字节。我不知道这个信息来源于何处,但在阅读了您的帖子后,我在D2007中进行了检查,结果发现它实际上只有4个字节。所以您是完全正确的。非常感谢! - Max
请注意,这里不适用值类型限制,因为dynarray已经是动态类型。这可能就是为什么“serg”的解决方法有效的原因。 - Marco van de Voort

4

我完全同意Mason的看法,但有一种方法可以绕过这个限制。基本上,您可以使用记录助手在声明MyRec2之后定义必要的功能。

type
  MyRec1 = record
    arr: array of byte;
  end;

  MyRec2 = record
    mr: MyRec1;
  end;

  MyRec1Helper = record helper for MyRec1
    procedure AllocateMyRec2(numItems: integer);
    function  GetMyRec2(i: integer): MyRec2;
    procedure SetMyRec2(i: integer; const value: MyRec2);
    property Rec2[i: integer]: MyRec2 read GetMyRec2 write SetMyRec2;
  end;

procedure MyRec1Helper.AllocateMyRec2(numItems: integer);
begin
  SetLength(arr, numItems * SizeOf(myRec2));
end;

function MyRec1Helper.GetMyRec2(i: integer): MyRec2;
begin
  Move(arr[i*SizeOf(MyRec2)], Result, SizeOf(MyRec2));
end;

procedure MyRec1Helper.SetMyRec2(i: integer; const value: MyRec2);
begin
  Move(value, arr[i*SizeOf(MyRec2)], SizeOf(MyRec2));
end;

var
  my: MyRec2;

begin
  my.mr.AllocateMyRec2(2);
  my.mr.Rec2[0].mr.AllocateMyRec2(3);
end.

4

对我来说,这个问题看起来像一个笑话 - 它不是关于相互引用的,而是关于无限类型定义循环的。至于相互引用,它们可以通过在记录内定义类型来解决(我使用了Delphi 2009):

type
  MyRec2 = record
  type
    MyRec2Array = array of MyRec2;
  type
    MyRec1 = record
      arr: MyRec2Array;
    end;
  var
    mr: MyRec1;
  end;

但是如何使用上述记录类型呢?真的很有趣。 :)
procedure TForm1.Button1Click(Sender: TObject);
var
  R: MyRec2;

begin
  SetLength(R.mr.arr, 1);
//  R.mr.arr[0]:= ???;
end;

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