Delphi:理解构造函数

Ian*_*oyd 34 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;
Run Code Online (Sandbox Code Playgroud)

我希望它们表现的方式可能从声明中可以明显看出,但是:

  • 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;
Run Code Online (Sandbox Code Playgroud)

我希望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;
Run Code Online (Sandbox Code Playgroud)

所以首先我会尝试修复TCellPhone.我将从随机添加overload关键字开始(我知道我不想要,reintroduce因为这会隐藏其他构造函数,我不想要):

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;
Run Code Online (Sandbox Code Playgroud)

但那失败了:Field definition not allowed after methods or properties.

我从经验中知道,即使我没有方法或属性后的字段,如果我颠倒virtualoverload关键字的顺序:德尔福将关闭:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); overload; virtual; 
end;
Run Code Online (Sandbox Code Playgroud)

但我仍然得到错误:

方法'Create'隐藏基类型'TComputer'的虚方法

所以我尝试删除两个关键字:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string);
end;
Run Code Online (Sandbox Code Playgroud)

但我仍然得到错误:

方法'Create'隐藏基类型'TComputer'的虚方法

所以我辞职,现在尝试reintroduce:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;
Run Code Online (Sandbox Code Playgroud)

现在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;
Run Code Online (Sandbox Code Playgroud)

两者都在抱怨我无法覆盖它们,所以我删除了override关键字:

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer);
   constructor Create(Cup: Integer; Teapot: string);
end;
Run Code Online (Sandbox Code Playgroud)

但是现在第二个创建说它必须标记为过载,我这样做(实际上我会将它们都标记为过载,因为我知道如果不这样做会发生什么):

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); overload;
   constructor Create(Cup: Integer; Teapot: string); overload;
end;
Run Code Online (Sandbox Code Playgroud)

这一切都很好interface.不幸的是我的实现不起作用.我的TiPhone的单个参数构造函数不能调用继承的构造函数:

constructor TiPhone.Create(Cup: Integer);
begin
    inherited Create(Cup); <---- Not enough actual parameters
end;
Run Code Online (Sandbox Code Playgroud)

Rob*_*edy 16

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

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

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

    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
    end;
    
    Run Code Online (Sandbox Code Playgroud)

    基类构造函数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;
    
    Run Code Online (Sandbox Code Playgroud)

现代文档说明了一切应该进入的顺序:

重新引入 ; 超载 ; 结合 ; 召集会议 ; 抽象的 ; 警告

其中结合虚拟,动态,或覆盖 ; 调用约定register,pascal,cdecl,stdcallsafecall ; 和警告平台,已弃用.

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

那么,你只需要记住每个指令的目的,并考虑哪些指令没有任何意义.例如,您不能使用reintroduce,并override在同一时间,因为他们有相反的意思.你不能一起使用virtual,override因为一个暗示另一个.

如果您有许多指令堆积在一起,那么overload在您计算出所需的其他指令时,您总是可以剪掉图片.给你的方法不同的名字,找出他们自己需要的其他指令,然后overload在你再次给他们所有相同的名字时加回来.


Mic*_*sen 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.

所需的订购详见文档:

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

重新引入; 超载; 捆绑; 召集会议; 抽象; 警告

绑定是虚拟,动态还是覆盖; 调用约定是register,pascal,cdecl,stdcall或safecall; 和警告是平台,已弃用或库.