程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> SqlServer數據庫 >> 關於SqlServer >> Delphi之動態數組(整理)

Delphi之動態數組(整理)

編輯:關於SqlServer
傳統的Pascal 語言其數組大小是預先確定的,當你用數組結構聲明數據類型時,你必須指定數組元素的個數。專業程序員也許知道些許動態數組的實現技術,一般是采用指針,用手工分配並釋放所需的內存。

Delphi 4中增加了非常簡單的動態數組實現方法,實現過程效仿我前面講過的動態長字符串。與長字符串一樣,動態數組的內存動態分配並且引用記數,不過動態數組不支持 copy-on-write 技術。這不是個大問題,因為你可以把變量值設置為nil釋放數組內存。

這樣你就可以聲明一個不指定元素個數的數組,並用SetLength 過程給數組分配一個特定大小的內存,SetLength 過程還可以改變數組大小而不影響其內容,除此外還有一些字符串過程也可用於數組,如Copy 函數。

以下摘錄的代碼突出了一點,這就是:定義數組後必須先為它分配內存,然後才能開始使用:

procedure TForm1.Button1Click(Sender: TObject);var  Array1: array of Integer;begin  Array1 [1] := 100; // error  SetLength (Array1, 100);  Array1 [99] := 100; // OK  ...end;

如果你只定義一個數組元素個數,那麼索引總是從0開始。Pascal 中的普通數組既能用不為零的下標,也能用非整數的下標,但動態數組均不支持這兩種下標。象普通數組一樣,你可以通過Length、High和Low 函數了解到動態數組的狀況,不過對於動態數組,Low 函數返回值總是0,High函數返回數組大小減1,這意味著空的動態數組其函數High返回值是-1,這是一個很怪的值,因為它比Low的返回值還小。

圖 8.1: 例 DynArr 窗體

以上作了簡短的介紹,現在舉個簡例,例名DynArr ,見圖8.1。例子實在是很簡單,其實動態數組沒有什麼特別復雜地方。我想通過該例說明幾個程序員可能犯的錯誤。程序中聲明了兩個全程數組並在OnCreate 事件中初始化了第一個數組:

var  Array1, Array2: array of Integer;procedure TForm1.FormCreate(Sender: TObject);begin  // allocate  SetLength (Array1, 100);end;

這樣就把數組所有值設置為0。完成這段代碼你馬上就能讀寫數組元素的值,而不用害怕內存出錯,當然條件是你沒有試圖訪問超過數組上界的元素。為了更好地初始化,程序中添加了一個按鈕,執行數組元素賦值操作:

procedure TForm1.btnFillClick(Sender: TObject);var  I: Integer;begin  for I := Low (Array1) to High (Array1) do    Array1 [I] := I;end;

Grow 按鈕用於修改數組大小,但並不影響數組內容。單擊Grow 按鈕後,你可以用Get value按鈕進行檢驗:

procedure TForm1.btnGrowClick(Sender: TObject);begin  // grow keeping existing values  SetLength (Array1, 200);end;procedure TForm1.btnGetClick(Sender: TObject);begin  // extract  Caption := IntToStr (Array1 [99]);end;

Alias 按鈕的OnClick 事件代碼稍復雜些,程序通過 := 算子把一個數組拷貝給另一個數組,從而有效地創建了一個別名(一個新變量,但引用內存中同一數組)。從中可見,如果你改變了其中一個數組,那麼另一個同樣也會改變,因為它們指向同一個內存區:

procedure TForm1.btnAliasClick(Sender: TObject);begin  // alias  Array2 := Array1;  // change one (both change)  Array2 [99] := 1000;  // show the other  Caption := IntToStr (Array1 [99]);

在btnAliasClick 事件中增加了兩部分操作內容。第一部分是數組等同測試,不過並不是測試實際的數組元素,而是測試數組所引用的內存區,檢測變量是不是內存中同一數組的兩個別名:

procedure TForm1.btnAliasClick(Sender: TObject);begin  ...  if Array1 = Array2 then    Beep;  // truncate first array  Array1 := Copy (Array2, 0, 10);end;

btnAliasClick 事件的第二部分內容是調用Copy 函數。該函數不僅把數據從一個數組移到另一個數組,而且用函數創建的新數組取代第一個數組,結果變量Array1 所引用的是11個元素的數組,因此,按Get value 和Set value 按鈕將產生一個內存錯誤,並且觸發一個異常(除非你把范圍檢查range-checking 選項關掉,這種情況下,錯誤仍在但屏幕上不會顯示異常)。雖然如此,Fill 按鈕仍能正常工作,因為需要修改的數組元素由數組當前的下標范圍確定。

