欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Delphi的指針 good

 Pointers are like jumps, leading wildly from one part of the data structure to another. Their introduction into high-level languages has been a step backwards from which we may never recover. — Anthony Hoare

對指針可能有最讓人誤解和懼怕的數據類(lèi)型,因此很多程序員喜歡躲避他們.

但是指針很重要.即使不顯式支持指針或使其很難應用的語(yǔ)言,在其背后指針也起到非常重要的作用.因此理解指針是很重要的.有幾個(gè)不同的途徑理解指針.

本文針對理解指針和使用指針有問(wèn)題的讀者.在Win32的Delphi環(huán)境下進(jìn)行討論.可能不會(huì )涉及到所有方面(例如,一個(gè)應用程序的內存不是一個(gè)大的連續區域),但出于實(shí)用目的是有所幫助的.我認為指針是很容易理解的.

內存

You probably already know what I write in this paragraph, but it is probably good to read it anyway, since it shows my view on things, which may differ a bit from your own.

指針是一個(gè)指向其他變量的變量.為解釋他們,有必要先理解內存地址和變量的概念.首先先簡(jiǎn)要的解釋一下計算機內存.

計算機內存簡(jiǎn)化的可以看做很長(cháng)的字節行.字節是最小存儲單元,包括256種不同的值(0到255).在當前32為Delphi中,內存可以看做是最大有2G字節長(cháng)度的數組.字節中存儲的內容與內容的解讀方式有關(guān),例如,使用方式.值97可以看做是一個(gè)byte類(lèi)型的97,可以看做字符a.如果包含多個(gè)字節,可以存儲更多的值.兩個(gè)字節可以表示256*256中不同的值.

內存中的字節可以按編號進(jìn)行訪(fǎng)問(wèn),從0開(kāi)始直到2147483647(假如有2G字節,即使你不知道這些,Windows也會(huì )試圖虛擬這種效果).在這個(gè)巨大的數組中字節索引叫做地址.

也可以說(shuō):字節是內存中最小的可用地址表示的塊.

事實(shí)上,內存非常復雜.有些計算機的字節不是8位,其能表示或大于或小于256種值,但Win32的Delphi不會(huì )遇到這樣的計算機.內存由軟件和硬件共同管理,全部的內存并非真正存在(內存管理程序處理同樣與硬盤(pán)交換空間來(lái)解決這些問(wèn)題),但本文中,將內存看做是字節組成的大塊并在多個(gè)程序間共用有助于理解問(wèn)題.

變量

變量是巨大數組中的一個(gè)或多個(gè)字節組成的存儲單元,可供程序讀寫(xiě).有名字,類(lèi)型,值及其地址進(jìn)行標示.

如果聲明了一個(gè)變量,編輯器將保留一塊適當大小的內存區域.變量存儲的具體地址由編譯器及運行時(shí)代碼決定.不能對變量具體存放地址做任何假設.

變量類(lèi)型定義了如何使用內存存儲單元.例如其定義了size (尺寸)決定占用多少字節,以及其structure(結構).例如,下圖是一個(gè)內存片段.顯示了起始地址在$00012344的4個(gè)字節.字節值分別為$4D, $65, $6D和$00.

注意上圖雖然使用了$00012344作為起始地址,但這是杜撰的,只是為了區別其他內存位置.并不是真實(shí)反映的內存地址,其依賴(lài)于很多事情,不可預測.

數據類(lèi)型決定了如何使用這些字節.例如Integer類(lèi)型其值為7169357 ($006D654D),或一個(gè)array[0..3] of Char類(lèi)型,表示C風(fēng)格的字符串'Mem',或其他內容,如集合變量,幾個(gè)單字節變量,一個(gè)小結構體, SingleDouble類(lèi)型的一部分等等.換句話(huà)說(shuō),在不知道存儲的變量類(lèi)型前,內存中存儲的值的意義是無(wú)法推測的.

變量的地址是其第一個(gè)字節的地址.上圖中,假設是一個(gè)Integer,其地址為 $00012344.

未初始化變量

內存對于變量來(lái)說(shuō)是可以重用的.通常內存為變量預留的時(shí)間與變量的生命周期一樣長(cháng).例如,函數或過(guò)程(兩者總稱(chēng)例程)中的局部變量?jì)H在例程運行期間可用.對象的域(也是一個(gè)變量)在對象存在期間可用.

如果聲明一個(gè)變量,編譯器預留出變量需要的字節數.但其中的內容是以前函數或過(guò)程使用的時(shí)候在字節中存放的.換句話(huà)說(shuō),未初始化的變量值是未定義的(但不是未定的).例如在如下簡(jiǎn)單的控制臺程序中:

program uninitializedVar;

{$APPTYPE CONSOLE}

procedure Test;

var

  A: Integer;

begin

  Writeln(A); // uninitialized yet

  A := 12345;

  Writeln(A); // initialized: 12345

end;

begin

  Test;

  Readln;

end.

第一個(gè)顯示的值(未初始化的變量A)依賴(lài)于變量A存儲的地址中以前的值.本例中,顯示為2147319808 ($7FFD8000) ,但在其他計算機上會(huì )顯示不同的值.值是未定義的,因為其未初始化.在復雜的程序中, 尤其就指針而言這經(jīng)常會(huì )導致程序癱瘓或意想不到的結果.賦值語(yǔ)句將變量A初始化為12345 ($00003039), 第二個(gè)值顯示正常.

指針

