MCVE:
在类{{1}的重载方法const
中将参数类型从var
切换到out
或Train
时,以下代码不会错误编译}
,但如果指定为non,则会编译。
[dcc32错误] Project14.dpr(41):E2250没有重载版本 这些参数可以调用的“火车”
TAnimalTrainer
找到解决方法:
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)
。问题:这是编译器中的错误吗?
答案 0 :(得分:8)
这是编译器中的错误吗?
不,不是。
尽管您是在重载方法的上下文中发现此问题的,但重载掩盖了实际问题。如果我们消除了过载,将更容易理解该问题。
因此,为此,请考虑以下程序:
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
参数,因此将其拒绝。此时,将不存在任何重载方法,因此会出现错误消息。
答案 1 :(得分:3)
参数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
实例,可以通过{{ 1}}或Cat
变量。基本上,您最终会在Dog
变量内找到TCat
实例。
很显然,当您调用TDog
时,应该得到Dog.Bark
而不是Bark
的输出。 Meow
是Meow
中的第一个方法,就像TCat
是Bark
中的第一个方法一样,当通过TDog
解析Bark
地址时虚拟方法表,它将找到TCat
方法。由于这两种方法都具有相同的签名,如果您认为输出错误是可以的,那么一切都很好。
现在,如果您尝试调用Meow
,则应用程序将因AV崩溃。 Dog.Fetch
类中的对应地址处没有匹配的方法,并且您基本上是在内存中调用一些未初始化的位置,而不是适当的方法。
这说明了为什么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;
实例,则会再次进入AV。
TDog