Delphi之數組 Object Pascal中可以建立豐富的數據類型。數組毫無疑問也是衆多自定義數據類型中的一種。 Type TA = array[0..9] of Integer; ... var A : TA; 和下面這段代碼通常效果是相同的(不同的地方在類型篇再說) var A : Array [0..9] of Integer; 這相當於C中的 int A[10]; 或Basic中的 Dim A(9) as Long或 Dim A(0 to 9) as Long 下面將分幾個方面講OP的數組: 多維數組: 多維數組的本質其實就是數組之數組。 type TA = array[0..9] of array [0..9] of Integer; TB = array[0..9, 0..9] of Integer; TCA = array[0..9] of Integer; TC = array[0..9] of TCA; 在這裏TA,TB,TC是等價的。在使用時是沒有分別的。例如X[0][0]:=1;Tx[0,0]:=1;(X是TA、TB、TC類型) 都是合法的。因爲這幾種類型都是在內存中開闢一塊100x100xSizeOf(Integer)的區域,你根本無法區 分他是如何申請的,也沒有必要去區分。 多維數組如何取得維數呢? 前面已經說過多維數組就是數組之數組,所以可以利用下面的方法來取得多維數組的維數: var k:array[2..10,3..20]of integer; begin showmessage(Inttostr(Low(k))+' '+Inttostr(High(k))); showmessage(Inttostr(Low(k[Low(K)]))+' '+Inttostr(High(k[Low(K)])));//k[n]是array[3..20] of integer;的數組
end; 動態數組: OP中動態數組的聲明是 type TA=Array of Integer; 動態數組應用中十分廣泛。現在有一種趨勢就是在數據結構中用動態數組代替鏈表 (到底哪個好哪個壞自有評價我們在這裏不予以討論)。 可能你會說動態數組根本不必要使用,我就從來沒有用過。 我不信你沒有用過動態數組! String類型你用過吧,它近似可以說是動態數組的一種。 動態數組的內存是在使用分配長度時才予以分配的,他的下界只能0(AnsiString字符串例外,下界是1,原因後面再說), 動態數組的生存期是自管理的使用後一般不用釋放,如果要強行釋放就用把Nil附給他。 使用動態數組往往愛犯的錯誤: 1)和靜態數組概念混淆: 動態數組的地址並不是他第一個元素的地址,而在靜態數組中我們大家常常使用這樣的語句: var A, B : array[0..19] of Integer; ... CopyMemory(@A, @B, 20*SizeOf(Integer)); 或者CopyMemory(@A[0], @B[0], 20*SizeOf(Integer)); 都是正確的,因爲靜態數組中數組的首地址就是他第一個元素的地址。
但是在動態數組中只有第二種寫法會得到正確結果。即只能寫成: var A, B : array of Integer; ... SetLength(A, 20); SetLength(B, 20); CopyMemory(@A[0], @B[0], 20*SizeOf(Integer)); 2)數組的附值: 靜態數組中的附值很簡單 var A, B : array[0..19] of Integer; ... A := B; 即可對數組進行賦值;在動態數組中就要倍加小心,請看 var A, B: array of Integer; begin Setlength(A, 10); SetLength(B, 10); B[1] := 50; A := B; B[1] := 100; end; a[1]是多少呢?按照常理A[1]的值應該是附值前的50,但恰恰相反A[1]的值是附值後B[1]再次被賦的值, 100動態數組中A := B;僅僅是將動態數組A 的指針指向動態數組B,而並不是像我們希望的那樣爲A開闢一 塊空間。如果非要爲A開闢一塊空間就要用Copy來複制B的數據。 AnsiString也是動態數組,所以同樣的情況也存在於String類型。 特殊的數組: 本來字符串想單獨說一說,但是卻因爲它具有太多的動態數組特性所以不單獨說了。 再一個,這裏的代碼和說的以後你很可能是用不到的,但是能加深你對ObjectPascal的理解。 在設計時你會知道它是如何工作的,通過這些你會更有效率的使用它。 大多數字符串與其說是數組倒不如說他是個結構。但是我們用他的數組特性更多一些。 1.ShorString:是爲了與老的Pascal兼容而保留的類型。最大255個字符。 Type TShortString Length:Byte; Data:array[1..Length] of Char; end; 從結構上可以看出ShortString最大隻能保存255個字符。我們可以做個實驗 var k:ShortString; begin k:='I am a Delphi fan!'; k[0]:=Char(13);//k.Length被置爲13 showmessage(k); end; ///兩種方法效果相同 var k:ShortString; p:^Byte; begin k:='I am a Delphi fan!'; p:=@k[1]; Dec(p,1); p^:=13;//k.Length被置爲13 showmessage(k); end; 運行一下看看會出什麼結果,呵呵。 爲什麼Short的數組下限是0呢?想一想Byte和Char的關係我不想多說. 2.AnsiString:標準的字符串,以Nil結尾。 Type TAnsiString=record allocSiz: LongWord;//分配的大小 ReferencCount:LongWord; //引用次數 Length:LongWord; //字符串長度 Data:array[1..(Length+1)] of AnsiChar; end; 有人要問爲什麼長度是1..(Length+1)而不是Length呢? 因爲後面還要有一位NULL字符,表示結束。可見AnsiString和CC++的兼容性也是很好的。 如下代碼: var k: AnsiString; p:^LongWord; begin k := 'I am a Delphi fan!'; p:=@K[1]; Dec(p,1); P^:=13; //k.Length被置爲13 showmessage(k); end; 由此我們會知道ANsiString的效率是很高的,他的信息存儲於字符串頭中。例如我們要取字符串的長度只須讀出 TAnsiString.Length的內容即可。而其cc++的字符串就不行,取字符串的長度要從頭讀起直到遇到NULL記錄下 讀過的字符個數就是長度。可是如果字符串很長效率的差異就顯示出來了,比如一個很大的文件? 前面講過動態數組賦值的問題,它並不爲動態數組單獨開闢一塊空間,而是簡單的把指針指向所賦的數組。只有被賦值的 數組在改變時才真正分配給他空間。這樣做的好處是在賦值的時候會很快。如果被賦值的數組沒有改變那就比直接分配空間 快上許多。另外如果被賦值數組發生改變重新分配空間,只是花費和直接分配空間相同的時間而已。 有些人會想那麼如下代碼: var k,j:AnsiString; begin k:'123'; j:='456'; k:=j; end; k的空間已經分配,既然k的指針指向了j,那麼k原來的空間豈不是浪費,造成了內存垃圾? 前面說過AnsiString也是動態數組的一種,符合動態數組生存期自管理。 但實際的情況是:當把一個指針指向一個字符創空間時,他的引用次數位就加1, 當有指針指向從這個字符串離開時引用次數位就減1,所以當引用次數位爲0是就意味着沒有人使用了 就自動釋放這段空間。這就是我們所說的生存期自管理技術。 Type //實際分配大小對我們來說意義不大,所以這裏我們先不取他 PAnsiString=^TAnsiString; TAnsiString= record ReferencCount:LongWord; //引用次數 Length:LongWord; //字符串長度 end; var k,j: AnsiString; P:^LongWord; MyAnsiString:PAnsiString; begin k:='I love Delphi!';計數器應該爲1,因爲剛剛分配內存,只有一個使用者 P:=@K[1]; Dec(P,2); MyAnsiString:=@(p^); showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); j:=K;//計數器應該加1,因爲j的指針指向了他,使用者又多了一個 showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); j:='123';//計數器應該減1,因爲j的指針指不再指向他,使用者少了一個 showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); k:=j;//k的指針指向j的內容所在,那麼這片數據的計數器再減去一個,即爲0。 //然而因該該內存區域已經被自動釋放所以這裏再去讀數的話就是一些隨機數據 showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); end;
動態數組的回收機制原理相同,現在明白了爲什麼動態數組再回收時要給他賦成Nil了吧!變體型等生存期自管理的 類型都是用這種機制來實現的. 爲什麼說字符串是動態數組的一種呢?因爲以上方法同樣適用於動態數組。我們用動態數組自己實現一個 AnsiString看看行不行呢? 比如: Type PAnsiString=^TAnsiString; TAnsiString= record ReferencCount:LongWord; //引用次數 Length:LongWord; //字符串長度 end; var k,j: array of Integer; P:^LongWord; MyAnsiString:PAnsiString; begin SetLength(k,10);//計數器應該爲1,因爲剛剛分配內存,只有一個使用者k SetLength(j,10); P:=@K[0]; Dec(P,2); MyAnsiString:=@(p^); showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); j:=K;//計數器應該加1,因爲j的指針指向了他,使用者又多了一個 showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); SetLength(j,100);//計數器應該減1,因爲j的指針指不再指向他,使用者少了一個 showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); SetLength(k,100);//k內存被重新分配,那麼這片數據的計數器再減去一個,即爲0。 //然而因沒有使用者該內存區域已經被自動釋放所以這裏再去讀數的話就是一些隨機數據 showmessage('現在計數器:'+Inttostr(MyAnsiString^.ReferencCount)); showmessage('長度爲:'+Inttostr(MyAnsiString^.Length)); end; 理論上來說能模擬實現AnsiString的一些功能,通過這些可以看到AnsiString不過是動態數組的一種形式。 3.WideString:寬字符串,由WideChar組成,以Nil結尾. Type TWideString=record allocSiz: LongWord;//分配的大小 Length:LongWord; //字符串長度 Data:array[1..(Length+1)] of WideChar; end; 有結構可以看出,WideString和AnsiString很類似,但是他沒有回收機制。不能進行生存期自管理。 但是在處理上還是較CC++效率高上許多。 4.Pchar:與其說成是字符串到不如說他是指針更準確一些。 Type TPchar=^Char; Pchar是爲了和C++兼容的實際上它是指向字符的指針。從這個字符開始到後面第一個NULL字符爲止都是 Pchar字符串的內容,也就是說PChar是它內容的首地址,因爲Pchar並非結構而是一種指針所以Pchar的 下限是0. var A:Pchar; ... A:='1234567890'; A[0]:='a';//這一句是合法的
看看如下代碼就明白了: var k:array of char; p:Pchar; i:Integer; begin SetLength(k,20); FillMemory(@k[0],Length(k),Byte('A')); i:=3; //i只要比20小,大於等於0,隨你調整看看不同的結果。 k[i]:=#0; p:=@k[0]; showmessage(p); end;
其它:數組在所有的編程語言中都是一個很重要的數據類型(不過Java的數組使用類來實現的)。 是應用很廣泛的一種技術。 如果你見過下列語法:
TA=Class(TXXX) ... public procedure Exec; Overload; procedure Exec(Value:Boolean); Overload; end; 一定會知道這是應用了這是重載。但是有沒有想過在對象的內部機制中重載是如何實現的呢? 在內部實際上它是一個函數的數組(內部由表來實現)。當調用時會根據參數的不同來決定調用哪一個。
附: 幾條數組常用的函數 High(),Low()取數組的上下界
2006-03-16
今天看到如下一段代碼,覺得有意思,就搞回來記錄下來了。
type TCars = (Ford, Vauxhall, GM, Nissan, Toyota, Honda); var cars : array[TCars] of string; // Range is 0..5 japCars : array[Nissan..Honda] of string; // Range is 3..5 begin // We must use the appropriate enumeration value to index our arrays: japCars[Nissan] := 'Bluebird'; // Allowed japCars[4] := 'Corolla'; // Not allowed japCars[Ford] := 'Galaxy'; // Not allowed end;
|