Classes and objects(類和對象)
類(或者類類型)定義了一個結構,它包括字段(也稱為域)、方法和屬性;類的實例叫做對象;類的字
段、方法和屬性被稱為它的部件(components)或成員。
• 字段在本質上是一個對象的變量。和記錄的字段類似,類的字段表示一個類實例的數據項;
• 方法是一個過程或者函數,它和類相關聯。絕大多數方法作用在對象(也就是類的實例)上,其它
一些方法(稱為類方法)作用在類上面。
• 屬性被看作是訪問對象的數據的接口,對象的數據通常用字段來存儲。屬性有存取設定,它決定數
據如何被讀取或修改。從程序的其它地方(在對象本身以外)來看,屬性在很大程度上就像一個字
段(但本質上它相當於方法,比如在類的實例中並不為它分配內存)。
對象被動態分配一個內存塊,內存結構由類類型決定。每個對象擁有類中所定義的每個字段的唯一拷貝,
但一個類的所有實例共享相同的方法。對象分別通過稱為構造函數和析構函數的方法創建和銷毀。
一個類變量實際是一個指針,它引用一個對象(稱它為對象引用),所以,多個變量可以指向同一個對象。
像其它指針一樣,一個類變量的值可以是nil。雖然類變量是一個指針,但我們直接用它來表示一個對象,
例如,SomeObject.Size := 100 是把對象的Size 屬性設為100,你不能用下面的命令給它賦值:
SomeObject^.Size := 100。
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;
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 類型只是為了向后兼容性,不推薦使用它們。
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 方法。
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
這里,
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