指針是存放指定類型(或未定義類型)變量內存地址的變量,因此指針間接引用一個值。
指針可以分為兩大類:無類型指針(Untyped Pointer)和有類型指針(Typed Pointer)。
直接用Pointer聲明的變量就是無類型指針,可以在使用時指向任何數據類型。有類型指針所能指向的數據是固定類型的,至少必須是兼容的,比如PInteger不能指向一個字符串,但可以指向一個Byte或者Word變量。
指針的常用操作符是@(或者函數Addr,取得地址,通常用於給指針變量賦值)和^(從指針變量取得實際的數據)。
有類型指針在你的Type部分用^(或Pointer)運算符聲明。對於有類型指針來說,編譯器能准確地跟蹤指針所指內容的數據類型,這樣用指針變量,編譯器就能跟蹤正在進行的工作。
例1:
program Project1; {$APPTYPE CONSOLE} uses SysUtils; type PInt = ^Integer; { 指向Integer的指針} POneRecord = ^TOneRecord; { 指向TOneRecord類型的指針} TOneRecord = record Name: string; Age: Word; end; var OneRecord: TOneRecord; { 定義一個TOneRecord記錄變量} PRecord: POneRecord; { 定義一個指向TOneRecord類型的指針變量} begin OneRecord.Name := '瓢蟲1'; { 初始化OneRecord記錄變量} OneRecord.Age := 28; PRecord := @OneRecord; { 讓PRecord指向OneRecord} PRecord.Name := '瓢蟲2'; Writeln('PRecord.Name = ', PRecord.Name); { 上面語句可以直接通過指針直接存取實際數據,實際上編譯器內部將此語句已經轉化為 PRecord^.Name,所以表面上的不合法最終被改造成了合法} PRecord^.Name := '瓢蟲3'; Writeln('PRecord^.Name = ', PRecord^.Name); Readln; end.
運行結果如下:
出了表示已分配內存的地址外,指針還能通過New()函數在堆中動態分配內存,不過當你不需要這個指針時,你必須調用Dispose()函數來釋放你動態分配的內存。
New()函數能為一個指針分配指定長度的內存空間,在為某記錄結構分配內存時,因為編譯器知道分配的內存大小,所以調用New()函數就能分配到所需的字節。而起它比GetMem()和AllocMem()更安全、更容易使用。但不能用New()為Pointer或者PChar變量分配內存,因為編譯器不知道需要分配多少內存。
當編譯器不知道要分配多少內存時,就要用到GetMem()和AllocMem(),在對PChar和Pointer類型分配內存時,編譯器不可能提前告訴你要分配多少,因為它們有長度可變特性。要注意,不要試圖操作分配空間以外的數據,因為這是導致“Access Violation”錯誤最常見的原因。用FreeMem()來釋放由GetMem()和AllocMem()分配的內存。AllocMem()要比GetMem()安全,因為AllocMem()總是把分配給它的內存初始化為零。
例2:
program Project1; {$APPTYPE CONSOLE} uses SysUtils; type TMyRec = record I: Integer; S: string; R: Real; end; PMyRec = ^TMyRec; var MyRec: PMyRec; begin New(MyRec); { 為MyRec分配內存} MyRec^.I := 10; { 為MyRec中的字段初始化} MyRec^.S := 'And now for something completely different.'; MyRec^.R := 6.384; { do something ...} Dispose(MyRec); { 不要忘記了釋放空間} end.
如果指針沒有值,你可以把nil賦值給它。這樣,你可以通過檢查指針是否為nil來判斷指針當前是否引用一個值。這經常會用到,因為訪問一個空指針的值會引起一個訪問沖突錯誤,在Win7中將產生“APPCRASH”錯誤(程序崩潰)。
例3:
{ 此程序請編譯生成可執行文件后,脫離IDE單獨運行,才能看到錯誤} { 在Win7中將報出APPCRASH錯誤,程序崩潰} program Project1; {$APPTYPE CONSOLE} uses SysUtils; var P: ^Integer; begin P := nil; Writeln(IntToStr(P^)); Readln; end.
訪問nil指針將引起系統錯誤,如下:
如果將上面的程序加以修改,訪問數據就安全了,將一個已經存在的局部變量賦值給指針,指針使用就安全了,雖然如此,還是加上一個安全檢查語句。
例4、
program Project1; {$APPTYPE CONSOLE} uses SysUtils; var P: ^Integer; X: Integer; begin P := @X; X := 100; if P <> nil then { 增加一個安全判斷,判斷是否為nil} Writeln(P^); Readln; end.
運行后結果為:
Delphi中很多數據類型在內部的實現為一個指針的,盡管它們表面看起來不是這樣。比如類和接口的實例本身就是指針,還有如長字符串、動態數組、類引用等實際上也是指針,它們在內部都是通過指針來實際實現數據存取的。
我們可以用給一個簡單的方法拍判斷某個類型或者變量實際上是否是指針:如果SizeOf(一個類型或變量)返回(返回值是該數據類型占據的內存大小,以字節為單位)4,而這個類型或者變量的實際數據又並不是4個字節空間可以完全存儲的,那么此時它很可能是一個指針。
例5、
program Project1; {$APPTYPE CONSOLE} uses SysUtils; var { 以下4個變量的數據顯然不是4個字節能完全存儲的} A: array[0..1] of Integer; DA: array of Integer; SS: string[10]; S: string; procedure ShowInfo(obj: string); begin Writeln(obj, '實際是一個指針。'); end; begin SetLength(DA, 2); if SizeOf(A) = 4 then ShowInfo('A'); if SizeOf(DA) = 4 then { 結果為True, 表明動態數組實際是一個指針} ShowInfo('DA'); if SizeOf(SS) = 4 then ShowInfo('SS'); if SizeOf(S) = 4 then { 結果為True, 表明長字符串實際是一個指針} ShowInfo('S'); Readln; end.
運行結果如下:
若要進行高級編程和完全理解Delphi對象模型,理解指針是很重要的,因為Delphi對象模型在幕后使用了指針。
以上代碼均在Delphi7中測試通過,示例代碼下載:20111228指針(Pointer).rar