日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Object Pascal 中类型

發布時間:2025/3/15 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Object Pascal 中类型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Object Pascal 中類型的一些注意 2010-04-16 14:15

---------------------------------------------------------

原創文章,如轉載,請注明出處

---------------------------------------------------------

以下內容為備忘,你可能早已知道或者早已注意。

1, 區分類的forward聲明和繼承自TObject的子類
例如:
type
TFirst = class;?? //forward聲明, 類TFirst的具體聲明在該聲明區域后部
TSecond = class?? //一個完整的類定義,定義了類型TSecond
end;
TThird = class(TObject); //同樣是一個完整的類型定義,定義了類型TThird,
???? //這個類不包含任何數據成員和方法

2, constructor 與 destructor
我談兩點:用對象名和類名調用create的不同、構造和析構的虛與實
首先需要說的是,對象被動態分配內存塊,內存塊結構由類型決定,同時類型也決定了對該類型的“合法”操作!
一個對象的構造函數用來得到這個內存塊。
<1>, 用對象名和類名調用create的不同
構造函數是一個類方法,通常應該由類來調用,如下:
AMan := TMan.Create;
這條語句,會在堆中分配內存塊(當然,它不僅僅干這些,實際上它還會把類型中所有的有序類型字段置0,
置所有字符串為空,置所有指針類型為nil,所有variant為Unassigned;實際上,構造函數只是把內存塊進行
了清零,內存塊清零意味著對所有的數據成員清零),并把這個內存塊的首地址給AMan。
但如果你用下面的方式調用,
AMan2 := AMan.Create; //假設AMan已經被構造, 如未被構造,會產生運行時異常,
???????? //本質上,對未構造的對象的操作是對非法內存的操作,結果不
???????? //可預知!
這實際上相當于調用一個普通的方法,不會分配內存塊,當然也不會自動初始化。這和你調用下面的方法
類似,
??????? AMan.DoSomething; //DoSomething 為類 TMan的普通方法
當然,構造函數畢竟是函數,它會有一個返回值,這個返回值就是對象的地址,所以AMan2和AMan指向同
一地址。
Note:不要試圖通過用對象名調用create方法來實現對象初始化!!因為把構造函數當做普通的方法來調用
并不會實現自動初始化,所以用對象名來調用create方法就顯得很雞肋了,當然,某些場合,這是一種技巧。
<2>, 構造和析構的虛與實
構造函數和析構函數應該是虛的還是實的?這很疑惑!
看代碼:
---------------------------------
type
TMan = class(TObject)
public
?? constructor Create;
?? destructor Destroy;
end;

TChinese = class(TMan)
public
?? constructor create;
?? destructor Destroy;
end;

TBeijing = class(TChinese)
public
?? constructor Create;
?? destructor Destroy;
end;
....
??????? var
?? AMan, AMan2, AMan3: TMan;
?? AChinese: TChinese;
?? ABeijing: TBeijing;
begin
?? AMan := TChinese.Create;
?? AMan2 := TMan.Create;
?? AMan3 := TBeijing.Create;
??
?? AMan.Free;
?? AMan2.Free;
?? AMan3.Free;
end;
如果構造都是“實”的,無論如何,對于上面的用法,對象都可以被正確構造。但是對于析構,上述代碼有
問題!如果加入測試代碼,你會發現所有的析構方法根本不會被執行。知道,Free方法繼承自TObject,其
源碼如下:
procedure TObject.Free;
begin
?? if Self <> nil then
?? Destroy;
end;
constructor TObject.Create;
begin
end;

destructor TObject.Destroy;
begin
end;
Note:通常,self內置參數是指向對象本身,而在類方法中self是指向類本身。
很顯然,在前面的代碼中, AMan, AMan2, AMan3不是nil,free方法執行了。但是很遺憾的是,它所執行的
Destroy其實是基類TObject.Destroy; 而TObject.Destroy什么也不做。要理解這一點,需要理解類的內存組織
結構,理解“隱藏”的概念。所謂隱藏,指的是基類和子類有同名的方法(不考慮虛、動態的情況), 子類的該名
方法隱藏基類的方法名。運行時,到底調用哪一個方法取決于調用者類型。如下代碼:
type
TCountry = class
public
?? procedure Test;
end;
TChina = class(TCountry)
public
?? procedure Test;
end;