指針也是變量.但其中不存儲數值或字符,而是一個(gè)內存存儲單元的地址.如果將內存看做是一個(gè)大數組,指針可以看做是這個(gè)數組中的一個(gè)入口,指向數組中另一個(gè)數組的入口索引.( If you see memory as an array,a pointer can be seen as an entry in the array which contains the index of another entry in the array.)

假設有如下的聲明和初始化過(guò)程:

var

  I: Integer;

  J: Integer;

  C: Char;

begin

  I := 4222;

  J := 1357;

  C := 'A';

并假如有如下的內存布局:

執行完這些代碼后,假如P是一個(gè)指針,

P := @I;

既有如下情形:

上圖中,顯示出了每個(gè)字節.通常是不必要的,因此可簡(jiǎn)化為:

這個(gè)圖不能在反映出真實(shí)的內存占用大小(C看起來(lái)可I或J一樣大小),但對理解指針來(lái)說(shuō)足夠了.

Nil

Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end. — Henry Spencer

Nil 是一個(gè)特殊的指針值.可以賦值給任意類(lèi)型的指針.其表示空指針(nil在拉丁語(yǔ)中是nihil,表示啥也沒(méi)有或零;或者說(shuō)NIL表示Not In List).表示指針有定義的狀態(tài),但不能訪(fǎng)問(wèn)任何值(C語(yǔ)言中nil表示為NULL--見(jiàn)上面的引用).

Nil 不執行可用的內存,但作為一個(gè)預定義的值,很多例程可以對其進(jìn)行檢查 (例如使用Assigned()函數).賦予了一個(gè)有效值后就無(wú)法檢測了,舊指針或未初始化指針與正常的指針沒(méi)有什么不同. 沒(méi)有方法區別他們.程序邏輯必須確保指針或是有效的或是nil.

Delphi中, nil 的值是0,指向內存區域中的第一個(gè)字節.很明顯這個(gè)字節Delphi代碼是無(wú)法訪(fǎng)問(wèn)到的.除非對后臺原理非常理解,通常不必關(guān)心nil等于0.nil 的值可能會(huì )在以后版本中出于某種目的進(jìn)行修改.

類(lèi)型指針

上例中P是Pointer類(lèi)型的.意味著(zhù)P包含一個(gè)地址,但不知道地址處的保存的變量信息. 指針之所以是通用的類(lèi)型,指針可以解讀指向的內存區域中存儲的特定類(lèi)型的內容.

假設有另一個(gè)指針, Q:

var

  Q: ^Integer;

Q 的類(lèi)型是^Integer,可以讀作”指向Integer” (^Integer相當于↑Integer).即這不是一個(gè)整數,而是指向一個(gè)常用的內存存儲單元.要將變量J的地址賦予Q,可以使用@ 地址操作符或定價(jià)的偽函數Addr,

  Q := @J; // Q := Addr(J);

Q指向了局部地址$00012348 (指向了有變量J標識的內存存儲單元).但由于Q是一個(gè)類(lèi)型化指針,編譯器將Q指向的內存單元看做是一個(gè)整數.Integer是Q的基本類(lèi)型.

雖然很少看到使用偽函數Addr的代碼,其等價(jià)于@.對于復雜的表達式@有時(shí)很難看出是作用于那個(gè)部分.而Addr使用的是函數語(yǔ)法,配合小括號減少了混淆:

  P := @PMyRec^.Integers^[6];

  Q := Addr(PMyRec^.Integers^[6]);

通過(guò)指針賦值與直接使用變量有少許不同.通常只能通過(guò)指針進(jìn)行操作.如果對普通變量賦值,形式如下:

  J := 98765;

將整數值98765 (十六進(jìn)制$000181CD)賦予內存存儲單元.使用指針Q來(lái)存取內存存儲單元,必須通過(guò)間接方式,使用^操作符:

  Q^ := 98765;

這叫做降低引用(dereferencing). 假想虛擬箭頭指向Q中的地址(這里是$00012348)并將對其賦值.

對于結構體,如果沒(méi)有異議,語(yǔ)法上可以省去^操作符.為了清晰,建議保留..

通常將常用的類(lèi)型指針預先定義好.例如,^Integer不能用于參數傳遞,需要預先定義一個(gè)類(lèi)型:

type

  PInteger = ^Integer;

procedure Abracadabra(I: PInteger);

事實(shí)上, Pinteger類(lèi)型和其他常用指針類(lèi)型已經(jīng)在Delphi的運行時(shí)庫 (例如SystemSysUtils單元)中預先定義.通常命名為P加上指向的類(lèi)型名稱(chēng).如果基本類(lèi)型名稱(chēng)以T作為前綴,則忽略T.例如:

type

  PByte = ^Byte;

  PDouble = ^Double;

  PRect = ^TRect;

  PPoint = ^TPoint;

匿名變量

上例中按需定義了變量.有時(shí)無(wú)法確認是否需要一個(gè)變量,以及多少個(gè)變量.通過(guò)指針就可以使用匿名變量. 可在運行時(shí)預留內存,并返回一個(gè)指針.使用偽函數New():

var

  PI: PInteger;

begin

  New(PI);

New() 是一個(gè)編譯器偽函數.其為PI預留基本類(lèi)型大小的內存,并使PI指向這塊內存區域(其中存儲了內存區域的地址).變量沒(méi)有名稱(chēng),因此是匿名的.只能提供指針間接訪(fǎng)問(wèn).現在可對其賦值,在函數間傳遞,在不需要的時(shí)候調用Dispose(PI)釋放:

  PI^ := 12345;

  ListBox1.Add(IntToStr(PI^));

  // lots of code

  Dispose(PI);

