问题 Delphi是否真的需要记录构造函数?


情况

我正在研究Nick Hodges的“Delphi中的更多编码”,他正在使用 TFraction 记录解释运算符重载。我自己写了这样的记录:

type
  TFraction = record
  strict private
    aNumerator: integer;
    aDenominator: integer;
    function GCD(a, b: integer): integer;
  public
    constructor Create(aNumerator: integer; aDenominator: integer);
    procedure Reduce;
    class operator Add(fraction1, fraction2: TFraction): TFraction;
    class operator Subtract(fraction1, fraction2: TFraction): TFraction;
    //... implicit, explicit, multiply...
    property Numerator: integer read aNumerator;
    property Denominator: integer read aDenominator;
  end;

当然,我必须创建一个构造函数,因为在Q(有理数)中我必须有一个不等于零的分母。

constructor TFraction.Create(aNumerator, aDenominator: integer);
begin
  if (aDenominator = 0) then
  begin
    raise Exception.Create('Denominator cannot be zero in rationals!');
  end;

  if ( (aNumerator < 0) or (aDenominator < 0) ) then
  begin
    Self.aNumerator := -aNumerator;
    Self.aDenominator := -aDenominator;
  end
  else
  begin
    Self.aNumerator := aNumerator;
    Self.aDenominator := aDenominator;
  end;
end;

问题

由于操作员超载返回a TFraction,我将定义一个这样的操作:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
var
  tmp: TFraction;
begin
  //simple algorithm of the sum
  tmp := TFraction.Create(fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator, fraction1.Denominator*fraction2.Denominator);
  tmp.Reduce;

  //return the result
  Result := tmp;
end;

正如你在这里看到的,我正在创造一个 tmp 从函数返回的。

当我阅读Marco Cantu的书时,他使用了另一种方法:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
  Result.aNumerator := (fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator);
  Result.aDenominator := fraction1.Denominator*fraction2.Denominator;
end;

我已经做了一些测试,我发现两者都给了我正确的结果,但有一些我无法理解的东西。在第一种方法中,我宣布 TMP 然后我调用构造函数,以便我可以返回一个 TFraction。在第二种方法中,我没有创建任何东西,因为记录具有自动构造函数。事实上,文档说:

使用默认的无参数自动构建记录   构造函数,但必须显式构造类。因为   记录有一个默认的无参数构造函数,任何用户定义的   记录构造函数必须具有一个或多个参数。

这里我有一个用户定义的记录构造函数。所以:

  1. 是构造函数调用 tmp 第一种方法不需要?如果我想打电话 Reduce (这是一个程序),我需要创建一个变量。是个 Result 只是返回一份副本 tmp 没有创造什么?

  2. 在第二种方法中,是 Result.aNumerator 和 Result.aDenominator 自动创建的构造函数的参数?


2055
2018-02-08 17:27


起源

在一个不相关的说明中,通常的做法是使用 F 作为类中私有字段的前缀,以及前缀 A 对于方法参数(您使用)。在Delphi中,它很好,但显然,在Lazarus中,它无法编译。 - Jerry Dodge
好的,谢谢,我不知道,我使用的是参数和变量。我会改变我的习惯! - Alberto Miola
@Jerry你是什么意思。 FPC不强制执行命名约定规则。 - David Heffernan
@大卫 stackoverflow.com/questions/42073767/... - Jerry Dodge
@jerry我不确定我是否相信。我怀疑雷米的评论有正确的信息。 - David Heffernan


答案:


记录构造函数不是什么神奇的东西。它只是一个像其他任何实例的方法。你写:

tmp := TFraction.Create(...);

但你也可以这样写:

tmp.Create(...);

我个人认为两者都没有特别有用,因为我习惯于为分配和默认初始化内存的类构造函数调用语义,然后调用构造函数方法。

尤其是第二个版本与我一起激动,因为这看起来像新手Delphi程序员在开始并尝试创建类的实例时所犯的经典错误。如果这个代码不好 TFraction 是一个班级,但记录是好的。

如果是我,我将摆脱记录构造函数,而是使用一个静态类函数返回一个新的记录类型的实例。我的惯例是命名这样的东西 New。但这些都是个人偏好的问题。

如果你这样做,它将被声明如下:

class function New(aNumerator, aDenominator: Integer): TFraction; static;

它会像这样实现:

class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
  Result.aNumerator := ...;
  Result.aDenominator := ...;
end;

然后你会这样称呼它:

frac := TFraction.New(num, denom);

但正如我所说,这是一个偏好问题。如果您喜欢记录构造函数,请随时坚持使用它们。


你问是否可以跳过构造函数。在记录分配方面,是的,你可以跳过它。在构造函数中运行代码方面,只有您可以确定。你想要那个代码执行吗?

如果您希望执行该代码,但又不想使用临时变量,那么您可以编写如下代码:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
  Result.Create(
    fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
    fraction1.Denominator*fraction2.Denominator
  );
  Result.Reduce;
end;

或者,如果您更喜欢静态类函数,它将是:

class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
  Result := TFraction.New(
    fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
    fraction1.Denominator*fraction2.Denominator
  );
  Result.Reduce;
end;

12
2018-02-08 17:38



我更喜欢构造方式,因为它对我来说似乎更清晰。尼克正在按照班级功能向你推荐 - Alberto Miola
我也不知道结果的事情。它被视为正常变量吗?我看到你可以调用Reduce了! - Alberto Miola
记录中的IMO构造函数具有误导性(一般而言)。没有办法确保它会被使用。记录是“值”,而不是对象。 - Etsitpab Nioliv
我在那章帮助了一下。您似乎知道结果是什么,因为在您的代码中使用它。分配给它时。它只是一个隐含的var param,它保存了返回值。 - David Heffernan
至于使用记录构造函数,不要抱怨你感到困惑并调用obj.Create obj是一个类引用!我记得在他写这一章时与尼克讨论这件事。 - David Heffernan