....

var
?? ACoun, ACoun1: TCountry;
?? AChina: TChina;
begin
?? ACoun := TCountry.Create;
?? ACoun1 := TChina.Create;
?? AChina := TChina.Create;

?? ACoun.Test; //調用TCountry.Test
?? ACoun1.Test; //調用TCountry.Test
?? AChina.Test; //調用TChina.Test

?? ACoun.Free;
?? AChina.Free;
end;
對于隱藏的情況,具體調用哪一個方法,取決于調用者本身的類型。
很顯然,一個實的析構方法有問題!可能會造成內存泄露。那么它們是虛的好了。問題來了,
type
TMan = class(TObject)
public
?? constructor Create;
?? destructor Destroy; virtual; override;
end;

TChinese = class(TMan)
public
?? constructor create;
?? destructor Destroy; virtual; override;
end;

TBeijing = class(TChinese)
public
?? constructor Create;
?? destructor Destroy; virtual; override;
end;
語法錯誤!上述的代碼編譯器會提示語法錯誤。ok,正確的應該是下面
TMan = class(TObject)
public
?? constructor Create;
?? destructor Destroy; override;
end;

TChinese = class(TMan)
public
?? constructor create;
?? destructor Destroy; override;
end;

TBeijing = class(TChinese)
public
?? constructor Create;
?? destructor Destroy; override;
end;
疑問來了,不是只有virtual 和 dynamic 才能被覆蓋么?在Delphi中為了保證對象被完全的析構,
所有的Destroy方法“天生”為虛方法,可以在任何地方被覆蓋!雖然這破壞了語言語法上的一致性,
但這保證一個對象不管從哪里(繼承)來,都只有唯一的析構方法。但是,編譯器不會強制你使用
override關鍵字,所以,你可以像下面這樣來定義析構。
TChinaese = class(TMan)
public
?? destructor Destroy;
end;
這可能會帶來問題,雖然不總是會帶來問題。因為在TChinese類中存在多個Destroy方法,分別是:
TObject.Destroy;
TMan.Destroy;
TChinese.Destroy;
所以,結論是,無論在什么地方定義Destroy,覆蓋它!!以確保整個類譜中只有一個Destroy;

****
至此,我們確信:一個實的構造方法可以工作,一個實的析構會有問題,析構應該總被override!
****
那么,虛的構造可以正常工作么?看代碼:
type
TMan = class(TObject)
public
?? constructor Create; virtual;
end;

TChinese = class(TMan)
public
?? constructor create; override;
end;

TBeijing = class(TChinese)
public
?? constructor Create; override;
end;
....
??????? var
?? AMan, AMan2, AMan3: TMan;
?? AChinese: TChinese;
?? ABeijing: TBeijing;
begin
?? AMan := TChinese.Create;
?? AMan2 := TMan.Create;
?? AMan3 := TBeijing.Create;
??
?? AMan.Free;
?? AMan2.Free;
?? AMan3.Free;
end;
Ok,可以,工作正常。原因在于我們對對象的使用方法。我們總是用"確定"的類名來構建的,這總可以保證
我們調用正確的方法。但如果我們使用類引用的話,情況不同了,如下:

type
?? TManClass = class of TMan;
var
?? AManClass: TManClass;
?? AObj, AObj2, AObj3: TMan;
begin
?? AManClass := TMan;?? //AManClass is TMan
?? AObj := AManClass.Create; //調用TMan.create

?? AManClass := TChinese; //AManClass is TChinese
?? AObj2 := AManClass.Create;??? //調用那一個create??

?? AManClass := TBeijing;
?? AObj3 := AManClass.Create;?? //which create???

?? ....
end;
和前面討論析構的情況類似,這取決于方法的內存布局,注意我在最初提到過類型決定布局。方法的內存布局
取決于它是virtual, override, overload, dynamic.當TMan.create 是virtual, 并且,TChinese.create是
override時,AObj2 := AManClass.Create 調用的是 TChinese.Create; 如果不是,則是TMan.Create;