end;

除了New和Dispose,也可調用更低級別的函數GetMem和FreeMem.但New 和Dispose有幾個(gè)優(yōu)點(diǎn).他們已經(jīng)知道指針的基本類(lèi)型,并對內存存儲單元進(jìn)行必要的初始化和釋放.因此無(wú)論何時(shí)都優(yōu)先使用New和Dispose替代GetMem和FreeMem.

確保每個(gè)New()都有一個(gè)對應的使用相同類(lèi)型和指針的Dispose()調用,否則變量就不會(huì )被正確的釋放.

可能相對于直接使用變量的優(yōu)點(diǎn)不是很明顯,但對于不知道需要多少個(gè)變量的情形很有用.假如一個(gè)鏈接列表(seebelow),或一個(gè)TList. TList存儲指針,如果想在List中存儲Double值,可以簡(jiǎn)單的對每個(gè)值調用New()并將指針存儲在Tlist中:

var

  P: PDouble;

begin

  while HasValues(SomeThing) do

  begin

    New(P);

    P^ := ReadValue(SomeThing);

    MyList.Add(P);

    // etc...

當然在列表不再使用的階段要對每個(gè)值調用Dispose().

使用匿名變量,可以容易的闡述通過(guò)類(lèi)型指針操作內存.兩個(gè)指針類(lèi)型不同,執行同一塊內存,可以顯示出不同的值:

program InterpretMem;

{$APPTYPE CONSOLE}

var

  PI: PInteger;

  PC: PAnsiChar;

begin

  New(PI);

  PI^ := $006D654D;     // Bytes $4D $65 $6D $00

  PC := PAnsiChar(PI);  // Both point to same address now.

  Writeln(PI^);         // Write integer.

  Writeln(PC^);         // Write one character ($4D).

  Writeln(PC);          // Interpret $4D $65 $6D $00 as C-style string.

  Dispose(PI);

  Readln;

end.

PI 將內存存儲單元填充為$006D654D (7169357).下圖(注意地址純粹是虛構的):

PC 指向了同一塊內存(由于基本類(lèi)型不同,不能直接進(jìn)行賦值,必須進(jìn)行類(lèi)型轉換).但PC 指向一個(gè)AnsiChar,因此如果調用PC^, 獲取到的是一個(gè)AnsiChar, ASCII字符值為$4D或'M'.

PC 是特殊情況,盡管是PAnsiChar類(lèi)型, 由于實(shí)際是指向AnsiChar的指針,處理起來(lái)和其他類(lèi)型指向稍有不同.在其他文章中解釋another article.PC,如果沒(méi)有降低引用,通??醋鍪侵赶蛞?0結尾的字符串,調用Writeln(PC) 將$4D $65 $6D $00字節顯示為'Mem'.

當思考指針問(wèn)題,尤其是復雜指針,我都準備一頁(yè)紙和鋼筆或鉛筆,繪制如本文所見(jiàn)的圖.變量的地址也是偽造的(并不是32位的,而是像30000,400004000440008 和 50000便于理解就好).

壞指針

適當使用指針是很有用而且靈活的.但一旦出錯將是一個(gè)大問(wèn)題.這也是很多人盡量避免使用指針的原因.這里描述常見(jiàn)錯誤.

未初始化指針

指針是變量,可其他變量一樣需要初始化,或賦予其他指針值,或使用New或 GetMem 等等:

var

  P1: PInteger;

  P2: PInteger;

  P3: PInteger;

  I: Integer;

begin

  I := 0;

  P1 := @I;  // OK: using @ operator

  P2 := P1;  // OK: assign other pointer

  New(P3);   // OK: New

  Dispose(P3);

end;

例如如果簡(jiǎn)單的聲明PInteger,但沒(méi)有初始化,指針包含了一個(gè)隨機字節值,其指向了隨機的內存區域.

如果訪(fǎng)問(wèn)這個(gè)隨機的內存地址,會(huì )發(fā)生令人厭惡的事情.如果這個(gè)內存地址不在當前應用程序預留范圍內,獲取一個(gè)非法訪(fǎng)問(wèn)(Access Violation)錯誤,程序崩潰.但如果內存在應用程序預留范圍,并寫(xiě)了數據,可能修改了不應修改的數據.如果數據在程序的其他部分稍后使用,會(huì )導致程序的數據錯誤.這樣的錯誤很難查找.

事實(shí)上,如果獲取到AV或其他類(lèi)型的明顯錯誤,是值得高興的(除了硬盤(pán)損壞).應用程序崩潰不好,但問(wèn)題很容易定位和修改.但對于錯誤數據和結果,問(wèn)題更加糟糕,可能沒(méi)有注意到或很久才爆發(fā).因此使用指針必須及其謹慎.要細致的檢查未初始化的指針.

舊指針

舊指針是以前有效的指針,但后來(lái)過(guò)時(shí)了.經(jīng)常因為指針指向的內存區域被釋放和重用.

經(jīng)常發(fā)生舊指針的情況是內存被釋放,但指針仍舊被使用.為避免這種情況,有些程序員在釋放內存后總將指針設置為nil.并在訪(fǎng)問(wèn)內存前檢查其是否為nil.換句話(huà)說(shuō),nil是指針不可用的標志.這是一種途徑,但并不總有效.

另一種常見(jiàn)情況是多個(gè)指針執行同一個(gè)內存區域,然后用其中的一個(gè)指針釋放內存.即使將那個(gè)指針置為nil,其他指針還是指向了這塊被釋放的內存.如果幸運報非法指針錯誤,但具體會(huì )發(fā)生什么事情是不確定的.

