Проблемы, возвращающие self как результат функции
у меня есть очень простое определение класса для 3D-векторов, TVector3D
и несколько методов, используемых для реализации . Если я пройду Normalise
функция вектор, который уже нормализован, я хочу, чтобы он вернул вектор, который я его передал. Здесь я использовал Result := Self
но у меня есть некоторые сумасшедшие возвращения.
консольное приложение:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TVector3D = Class
public
x : Single;
y : Single;
z : Single;
constructor Create(x : Single;
y : Single;
z : Single);
function GetMagnitude() : Single;
function IsUnitVector() : Boolean;
function Normalise() : TVector3D;
end;
constructor TVector3D.Create(x : Single;
y : Single;
z : Single);
begin
Self.x := x;
Self.y := y;
Self.z := z;
end;
function TVector3D.GetMagnitude;
begin
Result := Sqrt(Sqr(Self.x) + Sqr(Self.y) + Sqr(Self.z));
end;
function TVector3D.IsUnitVector;
begin
if Self.GetMagnitude = 1 then
Result := True
else
Result := False;
end;
function TVector3D.Normalise;
var
x : Single;
y : Single;
z : Single;
MagnitudeFactor : Single;
begin
if IsUnitVector then
Result := Self
else
MagnitudeFactor := 1/(Self.GetMagnitude);
x := Self.x*MagnitudeFactor;
y := Self.y*MagnitudeFactor;
z := Self.z*MagnitudeFactor;
Result := TVector3D.Create(x,
y,
z);
end;
procedure TestNormalise;
var
nonUnitVector : TVector3D;
unitVector : TVector3D;
nUVNormed : TVector3D;
uVNormed : TVector3D;
begin
//Setup Vectors for Test
nonUnitVector := TVector3D.Create(1,
1,
1);
unitVector := TVector3D.Create(1,
0,
0);
//Normalise Vectors & Free Memory
nUVNormed := nonUnitVector.Normalise;
nonUnitVector.Free;
uVNormed := unitVector.Normalise;
unitVector.Free;
//Print Output & Free Memory
WriteLn('nUVNormed = (' + FloatToStr(nUVNormed.x) + ', ' + FloatToStr(nUVNormed.y) + ', ' + FloatToStr(nUVNormed.z) + ')');
nUVNormed.Free;
WriteLn('uVNormed = (' + FloatToStr(uVNormed.x) + ', ' + FloatToStr(uVNormed.y) + ', ' + FloatToStr(uVNormed.z) + ')');
uVNormed.Free;
end;
begin
try
TestNormalise;
Sleep(10000);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Normalise
работает нормально для не-устройство vecors, т. е. IsUnitVector
возвращает false. Но для единичных векторов, таких как (1,0,0)
, вместо того, чтобы возвращаться, я получаю результат с очень низкими ненулевыми числами, где раньше было ненулевое значение, например (8.47122...E-38,0,0)
.
если я запускаю это через отладчик с точкой останова в строке Result := Self
установить для оценки себя, Self is (1,0,0)
пока результат становится (Very Low Number,0,0)
. Где Very Low Number
изменения каждый раз, когда я запускаю программу, но всегда кажется, чтобы быть вокруг!--13-->.
я не понимаю, почему это происходит. Почему это происходит и как лучше всего изменить мой чтобы избежать этого.
3 ответов
текущего TVector3D.Normalise
реализация имеет несколько вопросов:
- последние 4 строчки всегда выполнена, потому что вы не использовали
begin-end
блок послеelse
, - таким образом, рутина никогда не возвращается
Self
, но всегда новый экземпляр, - память возвращаемого экземпляра вероятно просочилась, потому что вы потеряли право собственности на него после вызова функции,
- , когда
IsUnitVector
возвращаетTrue
, затем назначениеMagnitudeFactor
будет пропущено, и это будет случайное значение (в настоящее время присутствует в адресе этой памяти), что объясняет, почему вы получаете rubish. Компилятор также предупреждает вас об этом:переменнаяMagnitudeFactor
возможно, не были инициализированы.
вместо этого я бы переписал процедуру следующим образом:
function TVector3D.Normalise: TVector3D;
begin
if not IsUnitVector then
begin
x := x / GetMagnitude;
y := y / GetMagnitude;
z := z / GetMagnitude;
end;
Result := Self;
end;
корень всех ваших проблем заключается в том, что вы используете класс, который является ссылочным типом. Вместо этого вам нужно сделать свой вектор типом значения. Это означает использовать record
.
в вашем коде, даже когда вы исправляете проблему, идентифицированную @NGLN, вы все равно уничтожили все экземпляры своего класса к моменту начала вызова WriteLn
.
если вы не поймете эту проблему в ближайшее время, я боюсь, что у вас будут проблемы. Переключение на использование типа value сделает ваше кодирование тривиально легко по сравнению с вашим текущим подходом.
вот что вам нужно для начала:
type
TVector3 = record
public
class operator Negative(const V: TVector3): TVector3;
class operator Equal(const V1, V2: TVector3): Boolean;
class operator NotEqual(const V1, V2: TVector3): Boolean;
class operator Add(const V1, V2: TVector3): TVector3;
class operator Subtract(const V1, V2: TVector3): TVector3;
class operator Multiply(const V: TVector3; const D: Double): TVector3;
class operator Multiply(const D: Double; const V: TVector3): TVector3;
class operator Divide(const V: TVector3; const D: Double): TVector3;
class function New(const X, Y, Z: Double): TVector3; static;
function IsZero: Boolean;
function IsNonZero: Boolean;
function IsUnit: Boolean;
function Mag: Double;
function SqrMag: Double;
function Normalised: TVector3;
function ToString: string;
public
X, Y, Z: Double;
end;
const
ZeroVector3: TVector3=();
class operator TVector3.Negative(const V: TVector3): TVector3;
begin
Result.X := -V.X;
Result.Y := -V.Y;
Result.Z := -V.Z;
end;
class operator TVector3.Equal(const V1, V2: TVector3): Boolean;
begin
Result := (V1.X=V2.X) and (V1.Y=V2.Y) and (V1.Z=V2.Z);
end;
class operator TVector3.NotEqual(const V1, V2: TVector3): Boolean;
begin
Result := not (V1=V2);
end;
class operator TVector3.Add(const V1, V2: TVector3): TVector3;
begin
Result.X := V1.X + V2.X;
Result.Y := V1.Y + V2.Y;
Result.Z := V1.Z + V2.Z;
end;
class operator TVector3.Subtract(const V1, V2: TVector3): TVector3;
begin
Result.X := V1.X - V2.X;
Result.Y := V1.Y - V2.Y;
Result.Z := V1.Z - V2.Z;
end;
class operator TVector3.Multiply(const V: TVector3; const D: Double): TVector3;
begin
Result.X := D*V.X;
Result.Y := D*V.Y;
Result.Z := D*V.Z;
end;
class operator TVector3.Multiply(const D: Double; const V: TVector3): TVector3;
begin
Result.X := D*V.X;
Result.Y := D*V.Y;
Result.Z := D*V.Z;
end;
class operator TVector3.Divide(const V: TVector3; const D: Double): TVector3;
begin
Result := (1.0/D)*V;
end;
class function TVector3.New(const X, Y, Z: Double): TVector3;
begin
Result.X := X;
Result.Y := Y;
Result.Z := Z;
end;
function TVector3.IsZero: Boolean;
begin
Result := Self=ZeroVector3;
end;
function TVector3.IsNonZero: Boolean;
begin
Result := Self<>ZeroVector3;
end;
function TVector3.IsUnit: Boolean;
begin
Result := abs(1.0-Mag)<1.0e-5;
end;
function TVector3.Mag: Double;
begin
Result := Sqrt(X*X + Y*Y + Z*Z);
end;
function TVector3.SqrMag: Double;
begin
Result := X*X + Y*Y + Z*Z;
end;
function TVector3.Normalised;
begin
Result := Self/Mag;
end;
function TVector3.ToString: string;
begin
Result := Format('(%g, %g, %g)', [X, Y, Z]);
end;
это извлечено из моей собственной кодовой базы. Я использую Double
, но если вы действительно предпочитаете использовать Single
, то вы можете легко изменить его.
использование перегрузки операторов делает код гораздо более читаемым. Теперь вы можете написать V3 := V1 + V2
и так далее.
вот как выглядит ваш тестовый код с этим запись:
var
nonUnitVector: TVector3;
unitVector: TVector3;
nUVNormed: TVector3;
uVNormed: TVector3;
begin
//Setup Vectors for Test
nonUnitVector := TVector3.New(1, 1, 1);
unitVector := TVector3.New(1, 0, 0);
//Normalise Vectors
nUVNormed := nonUnitVector.Normalised;
uVNormed := unitVector.Normalised;
//Print Output
WriteLn('nUVNormed = ' + nUVNormed.ToString);
WriteLn('uVNormed = ' + uVNormed.ToString);
Readln;
end.
или если вы хотите сжать несколько:
WriteLn('nUVNormed = ' + TVector3.New(1, 1, 1).Normalised.ToString);
WriteLn('uVNormed = ' + TVector3.New(1, 0, 0).Normalised.ToString);
несколько советов:
во-первых, на вашем месте я бы сделал вектор записью, а не классом, но YMMV. Это упростило бы большое, так как компилятор будет управлять временем жизни каждого вектора (вам никогда не нужно беспокоиться об освобождении вещей). Во-вторых,
function TVector3D.IsUnitVector;
begin
if self.GetMagnitude = 1 then
result := True
else
result := False;
end;
обычно пишется, синтаксически и точно эквивалентно,
function TVector3D.IsUnitVector;
begin
result := GetMagnitude = 1
end;
но даже так, это неправильно. Поскольку вы имеете дело с числами с плавающей запятой, вы невозможно надежно проверить равенство. Вместо этого вы должны увидеть, находится ли величина в пределах некоторого интервала единства, чтобы " пух " не мешал. Например, вы могли бы сделать (uses Math
)
function TVector3D.IsUnitVector;
begin
result := IsZero(GetMagnitude - 1)
end;
в-третьих, ваш всегда создайте новый векторный экземпляр! Тем не менее, моя точка зрения очень важна: ваша исходная функция "намеревается" (если вы просто добавите begin...end
block) Для возврата self
или создать новый экземпляр в зависимости от условия, что довольно страшно, так как вы не знаете, сколько экземпляров у вас есть! (И поэтому вы, вероятно, начнете пропускать векторы...)