上面的解釋仍然是疑惑的,問題的關鍵在于什么是“類引用”!從語義上說,類引用是這樣一個類型:它代表了
一系列相關的存在繼承關系的類型,一個類引用變量代表一系列類型。區別于一般的變量,一般的變量表示的
是一系列實體,如:var count: integer; 意思是你定義了一個實例count,它的類型是integer; count 對應
于堆或棧中的一個內存塊,count是數據。這是一般情況下我們定義一個變量所隱含的意思。但類引用變量稍有
不同,如上,AManClass變量,它代表的是一系列和TMan兼容的“類型”,它的類型是TManClass,它沒有內存塊
(你沒辦法說,一個類型的類型對應什么內存塊),實際上,可以認為類引用類型指向了一個“代碼塊”。類引用
變量的值是“類”!這很重要(稍后會進一步解釋)!Delphi對類引用的實現實際上就是一個32位的指針(一個普通
指針),它指向了類的虛方法表(VMT).
類引用的值是“類”,這決定了任何時候使用類引用變量只能調用類方法!一個構造函數是類方法,所以使用類
引用調用Create方法是合理的,但是你不可以使用類引用調用非類方法,比如Destroy方法,實際上在類引用中
你也找不到Free方法。
需要提及的是,構造函數雖然很接近于類方法,甚至于你也可以使用類方法來模擬構造函數:
TMan = class
public
?? class function Create: TMan;
end;

class function TMan.Create: TMan; //模擬構造函數
begin
?? result := inherited Create;
end;
但構造函數做的更多,除了前面提到的自動初始化外,構造函數還會在構造對象時將對象的VMT指針指向類的VMT。
此外,構造函數中的self指的是對象本身,類方法中self指的是類本身。總之,我們可以認為構造函數是一個
特殊的類函數。

Ok,到此,我們確信:類引用可以調用類方法,構造函數可以使用類引用來調用。
問題產生:由于類引用表示的是“一系列”類型,那么調用構造方法的時候,到底調用的是那一個構造方法呢?
對于TManClass類型變量而言,它只能調用的一定是TMan類的create方法,但如果TMan類的子類覆蓋了TMan的create
方法,調用TMan類的create方法實際上就是調用子類的create方法。這里的關鍵是理解“覆蓋”的概念。這實際上意味
著對使用類引用調用構造方法,虛的構造方法和實的構造方法是不同的。對于前面的例子而言,
AManClass := TChinese;
AObj2 := AManClass.create; //調用了TChinese.Create;

AManClass := TBeijing;
AObj3 := AManClass.create; //調用了TBeijing.Create;
但如果構造不是覆蓋虛函數,那么它們統統是調用TMan.Create;顯然這不是我們期望的。
Note:每個類有一個虛方法表(VMT),而不是每個對象有一個!

結論:構造函數是可以被類本身、類引用、對象調用,在這三種情況下構造函數的表現是不同的,在使用類引用的情況
下虛的構造通常是必要的。

那么,構造函數總是虛的是否更合理呢?我個人傾向于總是虛的更合理,因為這樣可以保證一個對象總是被正確的構造。
實際上,VCL中組件繼承的大多Create都被聲明成虛方法了。但Delphi并未將TObject的構造實現為虛,也許是出于兼容的
考慮,我不太清楚。我聽說,Java中的構造總是虛的。當然,總是虛的也有弊端,例如:你不可以通過調用Create方法來
只初始化父類的成員,因為在類的繼承體系中只有一個Create方法。但是靜態的構造可以實現父類的成員初始化。私下里,
我猜想也許是因為構造函數承擔了太多的義務,使得Delphi設計者沒有像析構那樣以總是虛來處理。

另,在C++中有一條法則,不要在構造中使用虛!但在Delphi中可以隨便,根本原因在于RTTI(運行時類型信息),Delphi在
運行時保存了完整的類型信息。這是C++和Delphi的重要不同之處。

?

總結:對析構函數而言,無論是否聲明為virtual,它總是虛的,你總需要使用override;對構造函數而言,虛和實取決
于的使用方法,但一般而言使用虛更安全,在使用類引用的情況下一定要保證子類覆蓋基類的構造方法。

總結

以上是生活随笔為你收集整理的Object Pascal 中类型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。