Delphi:理解构造函数

时间:2010-10-06 19:08:19

标签: delphi constructor delphi-5 constructor-chaining

我希望了解

  • 虚拟
  • 倍率
  • 过载
  • 重新引入

应用于对象构造函数时。每次我随机添加关键字直到编译器关闭 - 并且(在用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会覆盖两个构造函数,调用每个
  • 的继承版本

现在该代码无法编译。我想了解为什么它不起作用。我也想了解覆盖构造函数的正确方法。或许你永远不能覆盖构造函数?或者覆盖构造函数是完全可以接受的?也许你永远不应该有多个构造函数,也许完全可以接受多个构造函数。

我想了解为什么。修复它会很明显。

另见

编辑:我还希望对virtualoverrideoverloadreintroduce的顺序进行一些推理。因为在尝试所有关键字组合时,组合数量会爆炸:

  • 虚拟;过载;
  • 虚拟;覆盖;
  • 倍率;过载;
  • 倍率;虚拟;
  • 虚拟;覆盖;过载;
  • 虚拟;超载;覆盖;
  • 过载;虚拟;覆盖;
  • 倍率;虚拟;过载;
  • 倍率;超载;虚拟;
  • 过载;覆盖;虚拟;

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

我从经验中知道,即使我在方法或属性之后没有字段,如果我颠倒了virtualoverload关键字的顺序: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;

4 个答案:

答案 0 :(得分:16)

我发现原始声明集不应该干净地编译的原因有两个:

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

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

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

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

  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;
    
  3. Modern documentation告诉我们应该采取什么顺序:

      

    <强>重新引入;的过载; 结合; 调用约定;的抽象; 警告

         

    绑定 虚拟动态覆盖; 调用约定注册 pascal cdecl stdcall safecall ; 警告 平台已弃用图书馆

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

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

    如果你有许多指令堆积起来,你可以随时将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; - 和

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