第三,相似的問(wèn)題是指向不穩定的數據,例如,數據會(huì )隨時(shí)消失.最大的錯誤是在函數中返回一個(gè)指向局部數據的指針.一旦例程結束,數據消失,局部變量不再存在.典型(愚蠢)的范例:

function VersionData: PChar;

var

  V: array[0..11] of Char;

begin

  CalculateVersion(V);

  Result := V;

end;

V位于過(guò)程棧.這是每個(gè)運行時(shí)函數用來(lái)存放局部變量和參數,以及敏感的函數返回地址的地方.結果值指向V (PChar可以直接指向數組,見(jiàn)提及的article).VersionData 結束后,棧被另一個(gè)運行的例程改變,無(wú)論CalculateVersion 計算的結果是什么都以過(guò)時(shí),指針指向了同一棧位置的新內容.

同樣的問(wèn)題還有Pchar指向一個(gè)字符串,請見(jiàn)article about PChars.指針指向動(dòng)態(tài)數組元素也是一個(gè)經(jīng)典問(wèn)題,因為動(dòng)態(tài)數組變小或調用SetLength后會(huì )被移動(dòng).

使用錯誤的基本類(lèi)型

事實(shí)上指針可以指向任意內存存儲單元,兩個(gè)不同類(lèi)型的指針可以指向同一個(gè)區域,意味著(zhù)可以按不同方式存取同一個(gè)內存區域.使用指向Byte(^Byte)的指針,可以按字節修改整數或其他類(lèi)型的內容.

但也可能會(huì )寫(xiě)覆蓋(overwrite)或讀覆蓋(overread).例如,如果用整數指針去訪(fǎng)問(wèn)存儲一個(gè)字節的內存區域,將會(huì )寫(xiě)4個(gè)字節,而不僅僅是Byte類(lèi)型的單字節,還有其后的3個(gè)字節,因為編譯器將這連續的4個(gè)字節看做是一個(gè)整數.同樣讀取這個(gè)字節,也會(huì )多讀3個(gè)字節:

var

  PI: PInteger;

  I, J: Integer;

  B: Byte;

begin

  PI := PInteger(@B);

  I := PI^;

  J := B;

end;

J 有正確的值,因為編譯器會(huì )填充0將單字節擴展成為一個(gè)整數(4字節).但變量I則不同,其包括一個(gè)字節,以及其后的3個(gè)字節,組成了未定義的值.

指針允許不通過(guò)變量本身來(lái)設置變量的值.這可能在調試過(guò)程中很疑惑.知道一個(gè)變量的值錯誤,但不知道哪里的代碼修改了變量,因為其是通過(guò)指針設置的.

所有者和孤兒

指針不但有不同的基本類(lèi)型,還有不同的所有者語(yǔ)義.如果使用New或GetMem,或其他特定例程申請內存,你就是內存的所有者.很好,如果要持有這塊內存,需要將指針保存到一個(gè)安全的地方.這個(gè)指針是唯一可訪(fǎng)問(wèn)這塊內存的途徑,如果其中的地址丟失,就無(wú)法在訪(fǎng)問(wèn)或釋放這塊內存了.一個(gè)規則是申請的內存必須被釋放,因此你有責任照顧好他.設計良好的程序必須考慮到這些.

理解所有者是很重要的.擁有內存的人必須負責釋放內存.可以找代理來(lái)執行這個(gè)任務(wù),但必須確保執行正確..

一個(gè)常見(jiàn)的錯誤是使用指針指向一塊分配的內存,而后又將這個(gè)指針指向另外一個(gè)內存塊.指針指向第一個(gè)內存塊又再次指向了另外一個(gè)內存塊,原來(lái)的內存塊丟失.已經(jīng)沒(méi)有辦法找回第一個(gè)申請到的內存塊.內存塊成為了孤兒.已無(wú)法再次訪(fǎng)問(wèn)并處理他.這也叫做內存泄露.

這是摘自Borland的新聞組中的范例代碼:

var

  bitdata: array of Byte;

  pbBitmap: Pointer;

begin

  SetLength(bitdata, nBufSize);

  GetMem(pbBitmap, nBufSize);

  pbBitmap := Addr(bitdata);

  VbMediaGetCurrentFrame(VBDev, @bmpinfo.bmiHeader, @pbBitmap, nBufSize);

事實(shí)上,這個(gè)代碼有幾個(gè)沖突的地方. SetLengthbitdata分配字節.出于某種原因程序員使用GetMempbBitmap分配了同樣數量的字節.而后將pbBitmap指向了另外一個(gè)內存地址,導致由分配GetMem的內存無(wú)法被訪(fǎng)問(wèn).( pbBitmap是唯一訪(fǎng)問(wèn)的途徑,但現在不在指向他了).換句話(huà)說(shuō),內存泄露了.

事實(shí)上,還有幾個(gè)錯誤. bitdata 是一個(gè)動(dòng)態(tài)數組,獲取bitdata的地址只是得到了一個(gè)指針的地址,而不是緩沖區中第一個(gè)字節的地址(更多信息參見(jiàn)下面的動(dòng)態(tài)數組).而且,由于pbBitmap也是指針,在函數調用時(shí)使用@操作符傳遞參數是錯誤的.

正確的代碼如下:

var

  bitdata: array of Byte;

  pbBitmap: Pointer;

