后一页
前一页
回目录
回首页
Delphi 4增订的Object Pascal

本附錄在於說明Delphi 4新增訂的Object Pascal程式語言。總結來說,Delphi 4的Object Pascal增訂了以下幾大部分:

新增加數種內定資料型態。

Dynamic Arrays
Method and Routine Overloading
Default Parameters.
Implementating interfaces by delegation,

可用以下的寫法:
property MyInterface: IMyInterface read FMyInterface
implements IMyInterface;

新增的內定資料型態
整數方面:

新增加Int64這種長度六十四位元,範圍從-2^63 to 2^63 - 1。
32-bit unsigned integer新增加Longword型態,範圍從0..4294967295。
Cardinal的範圍調整成與上述Longword型態相同。

浮點數方面:
Real型態基於效率考量,由過去佔用48-bit調整成64-bits,
新增加Real48型態,精確度與過去的Real相同,仍是48-bit。

Dynamic Array
Delphi 4問世之前,Delphi程式設計師若需要動態長度的陣列,在不得已的情況下,往往採用以下這種「表面上陣列的語法,但實際自己配置管理記憶體」的方式來處理動態陣列:
#0001 procedure TForm1.Button1Click(Sender: TObject);
#0002 type
#0003 TIntegerArray = array[0..0] of integer;
#0004 PIntegerArray = ^TIntegerArray;
#0005 var
#0006 pArrayOfInteger: PIntegerArray;
#0007 j: integer;
#0008 begin
#0009 GetMem(pArrayOfInteger, 10 * SizeOf(Integer));
#0010 for j := 0 to 9 do
#0011 pArrayOfInteger^[j] := j;
#0012 FreeMem(pArrayOfInteger, 10 * SizeOf(Integer));
#0013 end;

如果應用Delphi 4新增加的Dynamic Array,則可改用以下的方式:

var MyFlexibleArray: array of Real;

定義之後,以 SetLength 函數改變陣列實際配置的記憶體大小,例如:

SetLength(MyFlexibleArray, 20); // 0..19

    方便很多,是嗎?若想知道Dynamic Array實際長度,請分別以HighLow函數判斷,傳回「-1」時,表示是一個該陣列的長度為零,例如:

#0001 procedure TForm1.Button2Click(Sender: TObject);
#0002 var
#0003 A: array of Integer;
#0004 begin
#0005 ShowMessage(IntToStr(High(A))); // -1
#0006 SetLength(A, 3);
#0007 ShowMessage(IntToStr(High(A))); // 3
#0008 A := nil;
#0009 ShowMessage(IntToStr(High(A))); // -1
#0010 end;

    上述的0008這列,指定nil值將會釋放陣列所配置到的記憶。
    值得注意的是,Compiler對於Dynamic Array並不會自動進行所謂的「Copy-on-Write」。請看以下的程式例:

#0001 procedure TForm1.Button1Click(Sender: TObject);
#0002 var
#0003 A, B: array of Integer;
#0004 begin
#0005 SetLength(A, 1);
#0006 A[0] := 1;
#0007 B := A;
#0008 B[0] := 2;
#0009 ShowMessage(IntToStr(A[0])); // ==> 2
#0010 end;

    0008這列改的雖是B[0],但Compiler顯然還沒有聰明到在可能修改陣列內容時,將陣列內容複製一份出來(Copy on (possible) Write),於是,0009這列程式顯示的結果仍是2。
於是,若要進行兩陣列各索引項目一對一的內容複製,只好自行寫迴圈一一複製,或者,採用較為簡便的寫法 ── 呼叫Copy函數:

#0001 procedure TForm1.Button4Click(Sender: TObject);
#0002 var
#0003 A, B: array of Integer;
#0004 begin
#0005 SetLength(A, 1);
#0006 A[0] := 1;
#0007 B := Copy(A, 0, 1);
#0008 B[0] := 2;
#0009 ShowMessage(IntToStr(A[0])); // ==> 1
#0010 ShowMessage(IntToStr(B[0])); // ==> 2
#0011 end;

    最後,請不要將Dynamic Array與第二章提到的Open Array(開放陣列)搞混了。函式參數的開放陣列,語法雖與Dynamic Array差不多,但開放陣列指的是:不限定傳入函式的陣列長度,Dynamic Array則是指:可變長度的陣列,兩者畢竟有所不同。以下是一則我準備的程式範例,不難看出兩者之間的差異:

