以下是我在自學Delphi的時候,對一些注意點的簡單總結,並沒有什么系統性可言,只是一個學習時順手記下的筆記,主要為了當時加深對知識的印象,並沒有希望能在以后的復習和使用Delphi中有什么多大的參考作用。
缺少Delphi的各種數據類型的介紹……知識點。
1. Delphi編譯器自動對全局變量符初值。當程序開始時,所有的整型變量賦值為0,浮點數賦值為0.0,指針為null,字符串為空等等,因此,在源代碼中不必對全局變量賦0初值
2. Object Pascal允許在const和var聲明的時候,用編譯期間的函數,這些函數包括Ord()、Chr()、Trunc()、Round()、High()、Low()、Sizeof(),例如下面的所有代碼都是合法的
type A = array[1..2] of Integer; const w: Word = Sizeof(Byte); var i: Integer = 8; j: SmallInt = Ord(‘a’); L: Longint = Trunc(3.1415926); x: ShortInt = Round(2,71828); B1: Byte = High(A); B2: Byte = Low(A); C: char = Chr(46); //注意在定義變量的類型並賦值時,使用 = 而不是 :=
3. 如果要移植Delphi1.0的16位代碼,請記住,無論是Integer還是Cardinal類型都已經從16位擴展到32位。更准確的說,在Deiphi2和Delphi3中,Cardinal被看做是31位的無符號證書,在Delphi4以后,Cardinal才真正成為32位的無符號數
4. 警告:在Delphi1、2和3中,Real是6字節的浮點數,這是Pascal特有的數據類型,和其他的語言不兼容。在Delphi4中,Real是Double類型的別名,6字節的浮點數仍然有,但現在是Real48.通過編譯開關 {$REALCOMPATIBILITY ON} 可以是Real仍代表6字節的浮點數
5. 雖然AnsiString在外表上跟以前的字符串類型幾乎相同,單它是動態分配的並有自動回收功能,正是因為這個功能AnsiString有時被稱為是生存期自管理類型。Object Pascal能根據需要為字符串分配空間,所以不用像在C/C++中所擔心的為中間結果分配緩沖區。另外,AnsiString字符串總是以null字符結束的,這使得AnsiString字符串能與Win32 API中的字符串兼容。實際上,AnsiString類型是一個指向堆棧中的字符串結構的指針
6. Borland沒有將長字符串類型的內部結構寫到文檔里,並倒流了在Delphi后續版本中修改長字符串內部格式的權利以下的介紹主要是幫助你理解AnsiString類型是怎么工作的,並且要避免直接依賴於AnsiString結構的代碼。
程序員如果在Delphi 1.0中避免了使用字符串的內部結構,則把代碼移植到Delphi 2.0沒有任何問題。而依賴於字符串內部結構寫代碼的程序在移植到Delphi 2.0時必須修改。
AnsiString字符串類型有引用技術的功能,這表示幾個字符串都能指向相同的物理地址。因此,復制字符串因為僅僅是復制了指針而不是復制實際的字符串而變得非常快
當兩個或更多的AnsiString類型共享一個指向相同物理地址的引用時,Delphi內存管理使用了copy-on-write技術,一個字符串要等到修改結束,才釋放一個引用並分配一個物理字符串,例子:
var S1, S2: string begin S1 := ‘And now for something…;’ //給S1賦值,S1的引用計數為1 S2 := S1; //現在S2和S1指向同一個字符串,S1的引用計數為2 S2 := S2 + ‘completely different’; //S2現在改變了,所以它被復制到自己的物理空間,並且S1的引用計數減1 end;
7. 除了AnsiString以外,Delphi還提供了其他幾種生存期自管理類型,這些類型包括:WideString、Variant、OleVariant、interface、dispinterface和動態數組
生存期自管理類型,又被稱為自動回收類型,是指那些在被使用時就占用一定的特殊資源,而在它離開作用域時自動釋放資源的類型。當然不同的類型使用不同的資源,例如AnsiString類型在被使用時就為字符串占用內存,當它超出作用域時就釋放被字符串占用的內存。
8. 再練習將一個字符串轉換為PChar類型時要小心,因為字符串在超出其作用范圍時有自動回收的功能,因此當進行P := PChar(Stri)的賦值時,P的作用域(生存期)應當大於Str的作用域
9. 不要存放比分配給字符串的空間長度更長的字符,如果聲明一個變量是string[8],並試圖對這個變量賦值為’a_pretty_darn_long_string’,這個字符串將被截取為僅有8個字符,就會丟失數據。
10. 跟AnsiString類型字符串不一樣,ShortString跟以null結尾的字符串不兼容,正因為如此,用ShortString調用Win32函數時,要做一些工作。
Win32 API函數需要以null結尾的字符串,不要把ShortString字符串傳遞給API函數,因為編譯器將報錯,長字符串可以傳遞給Win32 API函數。
下面這個S h o r t S t r i n g A s P C h a r ( )函數是在S T R U T I L S . PA S單元中定義的。func function ShortStringAsPChar(var S:ShortString):PChar;
這函數能使一個字符串以n u l l結尾,這樣就能傳遞給需要P C h a r類型參數的Win32 API函數,如果字符串超過2 5 4個字符,多出的部分將被截掉
11. 記住在以后Delphi版本中一個字符的長度要從一個字節變成兩個字節,因此,不能家丁一個字符的長度為一個字節,Sizeof() 就保證了不管字符長度是多少都能正確分配存
12. 有時候變量的類型在編譯期間是不能確定的,而Variant能夠在運行期間動態地改變類型,這就是引入Variant類型的目的,例子,下面的代碼在編譯期間和運行期間都是正確的
var V: Variant; begin V :=’Delphi is Great!’; //Variant此時是一個字符串 V :=1; //Variant此時是一個整數 V :=123.2; //Variant此時是一個浮點數 V :=true; //Variant此時是一個布爾值 V :=GreateOleObject(‘word.Basic’); //Variant此時是一個OLE對象 end;
Variant能支持所有簡單的數據類型,例如整型、浮點型、字符串、布爾型、日期和時間、貨幣以及OLE自動化對象等。注意Variant不能表達Object Pascal對象。Variant可以表達不均勻的數組(數組的長度是可變的,它的數據元素能表達前面介紹過的任何一種類型,也可以包括另一個Variant數組)
13. 作為一條普遍的規則,請不要直接訪問TVarData的數據域。如果確實需要直接訪問,必須清楚正在干什么。
14. OleVariant與Variant很相像,兩者的差別在於OleVariant僅支持自動化相兼容的類型。目前,不能跟自動化兼容的VType是VarString,即AnsiString類型。當試圖把AnsiString字符串賦值給一個OleVariant變量時,AnsiString自動轉化為OLEBSTR類型並作為varOleStr存儲在Variant變量中。
15. Object Pascal允許你建立任何類型變量的數組(除了文件類型),下面的例子聲明了一個數組,這個數組有八個整型數:
var A: Array[0..7] of Integer
它相當於C語言中的
int A[8];
Object Pascal的數組有一個不同於其他語言的特性,他們的下標不必以某個數為基准。像下面的例子,能從28開始定義一個有3個元素的數組
var A: Array[28..30] of Integer;
因為Object Pascal的下標不是必須從0或1開始的,在for循環中使用數組時一定要小心。在編譯器中有兩個內置的函數High()和Low(),它們分別返回一個數組變量或數組類型的上邊界和下邊界,在for循環中使用這兩個語句能使程序更加穩定和易於維護,例子:
var A: Array[28..30] of Integer; I: Integer; begin for i:= Low(A) to High(A) do //不要在循環中硬編碼 A[i] := i; end;
16. 定義多維數組,用逗號分開,例子
var
A: array[1..2, 1..2] of Integer;
為了訪問多維數組,在方括號里用逗號隔開每一維
I := A[1, 2];
17. 利用引用對動態數組進行操作,在語義上類似於AnsiString類型,而不像普通的數組。這里有一個小實驗:在下面的代碼運行完成后A1[0]的值是多少?
var A1, A2 : array of Integer; begin SetLength(A1, 4); A2 := A1; A1[0] := 1; A2[0] := 26;
正確答案是26,因為賦值語句A2 := A1並不是創建新的數組,僅僅是將A1數組的引用賦給A2,因此對A2數組的任何操作都影響到A1,如果想用A1的完全拷貝賦值給A2,用標准過程Copy: A2 := Copy(A1); 當這行代碼運行結束, A 1和A 2是兩個獨立的數組,但它們的初始值是相同的,改變其中的一個不會影響到另一個.
18. 在Object Pascal中用戶自定義的結構被稱為記錄。它相當於C語言中的struct
{Pascal} Type MyRec = record i: Integer; d: Double; end; /*C*/ Typedef struct{ int i; double d; }MyRec;
下面的例子演示一個可變記錄,其中Double、Integer和char共同占用相同的內存
type TVariantRecord = record; NullStrField : Pchar; IntField : Integer; case Integer of 0: (D: Double); 1:(I : Integer); 2:(C: char); end;
注意,Object Pascal規則聲明:一個記錄的可變部分不能是生存期自管理的類型。
上面的在C語言中的定義可以是
struct TUnionStruct{\
char * StrField;
int IntField;
union{
double D;
int I;
char c;
};
};
19. 一個集合最多只能有255個元素。另外,只有有序的類型才能跟關鍵字set of
集合在內部以位的形式存儲它的元素,這使得在速度和內存利用上更有效。集合如果少於32個元素,它就存儲在CPU的寄存器中,這樣效率就更高了,為了用集合類型得到更高的效率。記住,集合的基本類型的元素數目要小於32。
20. 在集合中,盡可能使用Include()和Exclude()來增刪元素,盡可能的少用+、-運算符。因為Include()和Exclude()僅需要一條機器指令,而+和-需要13+6n(n是喝的按位的長度)條機器指令
21. 常用的判斷集合中是否有某幾個元素的例子
用*運算符來計算兩個集合的交集,表達式S e t 1 * S e t 2的結果是產生的集合的元素在S e t 1和S e t 2集合中都存在,下面的例子用來判斷在一個給定的集合中是否有某幾個元素:
if {'a', 'b', 'c'}*CharSet={'a', 'b', 'c'} then
/ /繼續程序
22. Object Pascal中對象的定義大致如下
Type TChildObject = class(TparentObject); SomeVar :Integer; procedure SomeProc; end; 雖然Delphi的對象和C++中的對象不完全一致,但上面的代碼大致和在C++中的如下定義相同: Class TChildObject : public TParentObject{ int SomeVar; void SomeProc(); };
要定義一個方法,類似於定義函數和過程,只是要加上對象名字和小圓點:
procedure TChildObject.SomeProc; begin {過程體} end;
Object Pascal中的小圓點,在功能上類似於C++的 :: 運算符。
23. 注意,Object Pascal的對象和C+的對象在內存中的布局不一樣。因此,在Delphi中不能用C++的對象。
一個例外的情況是,在Borland C++ Builder中創建的新類,用_declspec(delphiclass)指令可以直接映射為Object Pascal類。但這樣的類與普通的C++對象不兼容。
24. 一個指針變量指示了內存的位置,Pascal通用指針類型的名稱是Pointer。Pointer有時又被成為無類型指針,因為他只指向內存地址,單邊一起並不管指針指向的數據,這一點與Pascal嚴謹的風格似乎不相稱,所以建議你在大部分情況下用有類型的指針。
對於有類型指針來說,編譯器能准確地跟蹤指針所指向內容的數據類型,這樣用指針變量,編譯器就能跟蹤正在進行的工作。例子
Type Pint = ^Integer; //PInt現在是一個指向Integer的指針 Foo = record GobbledyGook: string; Snarf: Real; end; PFoo = ^Foo; //PFoo是一個指向Foo類型的指針 var P: Pointer; //一個無類型指針 P2: PFoo; //PFoo的實例
記住:一個指針變量僅僅是存出一個內存的地址,為指針所指向的內容分配空間是程序員要干的工作。
25. 要訪問一個指針所指向的內容,在指針變量的名字后面跟上 ^ 運算符,這種方法成為對指針去內容,例子
Program PtrTest; Type MyRec = record I : Integer; S : string R : Real; end; PMyRec = ^MyRec; var Rec : PMyRec; begin New(Rec); //為Rec分配內存 Rec^.I := 10; //為Rec中的域賦值 Rec^.S := ‘And now for something completely different.’; Rec^.R := 3.14; {Rec現在滿了} Dispose(Rec); //不要忘記釋放空間 end;
26. 當編譯器不知道要分配多少內存時,就要用到GetMem()和AllocMem(),在對PChar和Pointer類型分配內存時,編譯器不可能提前告訴你要分配多少,因為它們有長度可變特性。要注意,不要試圖操作分配空間以外的數據,因為這是導致“Access Violation”錯誤的最常見的原因。用FreeMem()來釋放由getMem()和AllocMem()分配的內存。順便說一下,AllocMem()要比GetMem()安全,因為AllocMem()總是把分配給他的內存初始化為零。
27. C程序員在學習Object Pascal時感到頭疼的是,Object Pascal對指針類型的檢查非常嚴格,例如,下面的代碼中變量a和變量b並不兼容:
var a: ^Integer; b: ^Integer;
相反,在C中它們兼容
int *a;
int *b;
Object Pascal認為每一個指針類型是相異的,為了把a的值賦給b,你必須建立一個新的類型,示例如下:
Type PtrInteger = ^Integer; //建立新類型 var a, b : PtrInteger; //現在a和b相兼容了
28. 注意,只有當兩個變量的數據長度一樣時,才能對變量進行強制類型轉換
29. 如果在一條if語句中有多個條件,你需要用括號把這幾個條件分別用括號括起來,例如: if (x=7) and (y=8) then
下面的寫法將會導致編譯器警告: if x=7 and y-8 then
if (condition) then begin ... end //注意這里不用;。因為為了保證這個if和下面的if else是一對 //如果使用了;。就表示if語句就在此結束 //那么下面的else if 語句就會報錯,因為不符合語法 else if (condition2) then begin ... end //注意這里不用;。因為為了保證這個else if和下面的 else 是一對 else begin ... end; //注意這里用; 如果else只有一句就可以是這樣的 else ...; 最后一定要有; 表示結束
特別注意自在if-else if-else里面的分號的問題
if (a>0) then edt1.Text := '>' //注意這里並不像上面的那個例程里面使用了begin和end,但是同樣的道理, //在if的下面的單條執行語句里面最后不能加分號(如果if下面有多條執行語句, //那么就要用begin和end括起來,這時候begin和end之間的每條語句都要用分號結束, //但是end后就不能用分號) else if(a<0) then edt1.Text := '<' //else if 的規則同 if 的規則 else edt1.Text := '='; //最后的else語句,如果else只有一條執行語句,那么這條執行語句必須以分號結尾。 //如果有多條執行語句,必須放到begin和end里面,這是不光每條執行語句需要以分號結尾, //end也要以分號結尾!
30. Pascal中的case語句就像是C/C++中的switch語句,下面的代碼是Pascal的case語句
case SomeIntegerVariable of 101 : DoSomething; 202 : begin DoSomethinf; DoSomeThingElse end; 303 : DoSomething; else DoTheDefault; end;
注意,case語句的選擇因子必須是有序類型,而不能是非有序的類型如字符串作為選擇因子。上面類似於C中的代碼:
switch(SomeIntegerVariable){ case 101 : DoSomething; break; case 202 : DoSomething; DoSomethingElse; break; case 303 : DoSomething; break; default : DoTheDefault; }
當變量等譯值1和值2時,都執行語句1,那么可以這樣寫
case (表達式) of 值1,值2 : 語句1;
31. 警告:在Delphi1.0中,允許對控制變量賦值;而從Delphi2.0開始,不再允許對控制變量賦值,因為32位編譯器對循環進行了優化。
32. 循環
for循環
循環體可以是簡單語句,也可以是復合語句,若是復合語句,需要用begin...end括起來
在循環體中可以使用continue和break語句,他們也通常位於if語句之后
var I, X : Integer; begin X := 0; for I := 1 to 10 do //to是遞增 inc(X, I); end;
var I, X : Integer; begin X := 0; for I := 10 downto 0 do //downto是遞減 X := X + I; end;
while循環
Program FileIt; {$APPTYPE COMSOLE} var f : TextFile; //一個文本文件 S : string; begin AssignFile(f, ‘foo.txt’); Reset(f); while not EOF(f) do begin readln(f, S); writeln(S); end; CloseFile(f); end;
repeat…until循環(首先執行循環體,執行后在判斷循環條件,所以循環體至少執行一次)
循環體可以是簡單語句,也可以是復合語句,對於復合語句,不需要用begin...end括起來
注意:在“循環條件”為False的時候執行循環,為True的時候退出循環,這一點要和while語句進行區別
var x : Integer begin X := 1; repeat inc(x); until x>100; end;
33. 過程和函數。一個過程是一段程序代碼,它在被調用時能執行某種特殊功能並能返回到調用它的地方。函數和過程類似,不同的是函數在返回到調用它的地方要返回一個值。Pascal中的過程相當於C/c++中的函數返回void,而Pascal中的函數相當於C/C++函數返回一個值。例子如下
Program FuncProc {$ApPTYE COMSOLE} procedure BiggerThanTen(i : Integer); {write something to the screen if I is greater than 10} begin if i >10 then writeln(‘Funky.’); end; function IsPositive(i : Integer): Boolean; {Return True if i is 0 or positive, False if i is negative} begin if i <0 then Result :=False; else Result := True; end; var Num : Integer; begin Num := 23; BiggerThanTen(Num); if IsPositive(Num) then writeln(Num, ‘is positive.’); else writeln(Num, ‘is negative’); end;
注意,在IsPositive()函數中的本地變量Result需要特別注意。每一個Object Pascal函數都有一個隱含的本地變量成為Result,它包含函數的返回值,注意這里和C/C++不一樣,把一個值賦給Result,函數並不會結束。
34. 值參數。將參數以值的形式傳遞是默認的傳遞方式,一個參數以值的形式傳遞意味着創建這個變量的本地副本,過程很函數對副本進行運算,所以不會改變實參,看下面的例子:procedure Foo(s : string);
當用這種方式調用一個過程時,一個字符串的副本就被創建,Foo()將對副本s進行運算,這表示對這個副本的任何修改不會影響原來的變量。
35. 引用參數。Pascal允許通過引用把變量傳遞給函數和過程。通過引用傳遞的參數有時被稱為是變量參數,通過引用傳遞意味着接受變量的函數和過程能夠改變變量的值。為了通過引用傳遞變量,在過程或函數的參數表中用關鍵字var:
Procedure ChageMe(var x : longint); begin x := 3; {x在調用過程中變了} end;
不同於復制x,關鍵字var使得變量的地址唄復制,因此變量值就能被直接改變。
36. 常量參數。如果不想使傳遞給函數或過程的參數被改變,就用const關鍵字來聲明它。關鍵字const不僅保護了變量的值不被修改,而且對於傳遞給函數或過程的字符串和記錄來說能產生更優化的代碼,下面的代碼就是一個過程聲明接受一個字符串常量參數: procedure Goon(const s : string);
37. 開放數組。開放數組參數能對過程和函數傳遞不確定數組,既可以傳遞相同類型的開放數組也可以傳遞不同類型的常量數組,下面的代碼聲明了一個函數,這個函數接受一個類型是Integer的開放數組參數:
var i, Rez : Integer; const j = 23; begin i := 8; Rez := AddEmup([I, 50, j, 89]);
為了在函數和過程中用開放數組,應該用High()、Low()和Sizeof()等函數來獲取關於開放數組的信息。下面的代碼是AddEmUp()函數的實現,它返回傳遞給參數A的所有元素的總和:
function AddEmUp(A: array of Integer) : Integer’ var i : Integer; begin Reault := 0; for i:= Low(A) to Hign(A) do inc(Result, A[i]); end;
Object Pascal還支持常量數組,這樣就能把不同類型的數據放在一個數組中傳遞給函數和過程,下面就是它的語法:
procedure WhatHaveIGot(A: array of const);
可以使用下面的語法調用上述函數:
WhatHaveIGot([‘Tabasco’, 90, 5, 6, @WhatHaveIGot, 3.14159, True, ‘s’]);
38. 作用域是指一個過程、函數和變量能被編譯器識別的范圍,例如,一個全局常量的作用域是整個程序,而一些過程中的局部變量的作用域就是那些過程。
39. 單元(unit)。單元是組成Pascal程序的單獨的源代碼模塊,單元有函數和過程組成,這些函數和過程能被主程序調用。一個單元至少要有以下三個部分組成:
*一個unit語句,每一個單元都必須在開頭有這樣一條語句,以標識單元的名稱,單元的名稱必須和文件名相匹配。例如,如果有一個文件名為FooBar,則unit語句可能是:
unit FooBar;
*interface部分。在unit語句后的源代碼必須是interface語句。在這條語句和implementation語句之間是能被程序和其他單元所共享的信息。一個單元的interface部分是聲明類型、常量、變量、過程和函數的地方,這些都能被主程序和其他單元調用。這里只能聲明,而不能有過程體和函數體。Interface語句應當只有一個單詞且在一行:
interface
*implementation部分。它在interface部分的后面。雖然單元的implementation包含過程和函數的源代碼,但它同時也允許在此聲明不被其他單元所調用的任何數據類型、常量和變量。Inplementation是定義在interface中聲明的過程和函數的地方,inplementation語句只有一個單詞並且在一行上:
implementation
一個單元能可選的包含其他兩個部分:
*initialization部分。在單元中它放在文件結尾前,它包含了用來初始化單元的代碼,它在主程序運行前運行並只運行一次。
*finalization部分。在單元中它放在initialization和end之間。finalization部分是在Delphi2.0引進的,在Delphi1.0中這部分用函數AddExitProc()增加一個推出過程來實現的,如果要把Delphi1.0程序移植過來,應該把退出過程中的代碼移植到這部分來。
注意。如果幾個單元都有initialization/finalization部分,則它們的執行順序和單元在主程序的uses子句中的出現順序一致。不要使initialization/finalization部分的代碼依賴於它們的執行順序,因為這樣的話,主程序的uses子句只要有小小的修改,就會導致程序無法通過編譯。
40. uses子句。Uses子句在一個程序或單元中用來列出想要包含近來的單元。例如,如果有一個程序名字為FooProg,它要用到在兩個UnitA和UnitB中的函數和類型,正確的uses聲明應該是:
Program FooProg; uses UnitA, UnitB; 單元能有兩個uses子句,一個在interface部分,一個在implementation部分。例 Unit FooBar; interface use BarFoo; {在這里進行全局聲明} implementation use BarFly; {在這里進行局部聲明} initialization {在這里進行單元的初始化} finalization {在這里進行單元的退出操作} end
41. 循環單元引用。你經常會碰到這樣的情況,在UnitA中調用UnitB,並在UnitB中調用UnitA,這就被稱為循環單元引用。循環單元引用的出現表明了程序設計有缺陷,應該在程序中避免使用循環單元引用。比較好的解決方法是把UnitA和UnitB共有的代碼轉移到第三個單元中。然而,有時確實需要用到循環單元引用,這時就必須把一個uses子句轉到implementation部分,而把兩一個留在interface部分,這樣就能解決問題。
42. 包。Delphi的包能把應用程序的部分代碼放到一個單獨的模塊中,它能被多個應用程序所共享。如果你有用Delphi1.0和Delphi2.0開發的代碼,利用包技術,再不修改任何代碼的情況下就能把他們利用起來。
可以把包看作是若干個單元集中在一起類似於DLL的形式存儲的模塊(Borland Pack、age Library或BPL文件)。應用程序在運行的時候鏈接上這些包中的單元而不是在編譯/鏈接時,因為這些單元的代碼存在於BPL文件而不是存在於EXE或DLL中,所以EXE和DLL的長度將變得更短。
包的語法。包通常是用報編輯器創建的。要啟動包編輯器,可以使用File | New | Package菜單項。包編輯器產生一個Delphi包源文件( DPK ),它將被編譯進一個包。DPK的語法相當簡單,其格式如下:
package PackageName requires Package1, Package2, …; //列在requires子句的包是這個包需要調用的其他包 contains Unit1, in ‘Unit1.pas’, Unit2, in ‘Unit2.pas’, …; //在contains下列出的單元是這個包所包含的單元,在contains子句下列出的單元將被編譯進這個包,注意在這里列出的單元不能同時被列在requires子句中的包所包含。另外,有這些單元所引用的單元也會間接地包含到暴力,除非它們已經被列在requires子句中 end