begin

  if nBufSize > 0 then

  begin

    SetLength(bitdata, nBufSize);

    pbBitmap := Addr(bitdata[0]);

    VbMediaGetCurrentFrame(VBDev, @bmpinfo.bmiHeader, pbBitmap, nBufSize);

  end;

或者:

var

  bitdata: array of Byte;

begin

  if nBufSize > 0 then

  begin

    SetLength(bitdata, nBufSize);

    VbMediaGetCurrentFrame(VBDev, @bmpinfo.bmiHeader, @bitdata[0], nBufSize);

  end;

看起來(lái)是很?chē)乐氐膯?wèn)題,但是在復雜代碼中很容易出現.

注意指針不必一定執行自己的內存塊.指針通常用來(lái)遍歷數組(如下),或操作結構體中的成員.如果沒(méi)有為其分配內存,就不必負責對內存塊進(jìn)行控制.可以看做是用完就失效的臨時(shí)變量.

指針運算和數組

You can either have software quality or you can have pointer arithmetic, but you cannot have both at the same time. — Bertrand Meyer

Delphi allows some simple manipulations of a pointer. Of course you can assign to them, and compare them for equality (if P1 = P2 then) or inequality, but you can also increment and decrement them, usingInc and Dec. The neat thing is that these increments and decrements arescaled by the size of the base type of the pointer. An example (note that I set the pointer to a fake address. As long as I don't access anything with it, nothing bad will happen):

program PointerArithmetic;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils;

 

procedureWritePointer(P: PDouble);

begin

  Writeln(Format('%8p', [P]));

end;

 

var

  P: PDouble;

 

begin

  P := Pointer($50000);

  WritePointer(P);

  Inc(P);    // 00050008 = 00050000 + 1*SizeOf(Double)

  WritePointer(P);

  Inc(P, 6); // 00050038 = 00050000 + 7*Sizeof(Double)

  WritePointer(P);

  Dec(P, 4); // 00050018 = 00050000 + 3*Sizeof(Double)

  WritePointer(P);

  Readln;

end.

The output is:

00050000

00050008

00050038

00050018

The utility of this is to provide sequential access to arrays of such types. Since (one-dimensional) arrays contain consecutive items of the same type — i.e. if one element is at addressN, then the next element is at addressN+SizeOf(element) —, it makes sense to use this to access items of an array in a loop. You start with the base address of the array, at which you can access the first element. In the next iteration of the loop, you increment the pointer to access the next element of the array, and so on, and so forth:

program IterateArray;

 

{$APPTYPE CONSOLE}

 

var

  Fractions: array[1..8] of Double;

  I: Integer;

  PD: ^Double;

 

begin

  // Fill the array with random values.

  Randomize;

  for I := Low(Fractions) to High(Fractions) do

    Fractions[I] := 100.0 * Random;

 

  // Access using pointer.

  PD := @Fractions[Low(Fractions)]; 

  for I := Low(Fractions) to High(Fractions) do

  begin

    Write(PD^:9:5);

    Inc(PD); // Point to next item

  end;

  Writeln;

 

  // Conventional access, using index.

  for I := Low(Fractions) to High(Fractions) do

    Write(Fractions[I]:9:5);

  Writeln;

 

  Readln;

end.

Incrementing a pointer is, at least on older processors, probably slightly faster than multiplying the index with the size of the base type and adding that to the base address of the array for each iteration.

In reality, the effect of doing it this way is not nearly as big as you might expect. First, modern processors have special ways of addressing the most common cases using an index, so there is no need to update the pointer too. Second, the compiler will generally optimize indexed access into the pointer using version anyway, if this is more beneficial. And in the above, the gain found by using a slightly more optimized access is largely overshadowed by the time it takes to perform the Write().

As you can see in the program above, you can easily forget to increment the pointer inside the loop. And you must either usefor-to-do anyway, or use another way or counter to terminate the loop (which you must then also decrement and compare manually). IOW, the code using the pointer is generally much harder to maintain. Since it is not faster anyway, except perhaps in a very tight loop, I would be very wary of using that kind of access in Delphi. Only do this if you have profiled your code and found pointer access to be beneficial and necessary.

Pointers to arrays

But sometimes you only have a pointer to access memory. Windows API functions often return data in buffers, which then contain arrays of a certain size. Even then, it is probably easier to cast the buffer to a pointer to an array than to use Inc or Dec. An example:

type

  PIntegerArray = ^TIntegerArray;

  TIntegerArray = array[0..65535] of Integer;

var

  Buffer: array of Integer;

  PInt: PInteger;

  PArr: PIntegerArray;

  ...

  // Using pointer arithmetic:

  PInt := @Buffer[0];

  for I := 0 to Count - 1 do

  begin

    Writeln(PInt^);

    Inc(PInt);

  end;

 

  // Using array pointer and indexing:

  PArr := PIntegerArray(@Buffer[0]);

  for I := 0 to Count - 1 do

    Writeln(PArr^[I]);

  ...

end;

Delphi 2009

In Delphi 2009, pointer arithmetic, as usable for the PChar type (andPAnsiChar and PWideChar), is now also possible for other pointer types. When and where this is possible is governed by the new$POINTERMATHcompiler directive.

Pointer arithmetic is generally switched off, but it can be switched on for a piece of code using {$POINTERMATH ON}, and off again using {$POINTERMATH OFF}. For pointer types compiled with pointer arithmetic (pointer math) turned on, pointer arithmetic is generally possible.

Currently, besides PCharPAnsiChar and PWideChar, the only other type for which pointer arithmetic is enabled by default is thePByte type. But switching it on for, say, PInteger would simplify the code above considerably:

{$POINTERMATH ON}

var

  Buffer: array of Integer;

  PInt: PInteger;

  ...

  // Using new pointer arithmetic:

  PInt := @Buffer[0];

  for I := 0 to Count - 1 do

    Writeln(PInt[I]);

  ...

end;

{$POINTERMATH OFF}

So there is no need for the declaration of special TIntegerArray andPIntegerArray types to be able to access the type as an array anymore. Alternatively, instead of PInt[I], the (PInt + I)^ syntax could have been used, with the same result.

Apparently, in Delphi 2009, the new pointer arithmetic doesn't work as intended for pointers togeneric types yet. Whatever type the parametric type is instantiated as, indices are not scaled bySizeOf(T), as expected.

References

Many types in Delphi are in fact pointers, but pretend not to be. I like to call these typesreferences. Examples are dynamic arrays, strings, objects and interfaces. These types are all pointers behind the scenes, but with some extra semantics and often also some hidden content.

         Dynamic arrays

         Multi-dimensional dynamic arrays

         Strings

         Objects

         Interfaces

         Reference parameters

         Untyped parameters

What distinguishes references from pointers is:

      References are immutable. You can not increment or decrement a reference. References point to certain structures, but never into them, like for instance the pointers that point into an array, in the examples above.

      References do not use pointer syntax. This hides that they are in fact pointers, and makes them hard to understand for many, who do not know this, and therefore do things with them they would better not do.

Do not confuse such references with C++'s reference types. These are different in many ways.

動(dòng)態(tài)數組

在Delphi4之前,動(dòng)態(tài)數組還不是語(yǔ)言的一個(gè)特性,但存在這個(gè)概念.動(dòng)態(tài)數組是運行時(shí)分配的內存塊,并通過(guò)指針進(jìn)行管理.動(dòng)態(tài)數組可以增長(cháng)或壓縮.這意味著(zhù)需要重新分配指定大小的內存塊,原來(lái)內存塊中的內容需要拷貝到新的內存塊中,原來(lái)內存塊被釋放掉,指針指向新的內存塊.

Delphi中的動(dòng)態(tài)數組(如array of Integer)類(lèi)型也是這樣的.但由運行時(shí)附件的代碼來(lái)管理內存的讀取和分配.如下的內存存儲單元指針指向的地址有兩個(gè)附加的域:分配的元素數量和引用數量.

如果如上圖所示,N是動(dòng)態(tài)數組變量的地址,那么引用數量(reference count)的地址就是N-8,分配的元素數量(length指示器)是N-4.第一個(gè)元素的地址是N.

每增加一個(gè)引用(如賦值,參數傳遞等),引用計數就加一,每次解除引用(如離開(kāi)變量的作用域,或包含動(dòng)態(tài)數組成員的對象被釋放,或指向動(dòng)態(tài)數組的變量指向了其他動(dòng)態(tài)數組或nil)引用計數就減一.

使用低層次例程MoveFillChar或其他例程如TStream.Write存取動(dòng)態(tài)數組經(jīng)常出錯.對于一個(gè)正常的數組(為區別于動(dòng)態(tài)數組,將其叫做靜態(tài)數組),變量與內存塊等價(jià)的.而對于動(dòng)態(tài)數組,情況就不是這樣的了.因此如果一個(gè)例程想要按內存塊的方式存取數組中的元素,就不能引用動(dòng)態(tài)數組變量,而是需要使用動(dòng)態(tài)數組中的第一個(gè)元素.

var

  Items: array of Integer;

  ... 

  // Wrong: address of Items variable is passed

  MyStream.Write(Items, Length(Items) * SizeOf(Integer));

  ...

  // Correct: address of first element is passed

  MyStream.Write(Items[0], Length(Items) * SizeOf(Integer));

注意上面代碼中, Stream.Write 使用無(wú)類(lèi)型的var 參數,也是引用傳值.下面會(huì )進(jìn)行討論.

多維動(dòng)態(tài)數組

上面討論的是一維動(dòng)態(tài)數組.動(dòng)態(tài)數組也可以是多維的.但只是語(yǔ)法層面上的,事實(shí)上不是的.多維動(dòng)態(tài)數組實(shí)際上是一個(gè)指向另外一維數組的一維數組.

假如有如下聲明:

type

  TMultiIntegerArray = array of array of Integer;

var

  MyIntegers: TMultiIntegerArray;

現在看起來(lái)聲明了一個(gè)多維數組,而且可以通過(guò)MyIntegers[0, 3]的方式存取其中的元素.但是聲明的類(lèi)型應該這樣讀(語(yǔ)法層面上):

type

  TMultiIntegerArray = array of (array of Integer);

或者更加明確的描述為:

type

  TSingleIntegerArray = array of Integer;

  TMultiIntegerArray = array of TSingleIntegerArray;

可見(jiàn), TMultiIntegerArray 事實(shí)上是一個(gè)指向TSingleIntegerArray的一維數組.這樣TMultiIntegerArray存儲區域就不是按行和列排列的連續內存塊,實(shí)際上是不定長(cháng)的數組,如每個(gè)元素都指向了另一個(gè)數組,每個(gè)子數組都有不同的大小.因此對于

SetLength(MyIntegers, 10, 20);

(將分配10個(gè)TSingleIntegerArrays ,每個(gè)子數組有20個(gè)整數,表面上是一個(gè)矩形數組), 可以存取和修改每個(gè)子數組:

SetLength(MyIntegers, 10);

SetLength(MyIntegers[0], 40);

SetLength(MyIntegers[1], 31);

// etc...

字符串

Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. — Stan Kelly-Bootle

字符串在很多方面都與動(dòng)態(tài)數組相同.也有引用計數,有相同的內部結構,一個(gè)引用計數和指示其中存儲的字符串數據的長(cháng)度(在同樣的偏移量上).

不同之處在語(yǔ)法和語(yǔ)義上.不能將字符串設置為nil,可以設置為'' (空字符串)來(lái)清空他.字符串也可是一個(gè)常量(引用計數是-1,運行時(shí)例程中將其作為一個(gè)特殊值對其進(jìn)行增減或釋放字符串).第一個(gè)元素的索引是1,而動(dòng)態(tài)數組的第一個(gè)元素索引是0.

字符串更多信息見(jiàn)article about PChars and strings.

對象

對象— 更確切的說(shuō)是類(lèi)的實(shí)例,編譯器不會(huì )管理其生命周期.其內部結構很簡(jiǎn)單.每個(gè)類(lèi)實(shí)例的0偏移量處(相對于每個(gè)引用的指針執行的地址)有一個(gè)指針指向VMT表.其中包含指向類(lèi)中的每個(gè)虛方法的指針.這個(gè)表的負偏移量中是關(guān)于類(lèi)的其他信息.對此不作過(guò)多介紹.每個(gè)類(lèi)只有一個(gè)VMT表(而不是每個(gè)對象).

實(shí)現了接口的類(lèi)也有一個(gè)指向含有接口中被實(shí)現的方法表的指針,每個(gè)被實(shí)現的接口對應一個(gè).這個(gè)表在負偏移量中也有一些額外的信息.這些偏移量處的對象指針指向與基類(lèi)相關(guān)域的信息.編譯器知道具體細節.

在VMT指針和接口表指針后面,是對象的域,與結構體相似.

對象的RTTI數據和其他類(lèi)的信息從對象的這些引用中獲取,如VMT指針指向的VMT表中等.編譯器知道如何獲取其余的數據,通常通過(guò)包含其他結構體指針的復雜結構體,甚至會(huì )循環(huán)引用來(lái)獲得.

下例中,假設如下聲明:

type

  TWhatsit = class(TAncestor, IPrintable, IEditable, IComparable)

    // other field and method declarations

    procedure Notify(Aspect: TAspect); override;

    procedure Clear; override;

    procedure Edit;

    procedure ClearLine(Line: Integer);

    function Update(Region: Integer): Boolean; virtual;

    // etc...

  end;

 

  var

    Whatsit: TWhatsit;

   

  begin

    Whatsit := TWhatsit.Create;

對象布局如下圖:

接口

接口實(shí)際上上方法的集合.在內部,他們是指向一個(gè)指向了代碼的指針數組.假如有如下聲明:

type

  IEditable = interface

    procedure Edit;

    procedure ClearLine(Line: Integer);

    function Update(Region: Integer): Boolean;

  end;

 

  TWhatsit = class(TAncestor, IPrintable, IEditable, IComparable)

  public

    procedure Notify(Aspect: TAspect); override;

    procedure Clear; override;

    procedure Edit;

    procedure ClearLine(Line: Integer);

    function Update(Region: Integer): Boolean; virtual;

    // etc...

  end;

 

var

  MyEditable: IEditable;

 

begin

  MyEditable := TWhatsit.Create;

接口,實(shí)現對象,實(shí)現類(lèi)和方法的關(guān)系如下:

MyEditable 指向了由TMyClass.Create創(chuàng )建對象中的IEditable指針.注意MyEditable 不是指向對象的起始地址,而是有一個(gè)偏移量.MyEditable指向對象中的一個(gè)指向指針列表的指針,其中包括接口中的每個(gè)方法實(shí)現.代碼會(huì )調整Self指針(事實(shí)上指向了MyEditable) 指向一個(gè)對象的起始地址(通過(guò)在傳遞的指針中減去對象中IEditable的偏移量,然后調用真正的方法).這是類(lèi)實(shí)現的接口中方法的存根.

例如,假設實(shí)例的地址是50000, TWhatsit實(shí)現的IEditable接口在實(shí)例中指針偏移量是16.那么MyEditable指向50016. 50016處的IEditable 指針指向了在類(lèi)中實(shí)現的接口方法表(例如在30000),而后指向方法的存根(如在60000).存根知道由Self傳遞的值在50016,減去16得到50000.只是實(shí)現接口的對象地址.存根而后通過(guò)將50000作為Self的地址調用真實(shí)的函數.

上圖為簡(jiǎn)化忽略的QueryInterface, _AddRef和_Release的存根.

知道為什么需要用鉛筆盒紙張了吧?  ;-)

