Lazarus / Delphi - Create vs TSomeClass.Create inside constructor - 为什么这会造成麻烦?

Lazarus / Delphi - Create vs TSomeClass.Create inside constructor - why is this causing trouble?

突出显示

这个构造函数:

constructor TCoords.Create(const AX, AY: Integer);
begin
    TCoords.Create(Point(AX, AY));
end;

已确认 Linux Lazarus 2 和 Windows Delphi XE6 均出现故障。

这可能是一个错误吗?


我是 Lazarus 中的 OOP 新手/Delphi,请原谅新手可能犯的错误。谢谢。

我似乎无法理解为什么以下序列的 Lazarus / (Delphi-like) 自定义,非常基本,一个对象将不起作用。我已经尝试调试了几个小时,从那时起我发现:

什么有效:

调用一个不带参数的构造函数,并直接调用带 TPoint 参数的构造函数。

什么没有:

调用这个:

constructor Create(const AX, AY: Integer);

但是,我发现这会起作用 - 但是 仅当 在构造函数中没有其 class 名称的情况下调用。为什么会引起麻烦?


声明

// WORKS - this creates instance of TCoords initialized to PointOutOfReach
constructor Create; reintroduce;
// WORKS - this creates instance of TCoords initialized to user coordinates
constructor Create(const ACoords: TPoint);
// DOES NOT WORK, strangely returns Point(0, 0), if called with class name
// WORKS only if called without class name - confusing or error on my side?
constructor Create(const AX, AY: Integer);

来电

// OK - WORKING
NewCoords := TCoords.Create;
NewCoords.X:=12;
NewCoords.Y:=120;
ShowMessage(NewCoords.X.ToString + ' : ' + NewCoords.Y.ToString);
NewCoords.Free;

// OK - WORKING
NewCoords := TCoords.Create(Point(12, 120));
ShowMessage(NewCoords.X.ToString + ' : ' + NewCoords.Y.ToString);
NewCoords.Free;

// NOT WORKING as expected
NewCoords := TCoords.Create(12, 120);
ShowMessage(NewCoords.X.ToString + ' : ' + NewCoords.Y.ToString);
NewCoords.Free;

具有 TCoords 对象定义的坐标单位

unit Coords;

{$mode objfpc}{$H+}

interface

uses
    Classes;

type
    // Flexible X,Y coordinates object.
    TCoords = class(TObject)

    // these declarations are accessible within this unit only
    private
        // this is the variable we are working with
        FCoords: TPoint;
        // property for this function is unnecessary, but I like it as it is
        function IsInitialized: Boolean;

    // these declarations are accessible to all
    public
        // this creates instance of TCoords initialized to PointOutOfReach
        constructor Create; reintroduce;
        // this creates instance of TCoords initialized to user coordinates
        constructor Create(const ACoords: TPoint);

        // THIS ONE DOES NOT WORK, strangely returns Point(0, 0)
        constructor Create(const AX, AY: Integer);

        // this indicates if instance was initialized or not by the user
        property Initialized: Boolean read IsInitialized;
        // this works directly with private FCoords variable storing coordinates
        property P: TPoint read FCoords write FCoords;
        // these two are shortcuts for X,Y coordinates' direct access
        property X: Integer read FCoords.X write FCoords.X;
        property Y: Integer read FCoords.Y write FCoords.Y;

    end;

implementation

var
  // this gets initialized when loading this unit
  PointOutOfReach: TPoint;

constructor TCoords.Create;
begin
    // this is the same as `inherited`, but I like to be explicit
    inherited Create;
    // since called without argument, we have to ensure, there is some nonsense
    FCoords := PointOutOfReach;
end;

constructor TCoords.Create(const ACoords: TPoint);
begin
    // this is the same as `Create`, but I like to be explicit
    TCoords.Create;
    // in the previous mandatory call we have initialized FCoords already
    // but to PointOutOfReach; here we overwrite it with user coordinates
    FCoords := ACoords;
end;

constructor TCoords.Create(const AX, AY: Integer);
begin
    // this is the same as `Create(TPoint)`, but I like to be explicit
//    TCoords.Create(Point(AX, AY));

    // Why can't I call the above, shouldn't it be the very same?
    Create(Point(AX, AY));
end;

function TCoords.IsInitialized: Boolean;
begin
    // this returns True in case FCoords has been initialized
    // initialized means here for the FCoords point to be different from PointOutOfReach
    // achieved either by calling `Create(APoint)`, or later overwriting PointOutOfReach
    Result := FCoords <> PointOutOfReach;
end;

initialization

    // initialize PointOutOfReach to "impossible" coordinates when loading unit
    PointOutOfReach := Point(MAXINT, MAXINT);

end.

提前谢谢你,我自己似乎看不出这两者之间的区别。


更正了主要错误 - 没有变化

构造函数声明中缺失的 overload; 已更正。遗憾的是,仍然从最后一个构造函数中获得 0,0 坐标。

这是您声明属性的方式 X 并且 Y 将它们更改为此

property X: Integer read FCoords.X write SetCoordX;
property Y: Integer read FCoords.Y write SetCoordY;

procedure TCoords.SetCoordX(const Value: Integer);
begin
  FCoords.X := Value;
end;

procedure TCoords.SetCoordY(const Value: Integer);
begin
  FCoords.Y := Value;
end;

这与 Delphi 如何分配属性有关。在您的情况下,您正在分配给编译器在检索 X.

的值时添加的隐式变量

我似乎不记得我是在哪里读到的,我会找到它并编辑我的答案

David Heffernan在评论中说明了我的方法不起作用的原因,让我引用:

TCoords.Create(Point(AX, AY)) creates a new instance. When you do this inside a constructor you now have two instances. Replace it with Create(Point(AX, AY)).

谢谢您的解释!


即使解决了,我认为更好的方法是 不要链接那些 constructor


应用此规则有效,使用未链接的构造函数的工作片段:

constructor TCoords.Create;
begin
    inherited Create;
    // since called without argument, we have to ensure,
    // there are some corner-case coordinates, so that we can
    // differentiate between a [0:0] and uninitialized state
    FCoords := PointOutOfReach;
end;

constructor TCoords.Create(const ACoords: TPoint);
begin
    inherited Create;
    FCoords := ACoords;
end;

constructor TCoords.Create(const AX, AY: Integer);
begin
    inherited Create;
    FCoords := Point(AX, AY);
end;

PS:为了让代码正常工作,到目前为止我认为没有必要像 Nasreddine Galfout's .

那样应用 "setters"