再次深入理解delphi的类

property WindowState: TWindowState read FWindowState write SetWindowState;

{声明一个属性WindowState,它从字段FWindowState读取值,用方法SetWindowState保存值(方法SetWindowState在内部将值保存到字段FWindowState)}

 

property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy

{声明一个特殊的属性——事件OnDestroy,和上面的不同,OnDestroy的存取都是通过字段FOnDestroy进行的}

==================================

如果类的定义和类的使用者在同一个单元内,那么该类的所有成员无论位于哪个区域,对于使用者而言都是可见的。 一个类对于相同单元的其他类来说,类似于C++中的“友类”,其所有成员都可以被访问。因此,类成 员的可见性设置只是在它们位于不同单元时,才是有效的。这时候,区域内成员的可见性规定如下:

(1) private 域:总不可见。这个区域用来隐藏一些实现细节并防止使用者直接修改敏感信息,比如 容纳属性的存取字段和方法。 (2) protected:派生类可见。这样既可以起到private 域的作用,也能给派生类提供较大的灵活性。 该区域常被用来定义虚方法。 (3) public:总可见。通常用来放置构造、析构函数等供使用者调用的方法。 (4) published:总可见。而且这个区域的类成员有运行时类型信息,该区域通常用来放置供使用者 访问的属性和事件。 (5) automated:总可见。而且该域的成员具有自动化类型信息,用于创建自动化服务器。该关键字 已经不再使用,为向后兼容保留。

=====================================

我们发现字段Button1 和过程Button1Click 并没有被明确地放到哪个可见性区域中。那么这时候 它们的可见性按什么规则来确定呢?此时和编译指令$M 密切相关。 后面我们要讲:$M 用来控制编译器是否给类生成运行时类型信息。所以,在{$M+}状态,Button1 和Button1Click 被隐含指定到published 域;在{$M-}状态,则到public 域。 那么对于上面的TForm1 来说,因为它现在处在{$M+}状态,所以Button1 和Button1Click 实际上 被隐含指定到published 域。

=================================

从调用者角度可分为: ① 普通方法; ② 类方法。 普通方法只能被类实例(即对象)调用,而类方法不但可以被对象调用,还可以直接被类调用(比 如构造函数Create 和析构函数Destroy)。

。实现一个类方法时,要特别注意不要让它依赖 于任何实例信息,千万不要在类方法中存取字段、属性和普通方法。否则通过类而不是对象来调用它 时,将发生错误,因为此时并没有实例信息。

===================================

inherited 并不仅仅局限在子类覆盖后的虚方法中调用父类中被覆盖的方法,实际上,inherited 可以使用在任何地方、调用子类可见的任何父类方法(包括protected、public、published 等域的)。

 

===================================

③ 抽象方法。它是虚方法的特例,在虚方法声明后加上abstract 关键字构成,如: TParent = class procedure OneProc; virtual; abstract; function OneFun: Boolean; dynamic; abstract; end; 抽象方法和普通虚方法的区别: a. 抽象方法只有声明,没有实现;而虚方法必须有实现部分,哪怕没有实际代码而只有begin…end 头。 b.  抽象方法必须在子类中覆盖并实现后才用调用。因为没有实现的方法不能被分配实际地址,而 调用一个没有实际地址的方法显然是荒谬的。 所以,抽象方法也可以被称为纯虚方法。

如果一个类中含有抽象方法,那么这个类就成了抽象类,如TStrings 含有: procedure Clear; virtual; abstract; procedure Delete(Index: Integer); virtual; abstract; 等多个抽象方法。 抽象类是不应该直接用来创建实例的,因为一旦调用了抽象方法,将抛出地址异常,而我们很难 保证不在某些地方调用抽象方法。所以,尽管实例化抽象类是被允许的,却是应该避免的。 因此,抽象类一般都是中间类,实际使用的总是覆盖实现了抽象方法的子类。比如常用的字符串 列表类TStrings,我们总是使用它的子类而不是它本身来构造实例,如:

var Strs: TStrings; begin Strs := TStringList.Create; ⋯⋯ end;

 

========================================================

