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