我希望了解
应用于对象构造函数时。每次我随机添加关键字直到编译器关闭 - 并且(在用Delphi开发12年之后)我宁愿知道我在做什么,而不是随意尝试。
给出一组假设的对象:
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
会覆盖两个构造函数,调用每个现在该代码无法编译。我想了解为什么它不起作用。我也想了解覆盖构造函数的正确方法。或许你永远不能覆盖构造函数?或者覆盖构造函数是完全可以接受的?也许你永远不应该有多个构造函数,也许完全可以接受多个构造函数。
我想了解为什么。修复它会很明显。
编辑:我还希望对virtual
,override
,overload
,reintroduce
的顺序进行一些推理。因为在尝试所有关键字组合时,组合数量会爆炸:
编辑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
中有两个构造函数,从根本上说是错误的吗?
注意:此行以下的所有内容都不是严格要求回答的 问题 - 但它确实有助于解释 我的想法。也许你可以看到, 基于我的思维过程,什么 基本的一块我错过了 让事情变得清晰。
现在这些声明不能编译:
//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
关键字(我知道我不想要reintroduce
,因为这会隐藏其他构造函数,我不想要):
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;
但失败了:Field definition not allowed after methods or properties
。
我从经验中知道,即使我在方法或属性之后没有字段,如果我颠倒了virtual
和overload
关键字的顺序:Delphi会关闭:< / p>
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
但我仍然得到错误:
方法'创建'隐藏基类型'TComputer'的虚拟方法
所以我尝试删除两个关键字:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
end;
但我仍然得到错误:
方法'创建'隐藏基类型'TComputer'的虚拟方法
所以我辞职,现在尝试reintroduce
:
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;
interface
部分的所有内容都很好。不幸的是我的实现不起作用。我的TiPhone的单个参数构造函数不能调用继承的构造函数:
constructor TiPhone.Create(Cup: Integer);
begin
inherited Create(Cup); <---- Not enough actual parameters
end;
答案 0 :(得分:16)
我发现原始声明集不应该干净地编译的原因有两个:
TCellPhone
中应该有警告,其构造函数隐藏基类的方法。这是因为基类方法是虚拟,并且编译器担心您在不重写基类方法的情况下引入具有相同名称的 new 方法。签名不同并不重要。如果您的意图确实是隐藏基类的方法,那么您需要在后代声明中使用reintroduce
,正如您的一个盲目猜测所示。该指令的唯一目的是平息警告;它对运行时行为没有影响。
忽略TIPhone
之后会发生什么,以下TCellPhone
声明就是你想要的。它隐藏了祖先方法,但您希望它也是虚拟方法。它不会继承祖先方法的虚拟性,因为它们是两个完全独立的方法,恰好具有相同的名称。因此,您还需要在新声明中使用virtual
。
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
end;
基类构造函数TComputer.Create
也隐藏了其祖先TObject.Create
的方法,但由于TObject
中的方法不是虚拟的,编译器不会对此发出警告。隐藏非虚拟方法一直都在发生,并且通常不起眼。
您应该在TIPhone
中收到错误,因为不再需要覆盖任何单参数构造函数。你把它隐藏在TCellPhone
中。由于您希望有两个构造函数,reintroduce
显然不是以前使用的正确选择。您不想隐藏基类构造函数;你想用另一个构造函数来扩充它。
由于您希望两个构造函数具有相同的名称,因此您需要使用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告诉我们应该采取什么顺序:
<强>重新引入强>;的过载强>; 结合; 调用约定;的抽象强>; 警告
绑定 虚拟,动态或覆盖; 调用约定是注册, pascal , cdecl , stdcall 或 safecall 强>; 警告 平台,已弃用或图书馆。
这是六个不同的类别,但根据我的经验,在任何声明中都有超过三个类别。 (例如,需要调用约定的函数可能不是方法,因此它们不能是虚拟的。)我永远不记得订单;我从来没有见过记录到今天。相反,我认为记住每个指令的目的会更有帮助。当你还记得不同任务需要哪些指令时,你最终会得到两到三个,然后通过实验来获得有效的订单非常简单。编译器可能接受多个订单,但不要担心 - 在确定含义时,顺序并不重要。编译器接受的任何顺序都与其他任何顺序相同(除了调用约定;如果你提到不止一个,只有最后一个计数,所以不要这样做。)
那么,你只需要记住每个指令的目的,并考虑哪些指令没有任何意义。例如,您不能同时使用reintroduce
和override
,因为它们具有相反的含义。而且你不能同时使用virtual
和override
,因为一个暗示了另一个。
如果你有许多指令堆积起来,你可以随时将overload
从图片中删除,同时找出你需要的其他指令。为您的方法指定不同的名称,找出他们自己需要的其他指令,然后再添加overload
,同时再次给它们提供相同的名称。
答案 1 :(得分:5)
请注意,我没有Delphi 5,所以我的答案是基于最新版本的Delphi XE。我认为这不会在这里产生任何影响,但如果确实如此,你就会受到警告。 :)
这主要基于http://docwiki.embarcadero.com/RADStudio/en/Methods,这是方法如何工作的当前文档。您的Delphi 5帮助文件也可能与此类似。
首先,虚拟构造函数在这里可能没什么意义。在某些情况下你确实想要这个,但这可能不是一个。看看http://docwiki.embarcadero.com/RADStudio/en/Class_References是否需要虚拟构造函数 - 如果您在编码时始终知道对象的类型,则不需要。
您在1参数构造函数中遇到的问题是您的父类本身没有1参数构造函数 - 未公开继承的构造函数。您无法使用inherited
在层次结构中上升多个级别,您只能调用您的直接父级。您需要使用一些默认值调用2参数构造函数,或者也将1参数构造函数添加到TCellPhone。
一般来说,这四个关键字具有以下含义:
virtual
- 将此标记为您需要运行时调度的函数(允许多态行为)。这仅适用于初始定义,而不是在重写子类时。override
- 为虚拟方法提供新的实现。overload
- 标记与另一个函数同名的函数,但标注不同的参数列表。reintroduce
- 告诉编译器您实际上打算隐藏虚拟方法,而不是仅仅忘记提供override
。所需的订购详见文件:
方法声明可以包括 未使用的特殊指令 与其他功能或程序。 指令应出现在课堂上 只有声明,不在定义中 声明,应该永远 按以下顺序列出:
重新引入;超载;捆绑; 召集会议;抽象;警告
其中绑定是虚拟的,动态的,或 覆盖;调用约定是 注册,pascal,cdecl,stdcall或 safecall;和警告是平台, 不推荐使用,或库。
答案 2 :(得分:2)
这是所需定义的有效实现:
program OnConstructors;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); overload; override;
constructor Create(Cup: Integer; Teapot: string); override;
end;
{ TComputer }
constructor TComputer.Create(Cup: Integer);
begin
Writeln('Computer: cup = ', Cup);
end;
{ TCellPhone }
constructor TCellPhone.Create(Cup: Integer; Teapot: string);
begin
inherited Create(Cup);
Writeln('Cellphone: teapot = ', Teapot);
end;
{ TiPhone }
constructor TiPhone.Create(Cup: Integer);
begin
inherited Create(Cup);
Writeln('iPhone: cup = ', Cup);
end;
constructor TiPhone.Create(Cup: Integer; Teapot: string);
begin
inherited;
Writeln('iPhone: teapot = ', Teapot);
end;
var
C: TComputer;
begin
C := TComputer.Create(1);
Writeln; FreeAndNil(C);
C := TCellPhone.Create(2);
Writeln; FreeAndNil(C);
C := TCellPhone.Create(3, 'kettle');
Writeln; FreeAndNil(C);
C := TiPhone.Create(4);
Writeln; FreeAndNil(C);
C := TiPhone.Create(5, 'iPot');
Readln; FreeAndNil(C);
end.
结果:
Computer: cup = 1
Computer: cup = 2
Computer: cup = 3
Cellphone: teapot = kettle
Computer: cup = 4
iPhone: cup = 4
Computer: cup = 5
Cellphone: teapot = iPot
iPhone: teapot = iPot
第一部分符合this。然后,TiPhone
两个构造函数的定义如下:
overload; override
重载TCellPhone
,同时覆盖其他构造函数。override
来覆盖它的兄弟。答案 3 :(得分:0)
在两者上都使用重载,这就是我这样做的方式,并且有效。
constructor Create; Overload
; &lt; - 在这里使用重载
constructor Values; Overload;
&lt; - 和
请记住不要为两个不同的构造函数使用相同的名称