MCVE:
在const
类的重载方法var
中将参数类型从out
切换到Train
或TAnimalTrainer
时,以下代码无法编译时出错
但如果指定了non,则编译。
[dcc32错误] Project14.dpr(41):E2250没有可以用这些参数调用的'Train'的重载版本
program Project14;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TAnimal = class
private
FName: string;
end;
TDog = class(TAnimal)
public
constructor Create(Name: string);
end;
TAnimalTrainer = record // class or record
public
procedure Train({const}var aA: TAnimal); overload; // class method or not
procedure Train(const aName: string); overload;
end;
{ TAnimalTrainer }
procedure TAnimalTrainer.Train(const aName: string);
var
Dog: TDog;
begin
Dog := nil;
try
Dog := TDog.Create(aName);
Train(Dog); // error here
finally
Dog.Free;
end;
end;
procedure TAnimalTrainer.Train(var aA: TAnimal);
begin
aA := nil;
end;
{ TDog }
constructor TDog.Create(Name: string);
begin
FName := Name;
end;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
找到解决方法:
var
。TAnimal(Dog)
const
。问题:这是编译器中的错误吗?
这是编译器中的错误吗?
不它不是。
虽然您在重载方法的上下文中发现了这一点,但重载正在掩盖真正的问题。如果我们删除过载,将更容易理解该问题。
所以,为此,请考虑以下方案:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
procedure GetAnimal(var AAnimal: TAnimal);
begin
AAnimal := TAnimal.Create;
end;
var
Dog: TDog;
begin
GetAnimal(Dog);
end.
这在使用此错误调用GetAnimal
时无法编译:
[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical
为什么编译器拒绝这个?好吧,想象一下它是否接受了这个。如果它这样做那么当GetAnimal
返回时,Dog
变量将指的是一个不是TDog
的对象。
要看到这一点,请将程序的主体更改为如下所示:
GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));
当你这样做时,程序编译,但输出是
FALSE
在程序的上下文中,编译器面临一些重载。正如我们在此示例中看到的,编译器无法接受将TDog
变量传递给TAnimal
var参数,因此它拒绝该重载。它知道它不能将TDog
变量传递给string
参数,因此被拒绝。此时,没有剩余的重载方法,因此出现错误消息。
不匹配的var
参数的基本问题是你最终在调用变量中输入了错误的类型。
您可以通过使用absolute
关键字欺骗编译器来执行此操作 - 这允许您声明共享相同空间的不同类型的变量 - 并模拟编译器允许您使用此类构造时会发生什么。
考虑以下示例
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
如果您运行上面的代码,您将获得以下输出
Run
Meow
Dog
和Cat
变量共享相同的地址空间,因此当您调用Train(Cat)
作为结果时,您将获得TCat
实例,您可以通过Cat
或Dog
变量使用。基本上,你最终会在TCat
变量中使用TDog
实例。
很明显,当你打电话给Dog.Bark
时,你应该得到Bark
作为输出而不是Meow
。 Meow
是TCat
中的第一种方法,就像Bark
是TDog
中的第一种方法一样,当通过Bark
虚方法表解析TCat
地址时,它会找到Meow
方法。由于两种方法具有相同的签名,如果您认为输出错误,一切都很好。
现在,如果您尝试调用Dog.Fetch
,应用程序将与AV崩溃。 TCat
类中的相应地址没有匹配的方法,你基本上在内存中调用一些未初始化的位置而不是正确的方法。
这就解释了为什么var
或out
参数类型必须与调用者变量类型匹配。
至于为什么你可以通过TDog
或TCat
作为TAnimal
const
或值参数。 TDog
和TCat
都继承自TAnimal
,你可以用TAnimal
实例,TDog
和TCat
都支持它。它们可以覆盖特定的行为,因此您的猫可以与您的狗不同地运行,但无论您做什么,它都是明确定义的。你不能最终运行一些不存在的代码。
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
当然,如果Fetch
实际上是TAnimal
,这并不妨碍你测试特定类并使用类型转换来调用TDog
。
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
但是,如果您滥用类型转换和类型转换而不检查特定变量是否实际上是TDog
实例,您将再次进入AV。
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;