Delphi:了解构造函数 [英] Delphi: Understanding constructors

查看:188
本文介绍了Delphi:了解构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找




  • virtual

  • 覆盖

  • 过载

  • 重新引入



对象构造函数。每次我随机添加关键字,直到编译器关闭 - 并且(在使用Delphi开发12年之后),我宁愿知道我在做什么,而不是随机尝试。



给定一组假设的对象:

  TComputer = class(TObject)
public
构造函数Create(Cup:Integer);虚拟;
结束

TCellPhone = class(TComputer)
public
构造函数创建(Cup:Integer; Teapot:string);虚拟;
结束

TiPhone = class(TCellPhone)
public
构造函数创建(Cup:Integer);覆盖
构造函数创建(Cup:Integer; Teapot:string);覆盖
结束

我希望他们的行为方式可能从声明中显而易见,但是:




  • TComputer 有一个简单的构造函数,后代可以覆盖它

  • TCellPhone 有一个替代构造函数,后代可以覆盖它

  • TiPhone 覆盖这两个构造函数,调用每个
  • 的继承版本


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



我想了解为什么



另请参见





编辑:我也希望得到一些关于虚拟的顺序的推理覆盖 overload reintroduce 。因为当尝试所有的关键字组合时,组合数量会爆炸:




  • virtual;超载;

  • 虚拟;覆盖;

  • 覆盖;超载;

  • 覆盖; virtual;

  • virtual;覆盖超载;

  • 虚拟;超载;覆盖;

  • overload;虚拟;覆盖;

  • 覆盖;虚拟;超载;

  • 覆盖;超载; virtual;

  • overload;覆盖虚拟;




编辑2:应该从开始是甚至可能的对象层次结构?如果不是,为什么不呢?例如,从祖先获得构造函数是否从根本上不正确?

  TComputer = class(TObject)
public
构造函数创建(Cup:Integer);虚拟;
结束

TCellPhone = class(TComputer)
public
构造函数创建(Cup:Integer; Teapot:string);虚拟;
结束

我希望 TCellPhone 现在有两个构造函数。但是我在Delphi中找不到关键字的组合,以致于认为这是有效的。我从根本上错误地认为我可以在 TCellPhone 中有两个构造函数?







注意:这条线以下的所有内容都不是严格需要回答
的问题 - 但它有助于解释
我的想法。也许你可以看到,
基于我的思维过程,什么
基本的一块,我错过
使一切清楚。


现在这些声明不编译:

  //方法创建隐藏基类型的虚拟方法TComputer:
TCellPhone = class(TComputer)
构造函数创建(Cup:Integer; Teapot:string);虚拟;

//方法创建隐藏基类型的虚拟方法TCellPhone:
TiPhone = class(TCellPhone)
public
构造函数创建(Cup:Integer);覆盖
构造函数创建(Cup:Integer; Teapot:string);超载; < --------
end;






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

  TCellPhone =类(TComputer)
public
构造函数Create(Cup:Integer; Teapot:string);虚拟;超载;
结束

但是失败:方法或属性后不允许使用字段定义

我从经验中知道,即使我没有一个方法或财产后的字段,如果我扭转 virtual overload 关键字:Delphi会闭嘴:

  TCellPhone = class(TComputer)
public
构造函数创建(Cup:Integer; Teapot:string);超载;虚拟;
结束

但我仍然收到错误:


方法'创建'隐藏基类型TComputer的虚拟方法

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

  TCellPhone = class(TComputer)
public
构造函数创建(Cup:Integer; Teapot:string );
结束

但我仍然收到错误:


方法'创建'隐藏基类型TComputer的虚拟方法


所以我现在辞职了尝试 reintroduce

  TCellPhone =类(TComputer)
public
构造函数创建(Cup:Integer; Teapot:string);重新引入
结束

现在,TCellPhone编译,但它使TiPhone变得更糟:

  TiPhone = class(TCellPhone)
public
构造函数创建(Cup:Integer);覆盖< -----不能覆盖静态方法
构造函数Create(Cup:Integer; Teapot:string);覆盖< -----不能覆盖静态方法
end;

两人都抱怨我不能覆盖它们,所以我删除 / code>关键字:

  TiPhone = class(TCellPhone)
public
构造函数创建(杯子:整数);
构造函数创建(Cup:Integer; Teapot:string);
结束

