2017-02-08 3 views
10

SITUATIONWerden Delphi-Record-Konstruktoren wirklich benötigt?

I "Mehr Coding in Delphi" von Nick Hodges studiere, und er wird mit einem TFraction Rekordüberladen von Operatoren zu erklären. Ich habe bei mir selbst diesen Satz geschrieben:

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; 

Natürlich hatte ich einen Konstruktor zu erstellen, da in Q (rationals) Ich muss einen Nenner haben, die nicht gleich Null ist.

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; 

PROBLEM

Da die Betreiber Überlastungen eine TFraction zurückkehren, werde ich eine Operation wie folgt definieren:

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; 

Wie man hier sehen kann, ich bin Erstellen einer tmp, die von der Funktion zurückgegeben wird.

Als ich Marco Cantu Buch gelesen, benutzte er einen anderen Ansatz:

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; 

Ich habe einige Tests gemacht, und ich sehe, dass beide geben mir das richtige Ergebnis, aber es ist etwas, das ich nicht verstehen kann. Im ersten Ansatz deklariere ich tmp und dann rufe ich den Konstruktor, damit ich eine TFraction zurückgeben kann. Im zweiten Ansatz erstelle ich nichts, weil Datensätze einen automatischen Konstruktor haben. Die Dokumentation in der Tat sagt, dass:

Datensätze werden automatisch aufgebaut, unter Verwendung eines Standard-No-Argument Konstruktor, sondern Klassen explizit konstruiert werden müssen. Da Datensätze einen Standardkonstruktor ohne Argumente haben, muss jeder benutzerdefinierte Datensatzkonstruktor einen oder mehrere Parameter haben.

Hier habe ich einen benutzerdefinierten Datensatz Konstruktor. Also:

  1. Ist der Konstruktoraufruf auf tmp des ersten Ansatz nicht benötigt? Wenn ich Reduce aufrufen möchte (was eine Prozedur ist), muss ich eine Variable erstellen. Gibt die Result gerade eine Kopie von tmp zurück, ohne etwas zu erstellen?

  2. Im zweiten Ansatz, sind Result.aNumerator und Result.aDenominator die Parameter des automatisch erstellten Konstruktors?

+0

auf einer nicht verwandte Notiz, es ist in der Regel normale Praxis 'F' als Präfix für private Felder in einer Klasse und der Präfix' A' für Methodenparameter zu verwenden (die Sie verwenden). In Delphi ist es in Ordnung, aber anscheinend, in Lazarus, würde das nicht kompilieren. –

+0

Ok danke ich wusste nicht, ich verwende ein für Parameter und Variablen. Ich werde meine Gewohnheit ändern! –

+0

@Jerry Was meinst du? FPC erzwingt keine Regeln für Namenskonventionen. –

Antwort

9

Ein Datensatz Konstruktor ist nichts Magisches. Es ist nur eine Instanzmethode wie jede andere.Sie schreiben:

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

Aber man kann es ebenso wie dies auch schreiben:

tmp.Create(...); 

ich persönlich weder besonders nützlich, zu finden sein, weil ich Konstruktor bin verwendet, um Semantik für Klassen aufrufen, die und Standard Initialise zuweisen Speicher und rufen Sie dann die Konstruktormethode auf.

Und vor allem die zweite Variante rockt mit mir, denn das sieht aus wie der klassische Fehler, den Anfänger Delphi Programmierer machen, wenn Sie beginnen und versuchen, eine Instanz einer Klasse zu erstellen. Dieser Code wäre nicht gut, wenn TFraction eine Klasse wäre, aber für eine Aufzeichnung ist es in Ordnung.

Wäre es mir ich würde den Datensatzkonstruktor loswerden und stattdessen eine statische Klassenfunktion verwenden, die eine neu geprägte Instanz Ihres Datensatztyps zurückgegeben. Meine Konvention ist, solche Dinge zu benennen New. Aber das sind Angelegenheiten der persönlichen Präferenz.

Wenn ja, dass es so erklärt werden würde:

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

Es würde wie folgt umgesetzt werden:

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

Sie würden es dann rufen Sie wie folgt aus:

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

Aber wie gesagt, das ist eine Frage der Präferenz. Wenn Sie Plattenbauer mögen, können Sie gerne bei ihnen bleiben.


Sie fragen, ob Sie den Konstruktor überspringen können. In Bezug auf die Zuordnung des Datensatzes können Sie es ja überspringen. In Bezug auf das Ausführen des Codes im Konstruktor können nur Sie dies bestimmen. Soll dieser Code ausgeführt werden oder nicht?

Wenn Sie diesen Code wünschen ausgeführt werden, aber nicht wollen, eine temporäre Variable verwenden, dann können Sie den Code wie folgt schreiben:

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; 

Oder wenn Sie eine statische Klasse Funktion bevorzugt wäre es sein:

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; 
+0

Ich bevorzuge den Konstruktor Weg, weil es mir sauberer scheint. Nick macht genau das, was du mir mit der Klassenfunktion vorstellst –

+0

Auch ich wusste nichts über die Ergebnis-Sache. Wird es wie eine normale Variable behandelt? Ich sehe, dass du das Reduce drauf anrufen kannst! –

+3

IMO-Konstruktoren in Datensätzen sind (im Allgemeinen) irreführend. Es gibt keine Möglichkeit sicherzustellen, dass es jemals benutzt wird. Ein Datensatz ist ein "Wert", kein Objekt. –

Verwandte Themen