#0001 type
#0002 TDynamicInteger = array of integer;

#0003
#0004 procedure Clear_OpenArray(var A: array of Integer);
#0005 var
#0006 I: Integer;
#0007 begin
#0008 for I := 0 to High(A) do A[I] := 0;
#0009 // SetLength(A, 20); // 不可以這麼寫
#0010 end;
#0011
#0012 // 注意, 以下兩種寫法又有不同喔
#0013 // I: procedure Clear_DynamicArray(A:TDynamicInteger);
#0014 // II: procedure Clear_DynamicArray(var A:TDynamicInteger);
#0015 procedure Clear_DynamicArray(var A: TDynamicInteger);
#0016 var
#0017 I: Integer;
#0018 begin
#0019 for I := 0 to High(A) do A[I] := 0;
#0020 SetLength(A, 20); // 如果是Dynamic Array, 可以這麼寫
#0021 end;
#0022
#0023 procedure TForm1.Button3Click(Sender: TObject);
#0024 var
#0025 A: TDynamicInteger;
#0026 begin
#0027 SetLength(A, 3);
#0028 Clear_OpenArray(A);
#0029 ShowMessage(IntToStr(High(A))); // -1
#0030 Clear_DynamicArray(A);
#0031 // 以下這列的結果, 要看 Cleary_DynamicArray的
#0032 // 參數有沒有寫 var
#0033 ShowMessage(IntToStr(High(A)));
#0034 end;

    請幫我注意0004的Clear_OpenArray,傳入的是「由整數構成的陣列,不限定陣列的長度」,0015的Clear_DynamicArray的寫法則是「接受一個可變動陣列長度的動態陣列」,若不這樣寫,當場就變成了開放陣列了。

Method and Routine Overloading
    所謂的Method Overloading,簡單地說:一個以上的函式使用相同的函式名稱。這項特性是Object Pascal長久以來一直有人想要卻一直沒有支援的期盼,終於,Delphi 4多了一個保留字:overload ── 相同名稱的兩個函式,只要在宣告時額外加上overload,即使函式的參數型態不同,Delphi也會視呼叫當時傳入的參數資料型態,決定該呼叫哪一個函式。例如以下這則例子:

#0001 // 兩個 Divide 都必須寫overload保留字
#0002 function Divide(X, Y: Double): Double; overload;
#0003 begin
#0004 Result := X / Y;
#0005 end;
#0006
#0007 function Divide(X, Y: Integer):Integer;overload;
#0008 begin
#0009 Result := X div Y;
#0010 end;
#0011
#0012 procedure TForm1.Button1Click(Sender: TObject);
#0013 var
#0014 a, b, c: integer;
#0015 i, j, k: Double;
#0016 begin
#0017 a := 10;
#0018 b := 3;
#0019 i := 10;
#0020 j := 3;
#0021 c := Divide(a, b);
#0022 k := Divide(i, j);
#0023 ShowMessage(IntToStr(c)); // 3
#0024 ShowMessage(FloatToStr(k)); // 3.333333...
#0025 end;

    請幫我注意到0002與0007這兩列的Divide函數,函數名稱相同,但傳入的參數型態與函數的傳回值並不相同,即使如此,0023與0024呼叫到Divide時,還是會視當時參數的型態型態,決定該呼叫哪一個Divide
    很方便,是嗎?有了這項特性,我們就可以寫出應用範圍更廣彈性更大的函數──卻不必為每一種情況各自取一個函數名稱,同時,這項特性也使得函數呼叫的方式更為一致,真是一項早該支援的特性。除了一般的程序與函數,物件的方法也可以採用overload寫法,對於「物件.方法」的撰寫與呼叫,肯定更能提供簡便與彈性。
    overload可不可以應用在不同的兩個單元呢?答案是可以的,但是完全相同的函式宣告不能再寫一次。例如上述程式的0007-0010,可以移到其他單元,原來的單元只要uses這個新的單元,函數呼叫時仍然有Overloading的效果。可是,如果將0007-0010的程式碼移到其他單元卻忘了刪除原來位置的程式,換句話說,兩個單元各有一個完全長得一樣的函式,編譯時Delphi就搞不清楚了,結果自然是無法編譯。當然啦!同一個單元同一個範圍發生這樣事也是不許的,各Overloading的函式,在函式宣告上多少總有些不同。