但现在第二个创建表示必须标有重载,我做(事实上我'将两者都标记为超载,因为我知道如果我没有会发生什么事情):

  TiPhone = class(TCellPhone)
public
构造函数创建(Cup:Integer);超载;
构造函数创建(Cup:Integer; Teapot:string);超载;
结束

所有这些都在界面部分。不幸的是,我的实现将无法正常工作。 TiPhone的单参数构造函数不能调用继承的构造函数:

 构造函数TiPhone.Create(Cup:Integer); 
开始
继承创建(杯); < ----实际参数不足
结束;


解决方案

我看到原始的一组声明应该有两个原因, t编译干净:


  1. TCellPhone 中应该有一个警告 / code>其构造函数隐藏基类的方法。这是因为base-class方法是的虚拟,编译器担心您会引用具有相同名称的新的方法,而不会覆盖基类方法。签名不同,并不重要。如果您的意图确实是隐藏基类的方法,那么您需要在后代声明中使用 reintroduce ,这是您盲目猜测之一。该指令的唯一目的是平息警告;它对运行时行为没有影响。



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

      TCellPhone = class(TComputer)
    public
    构造函数创建(Cup:Integer; Teapot:string);重新引入虚拟;
    结束

    基类构造函数 TComputer.Create ,还隐藏了一个祖先的方法, TObject.Create ,但是由于 TObject 不是虚拟的,编译器不会警告它。隐藏非虚拟方法一直发生,通常不显着。


  2. 您应该在错误 > TIPhone ,因为不再需要覆盖任何一个参数的构造函数。您将其隐藏在 TCellPhone 中。既然你想有两个构造函数,那么 c> c> c> 不是早期使用的正确选择。你不想隐藏基类构造函数;你想用另一个构造函数来扩充它。



    既然你们希望这两个构造函数都有相同的名字,你需要使用 c $ c>指令。该指令需要在所有原始声明中使用 - 每个不同的签名首次在后代中引入后续声明。我以为这是所有声明(甚至是基类)都需要的,但是不要这样做,但是我猜这不是必需的。因此,您的声明应如下所示:

      TComputer = class(TObject)
    public
    构造函数创建(杯:整数);
    重载; //允许后代添加更多名为Create的构造函数。
    虚拟; //允许后代重新实现这个构造函数。
    结束

    TCellPhone = class(TComputer)
    public
    构造函数创建(Cup:Integer; Teapot:string);
    重载; //添加另一个名为Create的方法。
    虚拟; //允许后代重新实现这个构造函数。
    结束

    TiPhone = class(TCellPhone)
    public
    构造函数创建(Cup:Integer);
    覆盖; //重新实现祖先的Create(Integer)。
    构造函数创建(Cup:Integer; Teapot:string);
    覆盖; //重新实现祖先的Create(整数,字符串)。
    结束


现代文档告诉您应该执行的任何事情:


重新引入; 过载; 绑定; 调用约定; 抽象; ,或覆盖; pascal cdecl stdcall safecall ; 平台不支持


这是六种不同的类别,但根据我的经验,在任何声明中都有三个以上是罕见的。 (例如,需要调用约定的函数可能不是方法,所以它们不能是虚拟的。)我从来没有记住顺序;直到今天,我从来没有见过它。相反,我认为记住每个指令的目的更有帮助。当你记得你需要哪些指令用于不同的任务时,最终只能使用两到三个,然后实验得到一个有效的顺序就很简单了。编译器可能接受多个命令,但不要担心 - 顺序在确定意义上并不重要。编译器接受的任何排序将具有与任何其他命令相同的含义(除了调用约定;如果您提及多于一个,只有最后一个计数,所以不要这样做)。



那么你只需要记住每个指令的目的,并考虑哪些不一致。例如,您不能同时使用 reintroduce override ,因为它们具有相反的含义。而且您不能一起使用 virtual 覆盖。因为一个意味着另一个。



如果您有很多指令堆积,您可以在制定出您需要的其余指令时,始终将 overload 从图片中删除。给你的方法不同的名字,找出他们自己需要的其他指令,然后添加 overload ,同时给他们一样的


i'm looking to understand

  • virtual
  • override
  • overload
  • reintroduce

when applied to object constructors. Every time i randomly add keywords until the compiler shuts up - and (after 12 years of developing with Delphi) i'd rather know what i'm doing, rather than trying things randomly.

Given a hypothetical set of objects:

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;

The way i want them to behave is probably obvious from the declarations, but:

  • TComputer has the simple constructor, and descendants can override it
  • TCellPhone has an alternate constructor, and descendants can override it
  • TiPhone overrides both constructors, calling the inherited version of each

Now that code doesn't compile. i want to understand why it doesn't work. i also want to understand the proper way to override constructors. Or perhaps you could never override constructors? Or perhaps it is perfectly acceptable to override constructors? Perhaps you should never have multiple constructors, perhaps it is perfectly acceptable to have multiple constructors.

i want to understand the why. Fixing it would then be obvious.

See also

Edit: i'm also looking to get some reasoning on the order of virtual, override, overload, reintroduce. Because when trying all combinations of keywords, the number of combinations explodes:

  • virtual; overload;
  • virtual; override;
  • override; overload;
  • override; virtual;
  • virtual; override; overload;
  • virtual; overload; override;
  • overload; virtual; override;
  • override; virtual; overload;
  • override; overload; virtual;
  • overload; override; virtual;
  • etc

Edit 2: i guess we should begin with "is the object hierarchy given even possible?" If not, why not? For example, is it fundamentally incorrect to have a constructor from an ancestor?

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

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

i would expect that TCellPhone now has two constructors. But i can't find the combination of keywords in Delphi to make it think that's a valid thing to do. Am i fundamentally wrong in thinking i can have two constructors here in 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.

Now these declarations don't compile:

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


So first i'll trying fixing TCellPhone. i'll start by randomly adding the overload keyword (i know i don't want reintroduce because that would hide the other constructor, which i don't want):

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

But that fails: Field definition not allowed after methods or properties.

i know from experience that, even though i don't have a field after a method or property, if i reverse the order of the virtual and overload keywords: Delphi will shut up:

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

But i still get the error:

Method 'Create' hides virtual method of base type 'TComputer'

So i try removing both keywords:

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

But i still get the error:

Method 'Create' hides virtual method of base type 'TComputer'

So i resign myself to now trying reintroduce:

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

And now TCellPhone compiles, but it has made things much worse for 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;

Both are complaining that i cannot override them, so i remove the override keyword:

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer);
   constructor Create(Cup: Integer; Teapot: string);
