Нужны ли конструкторы записи Delphi?
СИТУАЦИЯ
Я изучаю "Больше кодирования в 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;
ПРОБЛЕМА
Так как оператор перегружает return 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
, который возвращается из функции.
Когда я прочитал книгу Марко Канту, он использовал другой подход:
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
. Во втором подходе я вместо этого ничего не создаю, потому что записи имеют автоматический конструктор. Фактически, документация гласит, что:
Записи создаются автоматически, используя аргумент no-argument по умолчанию конструктор, но классы должны быть явно построены. Потому как записи имеют конструктор без аргументов по умолчанию, любые пользовательские конструктор записи должен иметь один или несколько параметров.
Здесь у меня есть пользовательский конструктор записи. Итак:
-
Является ли вызов конструктора на tmp
первого подхода не нужен? Если я хочу вызвать Reduce
(это процедура), мне нужно создать переменную. Является ли Result
просто возвращением копии tmp
без создания чего-либо?
-
Во втором подходе есть Result.aNumerator
и Result.aDenominator
параметры автоматического созданного конструктора?
Ответы
Ответ 1
Конструктор записи не является чем-то волшебным. Это просто метод экземпляра, как любой другой. Вы пишете:
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;