有件事我想了很久:为什么 Delphi 记录不能继承(以及所有其他重要的 OOP 功能)?

这本质上会使记录成为类的堆栈分配版本,就像 C++ 类一样,并且会呈现“对象”(注意:不是实例)已过时。我不认为它有什么问题。这也是实施记录前向声明的好机会(我仍然对为什么它仍然缺失感到困惑)。

您认为这有什么问题吗?

有帮助吗?

解决方案

有关这个问题,有两种继承:接口继承和实现继承

接口继承通常意味着多态性。这意味着,如果B从A衍生,则B类型的值可以被存储在类型A的位置这是值类型(如记录),而不是因为切片以引用类型,有问题的。如果B比A大,然后将其存储在类型A的位置将截断值 - 的任何字段,在其定义中加入之上那些A的将会丢失乙

实施继承是从这个角度看问题较少。如果德尔福有记录的继承,但只有执行,而不是接口,事情就不会太差。唯一的问题是,只是让A型的值B类型的字段也大部分你想出来实现继承的东西。

另一个问题是虚拟方法。虚拟方法分派需要某种每值标签的指示值的运行时类型,以便正确重写的方法可以被发现。但记录没有任何地方来存储此类型:记录的领域是它拥有的所有领域。对象(旧Turbo Pascal的那种)可以有虚拟方法,因为它们具有VMT:在层次结构中的第一个对象定义一个虚拟方法隐含地增加了一个VMT到对象定义的末尾,它生长。但是,Turbo Pascal的对象具有上述相同切片的问题,这使得他们的问题。上值类型的虚拟方法有效地需要接口继承,这意味着切片问题。

因此,为了正确地支持记录接口继承正确,我们需要某种形式的解决切断问题。拳击是一种解决方案,但它通常需要垃圾回收是可用的,并且它会引入到歧义的语言,在这里不管你是用一个值或参考工作也未必清楚 - 有点像整数VS诠释在Java中使用自动装箱。至少在Java中没有用于装箱拆箱VS值类型的“种”不同的名字。另一种方式做拳击是像谷歌配合它的接口,这是一种接口继承的不实现继承,但需要的接口来单独定义,并且所有接口位置是引用。当通过接口参考称为值类型(例如记录)被装箱。当然,围棋也有垃圾收集。

其他提示

记录和类/对象是在Delphi两个非常不同的事情。基本上一个Delphi记录是一个C结构 - 的Delphi甚至支持的语法,以做的事情一样具有可以作为16位4点的整数或2个32位整数被访问的记录。像structrecord可以追溯到以前的面向对象编程输入的语言(帕斯卡时代)。

像一个结构的记录也是存储器内嵌块,而不是一个指针的存储器的块。这意味着,当你传递一个记录到一个函数,你传递一个副本,而不是一个指针/引用。这也意味着,当你声明代码中的记录类型变量,它是在编译时它是多么大的决心 - 在一个函数中使用记录类型的变量将堆栈(而不是在堆栈上的指针,而是在分配4,10,16,等等字节结构)。此固定大小不与多态性打好。

我看到的唯一问题(可与我和近视或错误)是目的。记录是用于存储数据的同时,对象是用于操纵和使用所述数据。为什么一个储物柜需要处理例程?

您说得对,增加继承来的记录将基本上把他们变成C ++类。这就是你的答案就在那里:它没有这样做,因为这将是做了可怕的事情。你可以有栈上分配的值类型,或者你可以有类和对象,但两者搅拌是一个非常糟糕的主意。一旦你这样做,你最终与各种生命周期管理问题,最终不得不建立丑陋的黑客像C ++的RAII模式进入语言,以应付他们。

底线:如果希望可以继承和扩展,使用类的数据类型。那他们是怎么在那里。

编辑:在响应于云的问题,这是不是真的东西,可以通过一个单一的简单的例子来说明。整个C ++对象模型是一种灾难。它可能看起来不像一个近距离;你要明白几个问题相互关联,以真正掌握大局。 RAII只是在金字塔顶端的混乱。也许我会在本周晚些时候写在我的博客更详细的解释,如果我有时间。

由于记录没有VMT(虚拟方法表)。

你可以尝试使用Delphi 目的 的关键字。这些基本上是可继承的,但其行为更像记录而不是类。

看到这个 线 和这个 描述.

在过去的时代我已经使用对象(而不是类!)与继承的记录。

不像在说什么有些人在这里有这个正当理由。我做的情况下,它涉及两种结构从外部源(API,没有什么关闭磁盘 - 我需要在存储器中的完全形成的记录)。,其中第二个仅延伸所述第一

这种情况下,是非常罕见的,虽然。

这是对主题您的问题,并涉及通过类和记录助手延伸记录和类类型的功能。据Embarcadero公司对这个文档,你可以扩展一个类或记录(但没有操作符重载由助手支持)。因此,基本上可以在成员方法,但没有数据成员)方面扩展功能。它们所支持的,你可以通过getter和setter访问通常的方式,虽然我没有测试过这个类的字段。如果你想接口来访问类的数据或记录你加入帮手,你也许可以做到这一点(即触发事件或者在原班或记录的数据成员被改变的信号)。你无法实现数据隐藏,虽然,但它允许您覆盖原始类的现有成员函数。

例如。此示例适用在Delphi XE4。创建一个新的VCL窗体应用程序,并从1单元用下面的代码替换代码:

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Types;

type

  TMyArray2D = array [0..1] of single;

  TMyVector2D = record
  public
    function Len: single;
    case Integer of
      0: (P: TMyArray2D);
      1: (X: single;
          Y: single;);
  end;

  TMyHelper = record helper for TMyVector2D
    function Len: single;
  end;


  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


implementation

function TMyVector2D.Len: Single;
begin
  Result := X + Y;
end;

function TMyHelper.Len: single;
begin
  Result := Sqrt(Sqr(X) + Sqr(Y));
end;

procedure TestHelper;
var
  Vec: TMyVector2D;
begin
  Vec.X := 5;
  Vec.Y := 6;
  ShowMessage(Format('The Length of Vec is %2.4f',[Vec.Len]));
end;

procedure TForm1.Form1Create(Sender: TObject);
begin
  TestHelper;
end;

注意,结果为7.8102,而不是11。这表明,可以隐藏原始类或记录与类或记录辅助的构件的方法。

因此,在某种程度上你会只把访问原始数据成员一样的,你在与其中的一类是通过属性而不是字段改变直接所以适当的动作中声明的单元内改变的值将采取由该数据的getter和setter。

感谢您询问的问题。当然,我学到了很多东西,试图找到答案,它帮了我很大的了。

布赖恩约瑟夫约翰斯

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top