Classes and objects(类和对象)
类(或者类类型)定义了一个结构,它包括字段(也称为域)、方法和属性;类的实例叫作对象;类的字
段、方法和属性被称为它的部件(components)或成员。
• 字段在本质上是一个对象的变量。和记录的字段相似,类的字段表示一个类实例的数据项;
• 方法是一个过程或者函数,它和类相关联。绝大多数方法做用在对象(也就是类的实例)上,其它
一些方法(称为类方法)做用在类上面。
• 属性被看做是访问对象的数据的接口,对象的数据一般用字段来存储。属性有存取设定,它决定数
据如何被读取或修改。从程序的其它地方(在对象自己之外)来看,属性在很大程度上就像一个字
段(但本质上它至关于方法,好比在类的实例中并不为它分配内存)。
对象被动态分配一个内存块,内存结构由类类型决定。每一个对象拥有类中所定义的每一个字段的惟一拷贝,
但一个类的全部实例共享相同的方法。对象分别经过称为构造函数和析构函数的方法建立和销毁。
一个类变量实际是一个指针,它引用一个对象(称它为对象引用),因此,多个变量能够指向同一个对象。
像其它指针同样,一个类变量的值能够是nil。虽然类变量是一个指针,但咱们直接用它来表示一个对象,
例如,SomeObject.Size := 100 是把对象的Size 属性设为100,你不能用下面的命令给它赋值:
SomeObject^.Size := 100。html
Class types(类类型)
类类型必须在实例化以前进行声明并给定一个名称(不能在变量声明中定义一个类类型),你只能在程序
(program)或单元(unit)的最外层声明类,而不能在过程或函数中声明。
一个类声明有以下格式
type className = class (ancestorClass)
memberList
end;
这里,className 是任何有效标志符,(ancestorClass)是可选的,memberList 声明类的各成员,也就是
它的字段、方法和属性。若你省略了(ancestorClass),则新定义的类直接继承自内置的类TObject。如
果包含(ancestorClass)而且memberList 是空的,你能够省略end。一个类声明也能够包括它实现的接
口列表,请参考Implementing interfaces。
在类声明中,方法看起来就像函数(或过程)头,而没有函数(或过程)体。方法的定义出如今程序的
其它地方。
好比,这里是Classes 单元中TMemoryStream 类的声明
type
TMemoryStream = class(TCustomMemoryStream)
private
FCapacity: Longint;
procedure SetCapacity(NewCapacity: Longint);
protected
function Realloc(var NewCapacity: Longint): Pointer; virtual;
property Capacity: Longint read FCapacity write SetCapacity;
public
destructor Destroy; override;
procedure Clear;
procedure LoadFromStream(Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SetSize(NewSize: Longint); override;
function Write(const Buffer; Count: Longint): Longint; override;
end;
TMemoryStream 是TStream(在Classes 单元中)的后代,继承了它的大部分红员,但又定义(或从新定
义)了几个方法和属性,包括它的析构(destructor)函数Destroy。它的构造函数Create 从TObject 继承,
没有任何改变,因此没有从新声明。每一个成员被声明为private、protected 或者public(这个类没有published
成员),关于这些术语的解释,请参考Visibility of class members。
给定上面的声明,你能够像下面同样建立TMemoryStream 的一个实例:
var stream: TMemoryStream;
stream := TMemoryStream.Create;node
Inheritance and scope(继承和做用域)
当你声明一个类时,能够指定它的父类,好比,
type TSomeControl = class(TControl);
定义了一个叫作TSomeControl 的类,它继承自TControl。一个类自动从它的父类继承全部的成员,且可
以声明新成员,也能够从新定义继承下来的成员,但不能删除祖先类定义的成员。因此,TSomeControl
包含了在TControl 以及它的每一个祖先中定义的全部成员。
类成员标志符的做用域开始于它声明的地方,直到类声明的结束,而且扩展到它的全部子类声明的地方,
以及类和子类的全部方法的定义区(也就是方法的定义部分)。数据库
TObject and TClass(TObject 和TClass)
类TObject 在System 单元声明,是全部其它类的最终祖先。TObject 只定义了少数方法,包括一个基本
的构造函数和析构函数。除了TObject,System 单元还声明了一个类引用类型TClass。
TClass = class of TObject;
若是在类声明中没有指定父类,则它直接继承于TObject,因此
type TMyClass = class
...
end;
等同于
type TMyClass = class(TObject)
...
end;
后者可读性较好,推荐使用。数组
Compatibility of class types(类类型兼容性)
类和它的祖先类是赋值兼容的,因此,某个类类型的变量能引用它的任何子类类型的实例。好比,在下
面的声明中
type
TFigure = class(TObject);
TRectangle = class(TFigure);
TSquare = class(TRectangle);
var
Fig: TFigure;
变量Fig 能被赋予TFigure、TRectangle 和TSquare 类型的值。服务器
Object types(Object 类型)
除了类类型,你可使用以下语法声明一个object 类型
type objectTypeName = object (ancestorObjectType)
memberList
end;
这里,objectTypeName 是任何有效标志符,(ancestorObjectType)是可选的,memberList 声明字段、方法
和属性。若(ancestorObjectType)被省略了,则新类型没有祖先。Object 类型不能有published 成员。
由于object 类型不是从TObject 继承,它们没有内置的构造函数和析构函数,也没有其它方法。你能使
用New 过程建立Object 类型的实例,并使用Dispose 过程销毁它们,你也能够像使用记录同样,采用简
单方式声明object 类型的变量。
Object 类型只是为了向后兼容性,不推荐使用它们。less
Visibility of class members(类成员的可见性)
类的每一个成员都有一个称为可见性的属性,咱们用下面的关键字之一来表示它:private、protected、
public、published 和automated。好比
published property Color: TColor read GetColor write SetColor;
声明一个叫作Color 的published 属性。可见性决定了一个成员在哪些地方以及如何能被访问,private
表示最小程度的访问能力,protected 表示中等程度的访问能力,public、published 和automated 表示最
大程度的访问能力。
若声明一个成员时没有指定其可见性,则它和前面的成员拥有相同的可见性;若在类声明的开始没有指
定可见性,当在{$M+}状态下编译类时(或者继承自一个在{$M+}状态下编译的类),它的默承认见性是
published,不然,它的可见性是public。
为可读性考虑,最好在声明类时用可见性来组织成员:把全部的private 成员组织在一块儿,接下来是全部
的protected 成员,依此类推。用这种方法,每一个可见性关键字最多出现一次,而且标明了每一个新段的开
始。因此,一个典型的类声明应该像下面的形式:
type
TMyClass = class(TControl)
private
... { private declarations here}
protected
... { protected declarations here }
public
... { public declarations here }
published
... { published declarations here }
end;
经过从新声明,你能够在派生类中增大一个成员的可见性,但你不能下降它的可见性。好比,一个protected
属性在派生类中能被改变为public,但不能改成private。还有,published 成员在子类中不能改成public。
要了解更多信息,请参考Property overrides and redeclarations。
Private, protected, and public members(私有、受保护和公有成员)
Private 成员在声明它的单元或程序以外是不可用的,换句话说,一个private 方法不能从另外一个模块
(module)进行调用,也不能从另外一个模块读取或写入一个私有的字段或属性。经过把相关类的声明放
在一个模块中,可使它们拥有访问其它类的私有成员的能力,同时又不会增大这些成员的访问范围。
Protected 成员在声明它的类的模块中是随处可用的,而且在它的派生类中也是可用的,而无论派生类出
如今哪一个模块。换句话说,在派生类的全部方法定义中,你能够调用protected 方法,也能读取或写入
protected 字段或属性。只有在派生类的实现中才应用的成员一般使用protected 属性。
对于public 成员,只要能使用类的地方都是可用的。
Published members(公布的成员)
Published 成员和public 成员具备相同的可见性,不一样之处是published 成员会产生RTTI 信息。RTTI
使应用程序能动态查询一个对象的字段和属性,也能定位它的方法。RTTI 用于在存储文件和从文件导入
时访问属性的值,也用于在Object Inspector 中显示属性,而且能为一些特定属性(叫作事件)关联特定
的方法(叫作事件处理程序)。
公布属性的数据类型受到限制,有序类型、字符串、类、接口和方法指针能被公布;当集合类型的基础
类型是有序类型,而且上界和下界介于0 到31 之间时(换句话说,集合必须符合byte、word 或double
word),集合类型也是能够公布的;除了Real48,任何实数类型都是能够公布的;数组类型的属性(区
别于数组属性,array properties)不能是公布的。
一些属性虽然是能够公布的,但不能彻底支持流系统,它们包括:记录类型的属性、全部可公布类型的
数组属性以及包含匿名值的枚举类型的属性。若是published 属性属于前面所述的类型,Object Inspector
不能正确显示它们,而且使用流向磁盘操做时也不能保存它们的值。
全部方法都是能够公布的,但一个类不能使用相同的名字公布两个或以上数目的被重载的方法。只有当
字段属于类或接口类型时,它才是能够公布的。
A class cannot have published members unless it is compiled in the {$M+} state or descends from a class
compiled in the {$M+} state. Most classes with published members derive from TPersistent, which is compiled
in the {$M+} state, so it is seldom necessary to use the $M directive.
除非一个类是在{$M+}状态下被编译,或者派生于一个在{$M+}状态下被编译的类,不然它不能有公布
的成员。大多数具备公布成员的类继承自TPersistent,而它是在{$M+}状态下被编译的,因此一般不多
使用$M 编译器指示字。
Automated members(自动化成员)
Automated 成员和public 成员具备相同的可见性,不一样之处是automated 成员会产生自动化类型信息
(Automation type information,自动化服务器须要)。Automated 成员只出如今Windows 类中,不推荐在
Linux 程序中使用。保留关键字automated 是为了向后兼容性,ComObj 单元的TAutoObject 类不使用自
动化成员。
对声明为automated 类型的方法和属性有如下限制:
• 全部属性、数组属性的参数、方法的参数以及函数的结果,它们的类型必须是自动化类型,包括Byte、
Currency、Real、Double、Longint、Integer、Single、Smallint、AnsiString、WideString、TDateTime、
Variant、OleVariant、WordBool 和全部接口类型。
• 方法声明必须使用默认的register 调用约定,它们能够是虚方法,但不能是动态方法。
• 属性声明能够包含访问限定符(读和写),但不能包含其它限定符(index、stored、default 和nodefault)。
访问限定符指定的方法必须使用默认的register 调用约定,而且限定符不能使用字段。
• 属性声明必须指定一个类型,而且属性不支持覆盖(override)。
Automated 方法或属性声明中能够包含dispid 指示字,但指定一个已经使用的ID 会致使错误。
在Windows 中,这个指示字的后面必须跟一个整数常数,它为成员指定一个Automation dispatch ID。否
则,编译器自动为它指定一个ID,这个ID 等于类(包括它的祖先类)的方法或属性使用的最大ID 加上
1。关于自动化的更多信息,请参考Automation objects。
Forward declarations and mutually dependent classes(Forward 声明
和相互依赖的类)
若声明一个类时以class 和分号结束,也就是有下面的格式,
type className = class;
在class 后面没有列出父类,也没有成员列表,这是一个forward 声明。Forward 声明的类必须在同一个
声明区域进行定义声明,换句话说,在forward 声明和它的定义声明之间除了类型声明外,不能有任何
其它内容。
Forward 声明容许建立相互依赖的类,好比
type
TFigure = class; // forward 声明
TDrawing = class
Figure: TFigure;
...
end;
TFigure = class // 定义声明
Drawing: TDrawing;
...
end;
不要把forward 声明和继承自TObject、不包含任何类成员的完整类声明混淆:
type
TFirstClass = class; // 这是forward 声明
TSecondClass = class // 这是一个完整的类声明
end; //
TThirdClass = class(TObject); // 这是一个完整的类声明
Fields(字段)
字段就像属于对象的一个变量,它能够是任何类型,包括类类型(也就是说,字段能够存储对象的引用)。
字段一般具备private 属性。
给类定义字段很是简单,就像声明变量同样。字段声明必须出如今属性声明和方法声明以前,好比,下
面的声明建立了一个叫作TNumber 的类,除了继承自TObject 的方法以外,它有一个惟一的整数类型的
成员Int。
type TNumber = class
Int: Integer;
end;
字段是静态绑定的,也就是说,它们的引用在编译时是固定的。要理解上面的意思,请考虑下面的代码:
type
TAncestor = class
Value: Integer;
end;
TDescendant = class(TAncestor)
Value: string; // 隐藏了继承的Value 字段
end;
var
MyObject: TAncestor;
begin
MyObject := TDescendant.Create;
MyObject.Value := 'Hello!'; // 错误
TDescendant(MyObject).Value := 'Hello!'; // 工做正常
end;
虽然MyObject 存储了TDescendant 的一个实例,但它是以TAncestor 声明的,因此,编译器把
MyObject.Value 解释为TAncestor 声明的整数字段。不过要知道,在TDescendant 对象中,这两个字段都
是存在的,继承下来的字段被新字段隐藏了,但能够经过类型转换对它进行操做。
Methods(方法)
Methods: Overview(概述)
方法是一个和类相关联的过程或函数,调用一个方法需指定它做用的对象(如果类方法,则指定类),比
如,
SomeObject.Free
调用SomeObject 的Free 方法。dom
Method declarations and implementations(方法声明和实现)
在类声明中,方法看起来像过程头和函数头,工做起来像forward 声明。在类声明后的某个地方(必须
属于同一模块),每一个方法必须有一个定义声明来实现它。好比,假设TMyClass 类声明包含一个叫作
DoSomething 的方法:
type
TMyClass = class(TObject)
...
procedure DoSomething;
...
end;
DoSomething 的定义声明必须在模块的后面出现:
procedure TMyClass.DoSomething;
begin
...
end;
虽然类声明既能够出如今单元的interface 部分,也能够出如今implementation 部分,但类方法的实现(定
义声明)必须出如今implementation 部分。
在定义声明的头部,方法名老是使用类名进行限定。在方法的头部能够从新列出类声明时的参数,若这
样作的话,参数的顺序、类型以及名称必须彻底相同,若方法是函数的话,返回值也必须相同。
方法声明可包含一些特殊指示字,而它们不会出如今其它函数或过程当中。指示字应当只出如今类声明中,
而且如下面的顺序列出:
reintroduce; overload; binding; calling convention; abstract; warning
这里,ide
binding 是virtual、dynamic 或override;函数
calling convention 是register、pascal、cdecl、stdcall 或safecall;性能
warning 是platform、deprecated 或library。
Inherited(继承)
关键字inherited 在实现多态行为时扮演着特殊角色,它出如今方法定义中,后面跟一个标志符或者不跟。
若inherited 后面跟一个成员名称,它表示一个一般的方法调用,或者是引用一个属性或字段(except that
the search for the referenced member begins with the immediate ancestor of the enclosing method’s class)。比
如,当
inherited Create(...);
出如今方法定义中时,它调用继承的Create 方法。
When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing
method. In this case, inherited takes no explicit parameters, but passes to the inherited method the same
parameters with which the enclosing method was called. For example,
当inherited 后面没有标志符时,它指的是和当前方法同名的继承下来的方法。在这种状况下,inherited
没有明确指定参数,但把当前使用的参数传给继承下来的方法。好比,
inherited;
常常出如今构造函数的实现中,它把相同的参数传给继承下来的构造函数。
Self(Self 变量)
在实现方法时,标志符Self 引用方法所属的对象。好比,下面是Classes 单元中TCollection 的Add 方法
的实现:
function TCollection.Add: TCollectionItem;
begin
Result := FItemClass.Create(Self);
end;
Add 方法调用FItemClass 的Create 方法,而FItemClass 所属的类老是TCollectionItem 的派生类,
TCollectionItem.Create 有一个TCollection 类型的单一参数,因此,Add 把此时TCollection 的实例传给它,
这如下面的代码表示:
var MyCollection: TCollection;
...
MyCollection.Add // MyCollection 被传给TCollectionItem.Create 方法
Self 在不少方面都有用,好比,一个在类中声明的成员(标志符)可能在方法中被从新声明,这种状况
下,你可使用Self.Identifier 来访问原来的成员。
关于类方法中的Self,请参考Class methods。
Method binding(方法绑定)
Method binding: Overview(概述)
方法分为静态方法(默认)、虚方法和动态方法。虚方法和动态方法能被覆盖,它们但是是抽象的。当某
个类类型的变量存储的是它的派生类时,它们的意义开始发挥做用,它们决定了调用方法时哪一种实现被
执行。
Static methods(静态方法)
方法默认是静态的。当调用一个静态方法时,类或对象被声明的类型决定了哪一种实现被执行(编译时决
定)。在下面的例子中,Draw 方法是静态的。
type
TFigure = class
procedure Draw;
end;
TRectangle = class(TFigure)
procedure Draw;
end;
给定上面的声明,下面的代码演示了静态方法执行时的结果。在第2 个Figure.Draw 中,变量Figure 引
用的是一个TRectangle 类型的对象,但却执行TFigure 中的Draw 方法,由于Figure 变量声明的类型是
TFigure。
var
Figure: TFigure;
Rectangle: TRectangle;
begin
Figure := TFigure.Create;
Figure.Draw; // 调用TFigure.Draw
Figure.Destroy;
Figure := TRectangle.Create;
Figure.Draw; // 调用TFigure.Draw
TRectangle(Figure).Draw; // 调用TRectangle.Draw
Figure.Destroy;
Rectangle := TRectangle.Create;
Rectangle.Draw; // 调用TRectangle.Draw
Rectangle.Destroy;
end;
Virtual and dynamic methods(虚方法和动态方法)
要实现虚方法或动态方法,在声明时包含virtual 或dynamic 指示字。不像静态方法,虚方法和动态方
法能在派生类中被覆盖。当调用一个被覆盖的方法时,类或对象的实际类型决定了哪一种实现被调用(运
行时),而不是它们被声明的类型。
要覆盖一个方法,使用override 指示字从新声明它就能够了。声明被覆盖的方法时,它的参数的类型和
顺序以及返回值(如有的话)必须和祖先类相同。
在下面的例子中,TFigure 中声明的Draw 方法在它的两个派生类中被覆盖了。
type
TFigure = class
procedure Draw; virtual;
end;
TRectangle = class(TFigure)
procedure Draw; override;
end;
TEllipse = class(TFigure)
procedure Draw; override;
end;
给定上面的声明,下面代码演示了虚方法被调用时的结果,在运行时,执行方法的变量,它的实际类型
是变化的。
var
Figure: TFigure;
begin
Figure := TRectangle.Create;
Figure.Draw; // 调用TRectangle.Draw
Figure.Destroy;
Figure := TEllipse.Create;
Figure.Draw; // 调用TEllipse.Draw
Figure.Destroy;
end;
只有虚方法和动态方法能被覆盖,可是,全部方法都能被重载,请参考Overloading methods。
Virtual versus dynamic(比较虚方法和动态方法)
虚方法和动态方法在语义上是相同的,惟一的不一样是在运行时决定方法调用的实现方式上,虚方法在速
度上进行了优化,而动态方法在代码大小上作了优化。
一般状况下,虚方法是实现多态行为的最有效的实现方式。当基类声明了大量的要被许多派生类继承的
(可覆盖的)方法、但只是偶尔才覆盖时,动态方法仍是比较有用的。
Overriding versus hiding(比较覆盖和隐藏)
在声明方法时,若是它和继承的方法具备相同的名称和参数,但不包含override,则新方法仅仅是隐藏
了继承下来的方法,并无覆盖它。这样,两个方法在派生类中都存在,方法名是静态绑定的。好比,
type
T1 = class(TObject)
procedure Act; virtual;
end;
T2 = class(T1)
procedure Act; // 从新声明Act,但没有覆盖
end;
var
SomeObject: T1;
begin
SomeObject := T2.Create;
SomeObject.Act; // 调用T1.Act
end;
Reintroduce(从新引入)
reintroduce 指示字告诉编译器,当隐藏一个先前声明的虚方法时,不给出警告信息。好比,
procedure DoSomething; reintroduce; // 父类也有一个DoSomething 方法
当要使用新方法隐藏继承下来的虚方法时,使用reintroduce 指示字。
Abstract methods(抽象方法)
抽象方法是虚方法或动态方法,而且在声明它的类中没有实现,而是由它的派生类来实现。声明抽象方
法时,必须在virtual 或dynamic 后面使用abstract 指示字。好比,
procedure DoSomething; virtual; abstract;
只有当抽象方法在一个类中被覆盖时,你才能使用这个类或它的实例进行调用。
Overloading methods(重载方法)
一个方法可使用overload 指示字来从新声明,此时,若从新声明的方法和祖先类的方法具备不一样的参
数,它只是重载了这个方法,并无隐藏它。当在派生类中调用此方法时,依靠参数来决定到底调用哪
一个。
若要重载一个虚方法,在派生类中从新声明时使用reintroduce 指示字。好比,
type
T1 = class(TObject)
procedure Test(I: Integer); overload; virtual;
end;
T2 = class(T1)
procedure Test(S: string); reintroduce; overload;
end;
...
SomeObject := T2.Create;
SomeObject.Test('Hello!'); // 调用T2.Test
SomeObject.Test(7); // 调用T1.Test
在一个类中,你不能以相同的名字公布(published)多个重载的方法,维护RTTI 信息要求每个公布
的成员具备不一样的名字。
type
TSomeClass = class
published
function Func(P: Integer): Integer;
function Func(P: Boolean): Integer // 错误
...
做为属性读写限定符的方法不能被重载。
实现重载的方法时,必须重复列出类声明时方法的参数列表。关于重载的更多信息,请参考Overloading
procedures and functions。
Constructors(构造函数)
构造函数是一个特殊的方法,用来建立和初始化一个实例对象。声明一个构造函数就像声明一个过程,
但以constructor 开头。好比:
constructor Create;
constructor Create(AOwner: TComponent);
构造函数必须使用默认的register 调用约定。虽然声明中没有指定返回值,但构造函数返回它建立的对
象引用,或者对它进行调用的对象(的引用)。
一个类的构造函数能够不止一个,但大部分只有一个。按惯例,构造函数一般命名为Create。
要建立一个对象,在类(标志符)上调用构造函数。好比,
MyObject := TMyClass.Create;
它在堆中为对象分配内存,并设置全部的有序类型的字段为0,把nil 赋给全部的指针和类类型的字段,
使全部的字符串类型的字段为空;接下来,构造函数中指定的其它动做(命令)开始执行,一般,初始
化对象是基于传给构造函数的参数值;最后,构造函数返回新建立的对象的引用,此时它已完成了初始
化。返回值的类型与调用构造函数的类相同。
当使用类引用来调用构造函数时,若执行过程当中发生了异常,则自动调用析构函数Destroy 来销毁不完
整的对象。
当使用对象引用来调用构造函数时(而不是使用类引用),它不是建立一个对象;取而代之的是,构造函
数做用在指定的对象上,它只是执行构造函数中的命令语句,而后返回一个对象的引用(是怎样的对象
引用,和调用它的同样吗?)。使用对象引用来调用构造函数时,一般和关键字inherited 一块儿使用来调
用一个继承的构造函数。
下面是一个类和构造函数的例子。
type
TShape = class(TGraphicControl)
private
FPen: TPen;
FBrush: TBrush;
procedure PenChanged(Sender: TObject);
procedure BrushChanged(Sender: TObject);
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
...
end;
constructor TShape.Create(Owner: TComponent);
begin
inherited Create(Owner); // 初始化继承下来的部分
Width := 65; // 改变继承下来的属性
Height := 65;
FPen := TPen.Create; // 初始化新字段
FPen.OnChange := PenChanged;
FBrush := TBrush.Create;
FBrush.OnChange := BrushChanged;
end;
构造函数的第一步,一般是调用继承下来的构造函数,对继承的字段进行初始化;而后对派生类中新引
入的字段进行初始化。由于构造函数老是把为新对象分配的内存进行“清零”(clear),因此,对象的所
有字段开始时都是0(有序类型)、nil(指针和类)、空(字符串)或者Unassigned(变体类型)。因此,
除非字段的值不为0 或者空值,咱们不必在构造函数中初始化各字段。
当使用类标志符调用构造函数时,声明为虚方法的构造函数和声明为静态时是相同的。可是,当和类引
用(class-reference)结合使用时,虚构造函数容许使用多态,也就是说,在编译时,对象的类型是未知
的(参考Class references)。
Destructors(析构函数)
析构函数是一个特殊的方法,用来销毁调用的对象而且释放它的内存。声明一个析构函数就像声明一个
过程,但以destructor 开头。好比:
destructor Destroy;
destructor Destroy; override;
析构函数必须使用默认的register 调用约定。虽然一个类的析构函数能够不止一个,但推荐每一个类覆盖
继承下来的Destroy 方法,并再也不声明其它析构函数。
要调用析构函数,必须使用一个实例对象的引用。好比,
MyObject.Destroy;
当析构函数被调用时,它里面的命令首先被执行,一般,这包括销毁全部的嵌入对象以及释放为对象分
配的资源;接下来,为对象分配的内存被清除。
下面是一个析构函数实现的例子:
destructor TShape.Destroy;
begin
FBrush.Free;
FPen.Free;
Classes and objects
- 107 -
inherited Destroy;
end;
析构函数的最后一步,一般是调用继承下来的析构函数,用来销毁继承的字段。
When an exception is raised during creation of an object, Destroy is automatically called to dispose of the
unfinished object. This means that Destroy must be prepared to dispose of partially constructed objects. Because
a constructor sets the fields of a new object to zero or empty values before performing other actions, class-type
and pointer-type fields in a partially constructed object are always nil. A destructor should therefore check for
nil values before operating on class-type or pointer-type fields. Calling the Free method (defined in TObject),
rather than Destroy, offers a convenient way of checking for nil values before destroying an object.
当建立对象时发生了异常,会自动调用析构函数来清除不完整的对象,这表示析构函数必须准备好来清
除只构建了一部分的对象。由于构造函数在执行其它动做以前先设置新对象的字段为0 或空值,在一个
只构建了一部分的对象中,类类型和指针类型的字段老是nil,因此,在操做类类型和指针类型的字段时,
析构函数必须检查它们是否为nil。销毁一个对象时调用Free 方法(在TObject 中定义)而不是Destroy
会更加方便,由于前者会检查对象是否为nil。
Message methods(Message 方法)
Message 方法用来响应动态分派的消息。Message 方法在各个平台上都是支持的,VCL 使用message 方
法来响应Windows 消息,CLX 不使用message 方法来响应系统事件。
在声明方法时,经过包含message 指示字来建立一个message 方法,并在message 后面跟一个介于1 到
49151 之间的整数常量,它指定消息的号码(ID)。对于VCL 控件(control),message 方法中的整数常
量能够是Messages 单元中定义的Windows 消息号码,这里还定义了相应的记录类型。一个message 方
法必须是具备一个单一var 参数的过程。
好比,在Windows 下:
type
TTextBox = class(TCustomControl)
private
procedure WMChar(var Message: TWMChar); message WM_CHAR;
...
end;
好比,在Linux 或在跨平台的状况下,你要以以下方式处理消息:
const
ID_REFRESH = $0001;
type
TTextBox = class(TCustomControl)
private
procedure Refresh(var Message: TMessageRecordType); message ID_REFRESH;
...
end;
Message 方法没必要包含override 指示字来覆盖一个继承的message 方法。实际上,在覆盖方法时也没必要
指定相同的方法名称和参数类型,而只要一个消息号码就决定了这个方法响应哪一个消息和是否覆盖一个
方法。
Implementing message methods(实现message 方法)
The implementation of a message method can call the inherited message method, as in this example (for
Windows):
实现一个message 方法时,能够调用继承的message 方法,就像下面的例子(适用于Windows):
procedure TTextBox.WMChar(var Message: TWMChar);
begin
if Chr(Message.CharCode) = #13 then
ProcessEnter
else
inherited;
end;
在Linux 或跨平台的状况下,你要以以下方式实现一样的目的:
procedure TTextBox.Refresh(var Message: TMessageRecordType);
begin
if Chr(Message.Code) = #13 then
...
else
inherited;
end;
命令inherited 按类的层次结构向后寻找,它将调用和当前方法具备相同消息号码的第一个(message)
方法,并把消息记录(参数)自动传给它。若是没有祖先类实现message 方法来响应给定的消息号码,
inherited 调用TObject 的DefaultHandler 方法。
DefaultHandler 没有作任何事,只是简单地返回而已。经过覆盖DefaultHandler,一个类能够实现本身对
消息的响应。在Windows 下,VCL 控件(control)的DefaultHandler 方法调用Windows 的DefWindowProc
(API)函数。
Message dispatching(消息分派)
消息处理函数不多直接调用,相反,消息是经过继承自TObject 的Dispatch 方法来分派给对象的。
procedure Dispatch(var Message);
传给Dispatch 的参数Message 必须是一个记录,而且它的第一个字段是Cardinal 类型,用来存储消息号
码。
Dispatch 按类的层次结构向后搜索(从调用对象所属的类开始),它将调用和传给它的消息具备相同号码
的message 方法。若没有发现指定号码的message 方法,Dispatch 调用DefaultHandler。
Properties(属性)
Properties: Overview(概述)
属性就像一个字段,它定义了对象的一个特征。但字段仅仅是一个存储位置,它的内容能够被查看和修
改;而属性经过读取或修改它的值与特定的行为关联起来。属性经过操纵一个对象的特征来提供对它的
控制,它们还使特征能被计算。
声明属性时要指定名称和类型,而且至少包含一个访问限定符。属性声明的语法是
property propertyName[indexes]: type index integerConstant specifiers;
这里
• propertyName 是任何有效标志符;
• [indexes]是可选的,它是用分号隔开的参数声明序列,每一个参数声明具备以下形式:identifier1, ...,
identifiern: type。更多信息请参考Array properties;
• type 必须是内置的或前面声明的数据类型,也就是说,像property Num: 0..9 ...这样的属性声明是非
法的;
• index integerConstant 子句是可选的。更多信息请参考Index specifiers;
• specifiers 是由read、write、stored、default(或nodefault)和implements 限定符组成的序列。每
个属性声明必须至少包含一个read 或write 限定符。
属性由它们的访问限定符定义。不像字段,属性不能做为var 参数传递,也不能使用@运算符,缘由是
属性不必定(是不必定,仍是必定不呢?)在内存中存在。好比,它可能有一个读方法从数据库中检索
一个值或者产生一个随机数值。
Property access(属性访问)
每一个属性有一个读限定符,一个写限定符,或二者都有,它们称为访问限定符,具备如下的格式
read fieldOrMethod
write fieldOrMethod
这里,fieldOrMethod 是一个字段或方法名,它们既能够和属性在同一个类中声明,也能够在祖先类中声
明。
• 若是fieldOrMethod 和属性是在同一个类中声明的,它必须出如今属性声明的前面;若是它是在祖先
类中声明的,则它对派生类必须是可见的,也就是说,若祖先类在不一样的单元声明,则fieldOrMethod
不能是私有的字段或方法;
• 若fieldOrMethod 是一个字段,它的类型和属性必须相同;
• 若fieldOrMethod 是一个方法,它不能是重载的,并且,对于公布的属性,访问方法必须使用默认的
register 调用约定;
• 在读限定符中,若fieldOrMethod 是一个方法,它必须是一个不带参数的函数,而且返回值和属性具
有相同的类型;
• 在写限定符中,若fieldOrMethod 是一个方法,它必须是一个带有单一值参(传值)或常量参数的过
程,这个参数和属性具备相同的类型;
好比,给定下面的声明
property Color: TColor read GetColor write SetColor;
GetColor 方法必须被声明为:
function GetColor: TColor;
SetColor 方法必须被声明为下面之一:
procedure SetColor(Value: TColor);
procedure SetColor(const Value: TColor);
(固然,SetColor 的参数名没必要非得是Value。)
当在表达式中使用属性时,经过在读限定符中列出的字段或方法读取它的值;当在赋值语句中使用属性
时,经过写限定符列出的字段或方法对它进行写入。
在下面的例子中,咱们声明了一个叫作TCompass 的类,它有一个公布的属性Heading。Heading 的值通
过FHeading 字段读取,写入时使用SetHeading 过程。
type
THeading = 0..359;
TCompass = class(TControl)
private
FHeading: THeading;
procedure SetHeading(Value: THeading);
published
property Heading: THeading read FHeading write SetHeading;
...
end;
给出上面的声明,语句
if Compass.Heading = 180 then GoingSouth;
Compass.Heading := 135;
对应于
if Compass.FHeading = 180 then GoingSouth;
Compass.SetHeading(135);
在TCompass 类中,读取Heading 属性时没有执行任何命令,只是取回存储在FHeading 字段的值;另外一
方面,给Heading 赋值变成了对SetHeading 方法的调用,咱们推测,它的操做将是把新值存储在FHeading
字段,还可能包括其它命令。好比,SetHeading 可能以以下方式实现:
procedure TCompass.SetHeading(Value: THeading);
begin
if FHeading <> Value then
begin
FHeading := Value;
Repaint; // 刷新用户界面来反映新值
end;
end;
若声明属性时只有读限定符,则它是只读属性;若只有写限定符,则它是只写属性。当给一个只读属性
赋值,或在表达式中使用只写属性时都将产生错误。
Array properties(数组属性)
数组属性是被索引的属性,它们能表示像下面的一些事物:列表中的条目、一个控件的子控件和位图中
的象素等等。
声明数组属性时包含一个参数列表,它指定索引的名称和类型,好比,
property Objects[Index: Integer]: TObject read GetObject write SetObject;
property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;
property Values[const Name: string]: string read GetValue write SetValue;
索引参数列表的格式和过程(或函数)的参数列表相同,除了使用中括号取代了圆括号。不像数组只使
用有序类型的索引,数组属性的索引能使用任何类型。
对数组属性,访问限定符必须使用方法而不是字段。读限定符的方法必须是一个函数,它的参数数目、
类型以及顺序必须和索引中列出的一致,而且返回值和属性是同一类型;对写限定符,它必须是一个过
程,这个过程必须使用索引中列出的参数,包括数目、类型以及顺序必须相同,另外再加一个和属性具
有相同类型的值参(传值)或常量参数。
好比,前面的属性可能具备以下的访问方法声明:
function GetObject(Index: Integer): TObject;
function GetPixel(X, Y: Integer): TColor;
function GetValue(const Name: string): string;
procedure SetObject(Index: Integer; Value: TObject);
procedure SetPixel(X, Y: Integer; Value: TColor);
procedure SetValue(const Name, Value: string);
一个数组属性经过使用属性索引来进行访问。好比,语句
if Collection.Objects[0] = nil then Exit;
Canvas.Pixels[10, 20] := clRed;
Params.Values['PATH'] := 'C:\DELPHI\BIN';
对应于
if Collection.GetObject(0) = nil then Exit;
Canvas.SetPixel(10, 20, clRed);
Params.SetValue('PATH', 'C:\DELPHI\BIN');
在Linux 下,上面的例子你要使用像“/usr/local/bin”的路径取代“C:\DELPHI\BIN”。
定义数组属性时能够在后面使用default 指示字,此时,数组属性变成类的默认属性。好比,
type
TStringArray = class
public
property Strings[Index: Integer]: string ...; default;
...
end;
若一个类有默认属性,你能使用缩写词object[index]来访问这个属性,它就至关于object.property[index]。
好比,给定上面的声明,StringArray.Strings[7]能够缩写为StringArray[7]。一个类只能有一个默认属性,
在派生类中改变或隐藏默认属性可能致使没法预知的行为,由于编译器老是静态绑定一个对象地默认属
性。
Index specifiers(索引限定符)
索引限定符能使几个属性共用同一个访问方法来表示不一样的值。索引限定符包含index 指示字,并在后
面跟一个介于-2147483647 到2147483647 之间的整数常量。若一个属性有索引限定符,它的读写限定符
必须是方法而不能是字段。好比,
type
TRectangle = class
private
FCoordinates: array[0..3] of Longint;
function GetCoordinate(Index: Integer): Longint;
procedure SetCoordinate(Index: Integer; Value: Longint);
public
property Left: Longint index 0 read GetCoordinate write SetCoordinate;
property Top: Longint index 1 read GetCoordinate write SetCoordinate;
property Right: Longint index 2 read GetCoordinate write SetCoordinate;
property Bottom: Longint index 3 read GetCoordinate write SetCoordinate;
property Coordinates[Index: Integer]: Longint read GetCoordinate write
SetCoordinate;
...
end;
对于有索引限定符的属性,它的访问方法必须有一个额外的整数类型的值参:对于读取函数,它必须是
最后一个参数;对于写入过程,它必须是倒数第2 个参数(在指定属性值的参数以前)。当程序访问属性
时,属性的整数常量自动传给访问方法。
给出上面的声明,若Rectangle 属于TRectangle 类型,则
Rectangle.Right := Rectangle.Left + 100;
对应于
Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);
Storage specifiers(存储限定符)
可选指示字stored、default 和nodefault 被称为存储限定符,它们对程序的行为没有影响,但决定了RTTI
的维护方式,它们决定是否把公布属性的值存储到窗体文件中。
stored 指示字后面必须跟True、False、Boolean 类型的字段名或者一个返回Boolean 值的无参数方法。
好比,
property Name: TComponentName read FName write SetName stored False;
若一个属性没有stored 指示字,就至关于指定了stored True。
default 指示字后面必须跟随一个和属性具备相同类型的常量,好比,
property Tag: Longint read FTag write FTag default 0;
要覆盖一个继承下来的默认值而不指定新值,使用nodefault 指示字。default 和nodefault 只支持有序类
型和集合类型(当它的基础类型是有序类型,而且上下边界都在0 到31 之间时)。若声明属性时没有使
用default 或者nodefault,它被看成nodefault 看待。对于实数、指针和字符串,它们分别有隐含的默认
值0、nil 和 ' '(空串)
当保存一个组件的状态时,组件中公布属性的存储限定符会被检查,若属性的当前值和默认值不一样(或
没有默认值),而且stored 为True,则它的值就会被保存;不然,属性的值不被保存。
注意:存储限定符不支持数组属性。在声明数组属性时,指示字default 有不一样的意义。
Property overrides and redeclarations(属性的覆盖和从新声明)
声明时没有指定类型的属性称为属性覆盖,它容许你改变一个属性继承下来的可见性或限定符。最简单
的覆盖只包含关键字property、并在后面跟属性标志符,这种方式用来改变属性的可见性。好比,祖先
类声明了一个受保护的属性,派生类能够从新声明它为公有的或公布的。属性覆盖可包含read、write、
stored、default 和nodefault,它们覆盖了继承下来的相应指示字。覆盖能够取代访问限定符、添加限定
符或增大属性的可见性,但不能删除访问限定符或下降可见性。覆盖可包含implements 指示字,它添加
能够实现的接口,但不能删除继承下来的那些。
下面的声明演示了属性覆盖的使用:
type
TAncestor = class
...
protected
property Size: Integer read FSize;
property Text: string read GetText write SetText;
property Color: TColor read FColor write SetColor stored False;
...
end;
type
TDerived = class(TAncestor)
...
protected
property Size write SetSize;
published
property Text;
property Color stored True default clBlue;
...
end;
覆盖的Size 属性添加了写限定符,容许属性能被修改;覆盖的Text 和Color 属性把可见性从protected
改变为published;覆盖的Color 属性还指定若它的值不为clBlue,它将被保存进文件。
若从新声明属性时包含类型标志符,这将隐藏继承下来的属性而不是覆盖它,也就是建立了一个(和继
承下来的属性)具备相同名称的新属性。任何指定类型的属性声明必须是完整的,也就至少要包含一个
访问限定符。
派生类中属性是隐藏仍是覆盖呢?属性的查找老是静态的,也就是说,对象(变量)声明的类型决定了
它的属性。因此,在下面的代码执行后,读取MyObject.Value 或给它赋值将调用Method1 或Method2,
即便MyObject 存储的是TDescendant 的一个实例;但你能够把MyObject 转换为TDescendant 来访问派
生类的属性和它们的访问限定符。
type
TAncestor = class
...
property Value: Integer read Method1 write Method2;
end;
TDescendant = class(TAncestor)
...
property Value: Integer read Method3 write Method4;
end;
var MyObject: TAncestor;
...
MyObject := TDescendant.Create;
Class references(类引用)
Class references: Overview(概述)
有时,咱们须要使用类自己而不是它的实例(也就是对象),好比,当使用类引用来调用构造函数时。你
老是能使用类名来引用一个类,但有时,你也须要声明变量或参数把类做为它的值,这种状况下,你需
要使用类引用类型。
Class-reference types(类引用类型)
类引用类型有时称为元类,用以下的构造形式表示
class of type
这里,type 是任何类类型。type(标志符)自己表示一个class of type(元类)类型的值。若type1 是type2
的祖先类,则class of type2(元类)和class of type1(元类)是赋值兼容的。这样
type TClass = class of TObject;
var AnyObj: TClass;
声明了一个叫作AnyObj 的变量,它能存储任何类引用。类引用类型的声明不能直接用于变量或参数声
明中。你能把nil 值赋给任何类引用变量。
要了解类引用类型如何使用,看一下TCollection(在Classes 单元)的构造函数声明:
type TCollectionItemClass = class of TCollectionItem;
...
constructor Create(ItemClass: TCollectionItemClass);
上面声明说,要建立一个TCollection 实例对象,你必须向构造函数传递一个类名,它属于TCollectionItem
类或是它的派生类。
当你调用一个类方法,或者调用一个类(或对象)的虚构造函数(编译时它们的类型不能肯定)时,类
引用是颇有用的。
类引用的用途就是建立在编译器没法肯定的对象,举个列子:
Type
TControlCls = Class of TControl;
function CreateComponent(ControlCls: TControlCls): TControl;
begin
result:=ControlCls.Create(Form1);
...
end;
调用时如:
CreateComponent(TMemo);//建立TMemo对象
CreateComponent(TEdit);//建立TEdit对象
Constructors and class references(构造函数和类引用)
构造函数可经过一个类引用类型的变量进行调用,这容许建立编译时类型并不肯定的对象。好比,
type TControlClass = class of TControl;
function CreateControl(ControlClass: TControlClass;
const ControlName: string; X, Y, W, H: Integer): TControl;
begin
Result := ControlClass.Create(MainForm);
with Result do
begin
Parent := MainForm;
Name := ControlName;
SetBounds(X, Y, W, H);
Visible := True;
end;
end;
CreateControl 函数须要一个类引用类型的参数,它指定建立何种控件,函数使用这个参数来调用构造函
数。由于类标志符(类名)表示一个类引用的值,因此能使用它做为参数来调用CreateControl 建立一个
实例。好比,
CreateControl(TEdit, 'Edit1', 10, 10, 100, 20);
使用类引用来调用的构造函数一般是虚方法,实际调用的构造函数(指实现)由运行时类引用的类型决定。
Class operators(类运算符)
Class operators: Overview(概述)
每一个类从TObject 继承了两个分别叫作ClassType 和ClassParent 的方法,前者返回对象的类引用,后者
返回对象的父类类引用。这两个方法的返回值都是TClass(这里TClass = class of TObject)类型,它们
能被转换为更加明确的类型。每一个类还继承了一个叫作InheritsFrom 的方法,它测试调用的对象是否从
一个指定的类派生而来(若是对象是类的一个实例,结果如何?)。这些方法被is 和as 运算符使用,很
少直接调用它们。
The is operator(is 运算符)
is 运算符执行动态类型检查,用来验证运行时一个对象的实际类型。
object is class
若object 对象是class 类的一个实例,或者是class 派生类的一个实例,上面的表达式返回True,不然返
回False(若object 是nil,则结果为False)。若是object 声明的类型和class 不相关,也就是说,若两个
类不一样而且其中一个不是另外一个的祖先,则发生编译错误。好比,
if ActiveControl is TEdit then TEdit(ActiveControl).SelectAll;
上面的语句先检查一个对象(变量)是不是TEdit 或它的派生类的一个实例,而后再决定是否把它转换
为TEdit。
The as operator(as 运算符)
as 运算符执行受检查的类型转换。表达式
object as class
返回和object 相同的对象引用,但它的类类型是class。在运行时,object 对象必须是class 类的一个实例,
或者是它的派生类的一个实例,或者是nil,不然将产生异常;若object 声明的类型和class 不相关,也
就是说,若两个类不一样而且其中一个不是另外一个的祖先,则发生编译错误。好比,
with Sender as TButton do
begin
Caption := '&Ok';
OnClick := OkClick;
end;
由于运算符优先权的问题,咱们常常须要把as 类型转换放在一对括号中,好比,
(Sender as TButton).Caption := '&Ok';
Class methods(类方法)
类方法是做用在类而不是对象上面的方法(不一样于构造函数)。类方法的定义必须以关键字class 开始,
好比,
type
TFigure = class
public
class function Supports(Operation: string): Boolean; virtual;
class procedure GetInfo(var Info: TFigureInfo); virtual;
...
end;
类方法的定义部分也必须以class 开始,好比,
class procedure TFigure.GetInfo(var Info: TFigureInfo);
begin
...
end;
在类方法的定义部分,Self 表示调用方法的类(which could be a descendant of the class in which it is
defined,它或许是定义方法的类的一个派生类)。若使用类C 调用方法,Self 的类型是class of C(元类)。
因此,你不能使用Self 访问字段、属性和日常的方法(由对象调用的方法),但能调用构造函数和其它
类方法。
类方法既能够经过类引用来调用,也可使用对象,当使用后者时, Self 值等于对象所属的类。
http://www.cnblogs.com/moon25/archive/2008/08/14/1267747.html