引用參數

引用參數通常叫做var 參數,但out 參數也是引用參數.

引用參數在實(shí)際傳參時(shí)并不會(huì )將真正的值傳遞給例程,而是傳遞參數地址.例如:

procedure SetBit(var Int: Integer; Bit: Integer);

begin

  Int := Int or (1 shl Bit);

end;

或多或少等價(jià)于下面的代碼:

procedure SetBit(Int: PInteger; Bit: Integer);

begin

  Int^ := Int^ or (1 shl Bit);

end;

不同之處:

       沒(méi)有使用指針語(yǔ)法.使用參數名稱(chēng)自動(dòng)進(jìn)行對參數解引用,即使用參數名稱(chēng)就可操作目標變量,而不是指針.

       引用參數不能被修改.使用參數名稱(chēng)來(lái)執行目標變量,不能將其指向其他地址,或對其增減操作.

       必須傳遞有地址的變量,例如,一個(gè)實(shí)際的內存存儲單元需要做一些變換.因此對于整型的引用參數,不能傳遞為17, 98765,或 Abs(MyInteger). 必須是一個(gè)變量(包括數組中的元素,對象或結構體中的成員等).

       實(shí)參必須與聲明的參數類(lèi)型相同,如,對聲明為TObject的參數不能傳遞為TEdit.為避免這個(gè)問(wèn)題需要聲明無(wú)類(lèi)型引用參數(untyped reference parameters).