Default Parameters

    過去,函式如果定義了六個參數,那麼,寫作函式呼叫的原始碼時,就一定得乖乖地傳入六個參數,不許多也不許少。若是應用「Default Parameters」,可以只傳入五個(或者更少)。那...,那其他沒給的參數怎麼辦?沒關係,函式內部的程式將自動以預設值代入,稍後我會說明其寫法。
    舉例來說,Windows API中有一個蠻好用的函數叫MessageBox,呼叫此函數將出現一通用對話盒,不管是顯示訊息或者徵詢使用者意見,都十分好用。像是以下這道敘述將產生如次頁的對話盒,提醒使用者定期備份資料:

Application.MessageBox('程式要結束了喔! 請記得定期備份資料',
'訊息', MB_OK + MB_ICONINFORMATION);

    除了顯示資料,經由最後一個參數,彈性地搭配各位元旗標,就可以製作出不同按鈕的對話盒,詢問使用者「是」、「否」、「取消」等簡單的問題。
應用Default Parameters的寫法,我們可以寫一個類似以下的函數:

#0001 function MyMessageBox(Prompt: string;#0002 Caption: string = '訊息';
#0003 Flag: LongInt = MB_OK +MB_ICONINFORMATION): integer;
#0004 begin
#0005 Result := Application.MessageBox(
#0006 PChar(Prompt), PChar(Caption), Flag);
#0007 end;
#0008
#0009 procedure TForm1.Button1Click(Sender: TObject);
#0010 begin
#0011 MyMessageBox('程式要結束了喔! 請記得定期備份資料');
#0012 end;

    如此一來,我們既能有一個完整支援MessageBox的函數,只想顯示文字時,也能有一個精簡型式的函數可用。大家應該明顯看得出來:不必寫成兩個函數。
觀察上述程式的寫法,其實也蠻容易的,只不過是在定義函數參數時,一併給定初值。
不過,實際動手去寫時,還是會發現一些限制。繼續這個例子來說,參數的預設值一定得是常數,另外,如果某一個參數開始給預設值,接在後頭的參數也必須用Default Parameters的寫法。為什麼得有這項限制?以上例來說,如果0001-0003我改成:

#0001 function MyMessageBox(Prompt: string;
#0002 Caption: string = '訊息';
#0003 Flag: LongInt): integer;

    那麼,只傳入兩個參數進去時,Delphi怎麼知道第二個參數該是Captioin或者是「Caption按預設值,第二個參數代入Flag」。Visual Basic對此的解法是在呼叫函數時,一併寫明各參數名稱與值的對應,由於這個緣故,Visual Basic甚至允許參數的次序不同也沒關係,因為,各參數與其值的對應關係在呼叫函式時已一併註明清楚。很可惜的,Delphi 4雖有Default Parametets,但還不支援以下的函數呼叫方式:

MyMessageBox('程式要結束了喔! 請記得定期備份資料', Flag = ...);

    雖然如此,上述「如果某一個參數開始給預設值,接在後頭的參數也必須用Default Parameters的寫法」這項限制,多少還是可以搭配前一節的Overloading加以彌補:

#0001 function MyMessageBox(Prompt: string;
#0002 Caption: string = '訊息';
#0003 Flag: LongInt = MB_OK + MB_ICONINFORMATION): integer; overload;
#0004 begin
#0005 Result := Application.MessageBox(
#0006 PChar(Prompt), PChar(Caption), Flag);
#0007 end;
#0008
#0009 function MyMessageBox(Prompt: string;
#0010 Flag: LongInt;
#0011 Caption: string = '訊息'
#0012 ): integer; overload;

#0013 begin
#0014 Result := Application.MessageBox(
#0015 PChar(Prompt), PChar(Caption), Flag);
#0016 end;
#0017
#0018 procedure TForm1.Button1Click(Sender: TObject);
#0019 begin
#0020 MyMessageBox('程式要結束了喔! 請記得定期備份資料');
#0021 MyMessageBox('別說我沒告訴你喔!', MB_OK);
#0022 end;

(好煩的程式,一再提醒要記得備份。:p)

     請幫我注意這次改寫過的程式0009-0012,我重新調整了各參數的次序,並且分別在兩同名函數都加上了overload保留字,這樣,Delphi就知道該呼叫的是哪一個了。




后一页
前一页
回目录
回首页