重载方法的几个特点: a. 可以分别是函数或者过程。因为在Delphi 中,可以将过程看做一个没有返回值的函数,一个函 数也可以当作过程调用。 b. 如果位于相同类中,都必须加上overload 关键字;如果分别在父类和子类中,那么父类的方法 可不加overload 而子类必须加overload。 c. 如果父类的方法是虚(virtual 或者dynamic)的,那么在子类中重载该方法时应该加上reintroduce 修饰字,否则会出现编译警告:“hides virtual method of base type”。当然只是编译时产生警告,如果你 不顾它的警告,坚持不加修饰字,对程序运行结果也不会造成影响。如: TParent = class procedure OneProc; virtual; end; TChild = class(TParent) procedure OneProc; reintroduce; overload; end; d. 在published 区不能出现多个相同的重载方法。如: TParent = class procedure OneProc; virtual; end; TChild = class(TParent) published procedure OneProc; reintroduce; overload; {和父类构成方法重载关系是可以的,因为在TChild的published区,只有一个OneProc方法,而下面两行企图重载AnotherProc则是没可能的,编译器不允许在published区出现 多个同名的方法} procedure AnotherProc(S: String); overload; procedure AnotherProc; overload; end;

===================================

消息方法。消息方法的作用是截获并处理特定的一个消息。它使用的声明关键字是message。如: TCustomForm = class(TScrollingWinControl) private procedure WMClose(var Message: TWMClose); message WM_CLOSE; ⋯⋯ end; TCustomForm 声明了一个消息方法WMClose,WMClose 的作用是捕获并处理消息WM_CLOSE。 当WM_CLOSE 消息到来时,方法WMClose 被自动调用。关于消息方法是如何被自动调用的,可以 参考“VCL 消息机制”的内容。 消息方法的规则命名:消息类名大写+‘_’+消息名(第一个字母大写,其余小写);当然这个规 则不是强制的,你可以任意命名它。 消息方法参数声明格式:var Message: 消息类型。消息类型可以是基本消息类型TMessage,也可 以是特定的消息类型(一般是规则消息方法名头部加‘T’),如上面的可以是TWMClose。特定消息类 型都是Delphi 预定义的,一般位于Messages 单元;一些和具体控件相关的消息类型则在该控件的定 义单元定义。 消息方法的实现类似下面的代码:   procedure TCustomForm.WMClose(var Message: TWMClose); begin {在本类中对消息作必要的处理} Close; {然后调用父类对该消息的处理代码} inherited; end;

==============================

绝大部分属性是可读也可写的,少部分是只读的。可读可写属性一般声明在published 区,供组件 用户在设计时存取属性值,只读属性一般声明在public 区,供运行时使用代码取值(典型的如

================================

(1)对于没有取值范围限制的属性,常常直接用字段来实现存取,如Controls 单元的片断: TControl = class(TComponent) ⋯⋯ private ⋯⋯ FHint: string; ⋯⋯ published ⋯⋯ property Hint: string read FHint write FHint; ⋯⋯ end. (2)当须要检查属性值的取值范围,或者须要执行一些附加操作时,通常采用方法来实现存取, 如TControl.Left 属性是这样声明的: property Left: Integer read FLeft write SetLeft; 方法SetLeft 实现如下: procedure TControl.SetLeft(Value: Integer); begin if Value <> FLeft then {如果新设定的值和原来的值相等,就没有必要执行下面的代码了} begin SetBounds(Value, FTop, FWidth, FHeight); {SetBounds内部执行FLeft := Value; SetBounds主要功能是根据新的Left、Top、 Width、Height属性值改变控件在界面上的显示位置} Include(FScalingFlags, sfLeft); end; end; TWinControl.Handle)。而只写属性在实际开发中几乎没有应用。

==========================

indexes 如果一个属性含有这个部分,那么该属性就具有数组的特点。indexes 就是存取数组元素时必须用 到的参数(我们将它理解为广义的数组元素索引)。一个很典型的例子是TCanvas 类定义的属性Pixels: TCanvas = class(TPersistent) private function GetPixel(X, Y: Integer): TColor; procedure SetPixel(X, Y: Integer; Value: TColor); public property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel; end; 当引用属性Pixels时,X和Y的值被自动传给GetPixel和SetPixel方法,这样GetPixel和SetPixel就可 以存取数组中有X和Y指定位置的元素了。

========================

接口和类的不同 (1)接口中只有方法、属性,没有字段。所以接口中的属性存取都必须通过方法。 (2)接口中的方法只有声明,没有实现。实现在类中完成。 (3)接口中的方法没有作用域。都是公开的,但是在类中则放到不同作用域。 (4)接口中的方法没有修饰字。可以认为它们都是抽象的。 (5)不能创建接口实例,要使用接口,必须在类中实现,通过类调用接口的方法。 (6)在类中必须声明和实现接口的所有方法。不像类继承中可以选择。 (7)接口中的方法在类中实现时,可以加virtual/dynamic、abstract 修饰,从而在子类中可以实现 覆盖。

原文地址:https://www.cnblogs.com/s-Yang/p/3272786.html