語(yǔ)法上使用引用參數要比使用指針參數簡(jiǎn)單.但需要注意一些潛規則.傳遞指針,增加了一級間接引用.換句話(huà)說(shuō)如果使用指針P指向一個(gè)整數,要傳遞參數必須轉換為P^:

var

  Int: Integer;

  Ptr: PInteger;

  Arr: array of Integer;

begin

  // Initialisation of Int, Ptr and Arr not shown...

  SetBit(Ptr^, 3);    // Ptr is passed

  SetBit(Arr[2], 11); // @Arr[2] is passed

  SetBit(Int, 7);     // @Int is passed

無(wú)類(lèi)型參數

無(wú)類(lèi)型參數也是引用參數,但可以是varconstout.可以傳遞任意類(lèi)型的參數,簡(jiǎn)化了可以接受任意大小和類(lèi)型參數的例程編寫(xiě),但是這也需要有機制傳遞參數的類(lèi)型信息,或作為類(lèi)型無(wú)關(guān)的例程.訪(fǎng)問(wèn)參數時(shí)必須進(jìn)行類(lèi)型轉換.

內部無(wú)類(lèi)型參數也是作為指針傳遞的.如下兩個(gè)范例中,第一個(gè)通用例程可以填充任意大小的緩沖區,而參數Buffer的類(lèi)型并不重要:

