40

我想了解

  • 虚拟的
  • 覆盖
  • 超载
  • 重新引入

当应用于对象构造函数时。每次我随机添加关键字直到编译器关闭 - 并且(在使用 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。因为在尝试所有关键字组合时,组合的数量会爆炸式增长:

  • 虚拟的; 超载;
  • 虚拟的; 覆盖;
  • 覆盖;超载;
  • 覆盖;虚拟的;
  • 虚拟的; 覆盖;超载;
  • 虚拟的; 超载; 覆盖;
  • 超载; 虚拟的; 覆盖;
  • 覆盖;虚拟的; 超载;
  • 覆盖;超载; 虚拟的;
  • 超载; 覆盖;虚拟的;
  • ETC

编辑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.

我从经验中知道,即使我在方法或属性之后没有字段,如果我颠倒virtualandoverload关键字的顺序:Delphi 会闭嘴:

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;
4

4 回答 4

18

我看到你的原始声明集不应该编译干净的两个原因:

  1. 应该有一个警告TCellPhone它的构造函数隐藏了基类的方法。这是因为基类方法是virtual,并且编译器担心您正在引入一个具有相同名称的方法而不覆盖基类方法。签名不同并不重要。如果您的意图确实是隐藏基类的方法,那么您需要reintroduce在后代声明上使用,正如您的一个盲目猜测所示。该指令的唯一目的是平息警告;它对运行时行为没有影响。

    忽略稍后会发生的事情TIPhone,以下TCellPhone声明就是您想要的。它隐藏了祖先方法,但您也希望它是虚拟的。它不会继承祖先方法的虚拟性,因为它们是两个完全独立的方法,只是碰巧具有相同的名称。因此,您还需要virtual在新声明上使用。

    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
    end;
    

    基类构造函数 ,TComputer.Create也隐藏了它的祖先的方法, TObject.Create, 但由于方法 inTObject不是虚拟的, 编译器不会警告它。隐藏非虚拟方法一直都在发生,而且通常不起眼。

  2. 您应该得到一个错误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;
    

现代文档告诉一切应该按什么顺序排列:

重新引入过载绑定调用约定摘要; 警告

其中bindingvirtualdynamicoverride调用约定registerpascalcdeclstdcallsafecall警告平台不推荐使用或库。

这是六个不同的类别,但根据我的经验,很少有任何声明超过三个。(例如,需要指定调用约定的函数可能不是方法,因此它们不能是虚拟的。)我不记得顺序;直到今天我还没有看到它记录在案。相反,我认为记住每个指令的目的更有帮助. 当您记住不同任务所需的指令时,您最终只会得到两个或三个,然后进行实验以获得有效订单非常简单。编译器可能接受多个顺序,但不要担心——顺序在确定含义时并不重要。编译器接受的任何顺序都将具有与其他任何顺序相同的含义(调用约定除外;如果您提到多个其中一个,则只有最后一个重要,所以不要这样做)。

所以,你只需要记住每个指令的目的,并考虑哪些一起没有任何意义。例如,您不能同时使用reintroduceoverride,因为它们具有相反的含义。而且你不能一起使用virtualoverride,因为一个暗示另一个。

如果您有很多指令堆积,您可以overload在制定所需的其余指令时随时删除图片。给你的方法起不同的名字,弄清楚它们自己需要哪些其他指令,然后overload在你给它们所有相同的名字的同时添加回来。

于 2010-10-06T21:37:42.057 回答
6

请注意,我没有 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.

所需的订购在文档中有详细说明:

方法声明可以包括不与其他函数或过程一起使用的特殊指令。指令应仅出现在类声明中,而不应出现在定义声明中,并且应始终按以下顺序列出:

重新引入;超载; 捆绑; 调用约定;抽象的; 警告

绑定是虚拟的、动态的或覆盖的;调用约定是 register、pascal、cdecl、stdcall 或 safecall;警告是平台、已弃用或库。

于 2010-10-06T19:43:29.903 回答
3

这是所需定义的工作实现:

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

第一部分就是按照这个来的。两个构造函数的定义TiPhone如下:

  • 第一个构造函数重载继承的两个构造函数之一并覆盖其兄弟。为此,请使用overload; override重载TCellPhone一个,同时覆盖另一个构造函数。
  • 完成后,第二个构造函数需要一个简单override的来覆盖它的兄弟。
于 2010-10-06T22:46:26.967 回答
0

对两者都使用重载,这是我这样做的方式,并且有效。

constructor Create; Overload; <-- 在这里使用重载

constructor Values; Overload;<-- 在这里

记住不要为两个不同的构造函数使用相同的名称

于 2016-08-19T20:48:42.883 回答