end;

But now the 2nd create says it must be marked with overload, which i do (in fact i'll mark both as overload, since i know what will happen if i don't):

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); overload;
   constructor Create(Cup: Integer; Teapot: string); overload;
end;

All all is good in the interface section. Unfortunately my implementations won't work. My single parameter constructor of TiPhone cannot call the inherited constructor:

constructor TiPhone.Create(Cup: Integer);
begin
    inherited Create(Cup); <---- Not enough actual parameters
end;

解决方案

I see two reasons your original set of declarations shouldn't compile cleanly:

  1. There should be a warning in TCellPhone that its constructor hides the method of the base class. This is because the base-class method is virtual, and the compiler worries that you're introducing a new method with the same name without overriding the base-class method. It doesn't matter that the signatures differ. If your intention is indeed to hide the method of the base class, then you need to use reintroduce on the descendant declaration, as one of your blind guesses showed. The sole purpose of that directive is to quell the warning; it has no effect on run-time behavior.

    Ignoring what's going to happen with TIPhone later on, the following TCellPhone declaration is what you'd want. It hides the ancestor method, but you want it to be virtual as well. It won't inherit the virtualness of the ancestor method because they're two completely separate methods that just happen to have the same name. Therefore, you need to use virtual on the new declaration as well.

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

    The base-class constructor, TComputer.Create, is also hiding a method of its ancestor, TObject.Create, but since the method in TObject is not virtual, the compiler doesn't warn about it. Hiding non-virtual methods happens all the time and is generally unremarkable.

  2. You should get an error in TIPhone because there is no longer any one-argument constructor to override. You hid it in TCellPhone. Since you want to have two constructors, reintroduce clearly wasn't the right choice to use earlier. You don't want to hide the base-class constructor; you want to augment it with another constructor.

    Since you want both constructors to have the same name, you need to use the overload directive. That directive needs to be used on all the original declarations — the first time each distinct signature is introduced subsequent declarations in descendants. I thought it was required on all declarations (even the base class), and it doesn't hurt to do that, but I guess it's not required. So, your declarations should look like this:

    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 tells what order everything should go in:

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.

Those are six different categories, but in my experience, it's rare to have more than three on any declaration. (For example, functions that need calling conventions specified probably aren't methods, so they can't be virtual.) I never remember the order; I've never seen it documented till today. Instead, I think it's more helpful to remember each directive's purpose. When you remember which directives you need for different tasks, you'll end up with just two or three, and then it's pretty simple to experiment to get a valid order. The compiler might accept multiple orders, but don't worry — order isn't important in determining meaning. Any ordering the compiler accepts will have the same meaning as any other (except for calling conventions; if you mention more than one of those, only the last one counts, so don't do that).

So, then you just have to remember the purpose of each directive, and think about which ones don't make any sense together. For example, you cannot use reintroduce and override at the same time because they have opposite meanings. And you can't use virtual and override together because one implies the other.

If you have lots of directives piling up, you can always cut overload out of the picture while you work out the rest of the directives you need. Give your methods different names, figure out which of the other directives they need by themselves, and then add overload back while you give them all the same names again.

这篇关于Delphi:了解构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