// Example of routine where type doesn't matter

procedure FillBytes(var Buffer; Count: Integer;

  Values: array of Byte);

var

  P: PByte;

  I: Integer;

  LenValues: Integer;

begin

  LenValues := Length(Values);

  if LenValues > 0 then

  begin

    P := @Buffer; // Treat buffer as array of byte.

    I := 0;

    while Count > 0 do

    begin

      P^ := Values[I];

      I := (I + 1) mod LenValues;

      Inc(P);

      Dec(Count);

    end;

  end;

end;

第二個(gè)TTypedList 的子類(lèi)TIntegerList中的方法:

function TIntegerList.Add(const Value): Integer;

begin

  Grow(1);

  Result := Count - 1;

  // FInternalArray: array of Integer;

  FInternalArray[Result] := Integer(Value);

end;

可見(jiàn),使用指針必須傳遞一個(gè)實(shí)參地址,即使參數已經(jīng)是需要的指針了.而且,間接引用級別增加.

要存取引用目標,可以簡(jiǎn)單的作為正常的引用參數使用,但必須進(jìn)行類(lèi)型轉換,編譯器指定如何對指針進(jìn)行解引用.

注意間接引用級別.如果需要使用FillBytes函數初始化動(dòng)態(tài)數組,不能傳遞變量,而是需要傳遞數組中第一個(gè)元素.事實(shí)上,也可傳遞靜態(tài)數組的首地址.因此如果要將數組作為無(wú)類(lèi)型引用參數的實(shí)參進(jìn)行傳遞,最后傳遞第一個(gè)元素而不是數組本身,除非要故意的訪(fǎng)問(wèn)錯誤的動(dòng)態(tài)數組.

數據結構

指針很廣泛的用于數據結構中,如鏈接列表,樹(shù)和層等.在此不作討論.需要說(shuō)明的是高級的結構沒(méi)有指針和引用是無(wú)法實(shí)現的,雖然很多語(yǔ)言(Java)官方說(shuō)不使用指針.要了解更多結構的信息請閱讀相關(guān)專(zhuān)題的文檔.

如下是簡(jiǎn)單的數據結構圖表,非常依賴(lài)于指針的鏈接列表:

如果使用這樣的結構,通常都是在類(lèi)內部進(jìn)行了封裝,使用指針可以減少類(lèi)內部的實(shí)現難度,但不會(huì )將指針暴露在公共接口中.指針很強大,但很難駕馭,盡量避免使用.

結論

這里給出了很多指針的知識面.另一方面,使用帶箭頭的圖表對理解復雜指針或接口變量,對象,類(lèi)和代碼間的關(guān)系很有幫助.繪制的圖都是用于理解非常復雜的指針.

本文要闡述的是指針無(wú)處不在,即使沒(méi)有看到他們.但不能濫用,理解指針可以更好的理解下層機制,可以避免很多錯誤.

希望對于大家有所幫助.本文一定不是很全面,需要改進(jìn)之處請e-mail.

Rudy Velthuis

http://blog.csdn.net/henreash/article/details/7368088

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
delphi 指針操作
指向函數的指針
CSDN技術(shù)中心 第一章 什么是數組名?----一個(gè)讓你吃驚的事實(shí)!
水滴石穿C語(yǔ)言之指針綜合談 - C語(yǔ)言、C++編程技術(shù)館 - 博客園
C語(yǔ)言學(xué)習之基礎知識點(diǎn)—指針!上萬(wàn)字的干貨知識
C語(yǔ)言指針從入門(mén)到精通
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久