>虚拟
>覆盖
>过载
>重新引入
当应用于对象构造函数。每次我随机添加关键字,直到编译器关闭 – 和(经过12年的Delphi开发)我宁愿知道我在做什么,而不是随机尝试。
给定假设的对象集合:
TComputer = class(TObject) public constructor Create(Cup: Integer); virtual; end; TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); virtual; end; TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); override; constructor Create(Cup: Integer; Teapot: string); override; end;
我希望他们的行为方式很可能从声明,但是:
> TComputer有简单的构造函数,后代可以覆盖它
> TCellPhone有一个替代构造函数,后代可以覆盖它
> TiPhone覆盖两个构造函数,调用每个构造函数的继承版本
现在代码不编译。我想明白为什么它不工作。我也想要理解覆盖构造函数的正确方法。或者你可以永远不重写构造函数?或者也许是完全可以接受的重写构造函数?也许你不应该有多个构造函数,也许它是完全可以接受的多个构造函数。
我想了解为什么。修复它将是显而易见的。
也可以看看
> Delphi: How to hide ancestor constructors?
> Reintroducing functions in Delphi
> Delphi: How to add a different constructor to a descendant?
编辑:我也希望得到一些推理的虚拟,覆盖,重载,重新引入的顺序。因为尝试所有关键字组合时,组合数量会爆炸:
> virtual;超载;
> virtual;覆盖;
> override;超载;
> override;虚拟;
> virtual;覆盖;超载;
> virtual;超载;覆盖;
>过载;虚拟;覆盖;
> override;虚拟;超载;
> override;超载;虚拟;
>过载;覆盖;虚拟;
>等
编辑2:我想我们应该开始“是对象层次给定甚至可能?如果不是,为什么不呢?例如,它从根本上不正确有一个祖先的构造函数?
TComputer = class(TObject) public constructor Create(Cup: Integer); virtual; end; TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); virtual; end;
我期望TCellPhone现在有两个构造函数。但我不能在Delphi中找到关键字的组合,使它认为这是一个有效的事情。我从根本上错了想我可以有两个构造函数在这里在TCellPhone?
Note: Everything below this line is not strictly needed to answer the
question – but it does help to explain
my thinking. Perhaps you can see,
based on my thought processes,what
fundamental piece i’m missing that
makes everything clear.
现在这些声明不编译:
//Method Create hides virtual method of base type TComputer: TCellPhone = class(TComputer) constructor Create(Cup: Integer; Teapot: string); virtual; //Method Create hides virtual method of base type TCellPhone: TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); override; constructor Create(Cup: Integer; Teapot: string); overload; <-------- end;
所以首先,我会尝试修复TCellPhone。我将开始随机添加overload关键字(我知道我不想重新引入,因为这将隐藏其他构造函数,我不想要):
TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); virtual; overload; end;
我知道从经验,即使我没有一个方法或属性后的字段,如果我颠倒的虚拟和重载关键字的顺序:Delphi将关闭:
TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); overload; virtual; end;
但我仍然得到的错误:
Method ‘Create’ hides virtual method of base type ‘TComputer’
所以我尝试删除这两个关键字:
TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); end;
但我仍然得到的错误:
Method ‘Create’ hides virtual method of base type ‘TComputer’
所以我辞职,现在试着重新介绍:
TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); reintroduce; end;
现在TCellPhone编译,但它已经使事情更糟的TiPhone:
TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); override; <-----cannot override a static method constructor Create(Cup: Integer; Teapot: string); override; <-----cannot override a static method end;
两个都抱怨我不能覆盖它们,所以我删除override关键字:
TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); constructor Create(Cup: Integer; Teapot: string); end;
但现在第二个创建说,它必须标记为重载,我做的(事实上,我会标记为超载,因为我知道如果我不会发生什么):
TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); overload; constructor Create(Cup: Integer; Teapot: string); overload; end;
所有的一切都在接口部分。不幸的是我的实现不会工作。我的单参数构造函数TiPhone不能调用继承的构造函数:
constructor TiPhone.Create(Cup: Integer); begin inherited Create(Cup); <---- Not enough actual parameters end;
解决方法
>在TCellPhone中应该有一个警告,它的构造函数隐藏了基类的方法。这是因为基类方法是虚拟的,编译器担心你引入一个相同名称的新方法,而不覆盖基类方法。签名不同并不重要。如果你的意图确实是隐藏基类的方法,那么你需要使用reindroduce对后代声明,作为你的盲目猜测之一。该指令的唯一目的是平息警告;它对运行时行为没有影响。
忽略以后会发生什么TIPhone,下面的TCellPhone声明是你想要的。它隐藏了祖先方法,但你也希望它是虚拟的。它不会继承祖先方法的虚拟性,因为它们是两个完全独立的方法,它们恰好具有相同的名称。因此,你需要在新的声明上使用virtual。
TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual; end;
基类构造函数TComputer.Create也隐藏了它的祖先TObject.Create的一个方法,但是因为TObject中的方法不是虚函数,编译器不会警告它。隐藏非虚拟方法总是发生,并且通常是不可见的。
>你应该在TIPhone中得到一个错误,因为不再有任何单参数构造函数要覆盖。你隐藏在TCellPhone。因为你想有两个构造函数,重新引入显然不是更好使用的正确选择。你不想隐藏基类构造函数;你想用另一个构造函数扩充它。
由于您希望两个构造函数具有相同的名称,因此您需要使用overload指令。该指令需要用于所有原始声明 – 第一次每个不同的签名在后代中引入后续声明。我认为这是所有的声明(即使是基类),它不伤害这样做,但我想这不是必需的。所以,你的声明应该看起来像这样:
TComputer = class(TObject) public constructor Create(Cup: Integer); overload; // Allow descendants to add more constructors named Create. virtual; // Allow descendants to re-implement this constructor. end; TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); overload; // Add another method named Create. virtual; // Allow descendants to re-implement this constructor. end; TiPhone = class(TCellPhone) public constructor Create(Cup: Integer); override; // Re-implement the ancestor's Create(Integer). constructor Create(Cup: Integer; Teapot: string); override; // Re-implement the ancestor's Create(Integer,string). end;
Modern documentation告诉什么顺序应该进入:
reintroduce; overload; binding; calling convention; abstract; warning
where binding is virtual,dynamic,or override; calling convention is register,pascal,cdecl,stdcall,or safecall; and warning is platform,deprecated,or library.
这些是六个不同的类别,但在我的经验中,很少有超过三个任何声明。 (例如,需要调用约定的函数可能不是方法,所以它们不能是虚拟的。)我从来不记得顺序;我从来没有看到它记录到今天。相反,我认为记住每个指令的目的更有帮助。当你记得你需要不同任务的指令时,你最终只有两三个,然后很简单的实验得到一个有效的订单。编译器可能接受多个命令,但不要担心 – 命令在确定意义时不重要。编译器接受的任何顺序将具有与任何其他顺序相同的含义(除了调用约定;如果提到多个顺序,只有最后一个计数,所以不要这样做)。
所以,那么你只需要记住每个指令的目的,并考虑哪些不一起有意义。例如,您不能同时使用重新引入和覆盖,因为它们有相反的含义。你不能使用虚拟和覆盖在一起,因为一个暗示另一个。
如果你有很多指令堆积,你可以随时切出超出的图片,而你完成剩下的指令,你需要的。给你的方法不同的名字,找出他们自己需要的其他指令,然后添加重载,而你再次给他们所有相同的名称。