自從有了動態數組,鏈表除了在教科書裡出現外,已經很少在實際編程中被使用了,事實也是如此,數組的確比傳統鏈表快得多,而且也方便的多。

    從 Delphi4起,開始了內建各種類型的動態數組支持。但是,對我們來說動態數組支持似乎做的不夠徹底,因為Delphi竟然連刪除、插入、移動連續元素的函數都沒有提供,讓人使用起來總覺得不夠爽!!! J 。作為一名程序員,我們當然要有自己解決問題的能力,下面就讓我們簡單介紹一下Delphi 下的動態數組。

在Delphi中,數組類型有靜態數組(a : array[0..1024] of integer)、動態數組(var a : array of integer)、指針數組(即指向靜態數組的指針)和開放數組(僅用於參數傳遞)。靜態數組、指針數組有速度快的好處,動態數組有大小可變的優勢,權衡之下就有了折衷的辦法,那就是定義的動態數組在必要時轉換為指針。

動態數組聲明之後,只有下面幾個函數可┎僮鳎?o:p>

1.  設置數組大小,可以任意縮減或增加數組大小

Procedure SetLength(var S ; NewLength : integer);

2.  取出連續元素,復制給另一個數組變量

Function Copy(s;Index,Count : integer) : array ;

3.  取得數組大小及上下限

Function Length(s):integer;

Function High(x):integer;

Function Low(x):integer;

值得注意的是,不加const或var修飾的動態數組會被作為形參傳遞,而動態數組用const修飾並不意味著你不能修改數組裡的元素(不信你可以字自己在程序中試試。還有一點是High函數調用了Length 函數,所以我們在獲取數組上限時最好直接用 Length(s) 函數。

動態數組在內存空間中占用4個字節.   動態數組在內存中的分配表如下:

偏移量                                      內容

-8                                   32-bit 引用計數

-4                                   32-bit 數組長度

0..數組長度 * (元素尺寸) - 1   數組元素    元素尺寸=Sizeof(元素類型)

根據上面的分配情況,可以得到如下結果:

如果我們想要清空一個動態數組只需要把“數組長度”和“引用計數”清空即可。”引用上面的一句話就是:“權衡之下就有了折衷的辦法,那就是定義的動態數組在必要時轉換為指針。”下面是清空動態數組的函數:

procedure DynArraySetZero(var A);

var

  P: PLongint; //占用4個字節,正好符合 32 位內存排列

begin

  P := PLongint(A); // 指向 A 的地址

  Dec(P); //P 地址偏移量是 sizeof(A),指向了數組長度

  P^ := 0; // 長度清空

  Dec(P); // 指向引用計數

  P^ := 0; //計數清空。

end;

上面的函數就這麼簡單,而且效率也非常高。

下面讓我們再來看看怎樣刪除動態數組中的元素,函數體如下:

{************************************

 A 變量類型  , elSize = SizeOf(A)

index 開始刪除的位置索引 ,Count 刪除的數量

****************************************}

procedure DynArrayDelete(var A; elSize: Longint; index, Count: Integer);

var

  len, MaxDelete: Integer;

  P : PLongint; //4 個字節的長整形指針

begin

  P := PLongint(A);// 取的 A 的地址

  if P = nil then

    Exit;

  {

下面這句完全等同於 Dec(P) ; len := P^  因為 Dec(P) = Pchar(P) – 4  同樣是移動4 字節的偏移量,只不過後者按字節來移動    }

len := PLongint(PChar(P) - 4)^; // 變量的長度 ,偏移量 -4

  if index >= len then //要刪除的位置超出范圍,退出

    Exit;

  MaxDelete := len - index; // 最多刪除的數量

  Count := Min(Count, MaxDelete); // 取得一個較小值

  if Count = 0 then // 不要求刪除

    Exit; 

Dec(len, Count);// 移動到要刪除的位置

  MoveMemory(PChar(P)+index*elSize , PChar(P)+(index + Count)*elSize , (len-index)*elSize); //移動內存

  Dec(P);  //移出 “數組長度”位置

  Dec(P);  //移出“引用計數” 位置

  //重新再分配調整內存,len 新的長度. Sizeof(Longint) * 2 = 2*Dec(P)

  ReallocMem(P, len * elSize + Sizeof(Longint) * 2);

  Inc(P); // 指向數組長度

  P^ := len; // new length

  Inc(P); // 指向數組元素,開始的位置

  PLongint(A) := P;

end;

 

對上面的例子,我們需要注意的是 elSize 參數 ,它必須是 SizeOf(DyArray_Name),表示元素所占用的字節數。

    相信看了上面的例子後,對於動態數組的拷貝,移動想必也可以自己實現了吧 J

後續:

    其實,Delphi 對許多類型的內存分配都很相似,比如 string 類型,其實它和動態數組是很相似的,我們完全可以把它拿來當成動態數組。實質上 string 是 Pchar 的簡易版本。不管怎麼說,了解一些內存的分配對我們這些開發人員來說還是有一些好處的。



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved