附件資料*指針的使用(代碼)示例:簡單的指針應用 代碼: procedure TForm1.Button2Click(Sender: TObject); var a: Integer; p: ^Integer; begin with self.Memo1.Lines do begin a := 100; Add('數(shù)據(jù)內容:' + inttostr(a)); //--- p := @a; Add('數(shù)據(jù)地址:' + inttostr(Integer(p))); //--- Add('數(shù)據(jù)地址指向的內容:' + inttostr(p^)); end; end;
procedure TForm1.Button2Click(Sender: TObject); type PInteger = ^Integer; PPInteger = ^PInteger; var a: Integer; p: PInteger; pp: PPInteger; begin with self.Memo1.Lines do begin a := 100; Add('Integer數(shù)據(jù)內容:' + inttostr(a)); //--- p := @a; Add('Integer指針內容:' + inttostr(Integer(p))); Add('Integer指針指向的數(shù)據(jù):' + inttostr(p^)); //--- pp := @p; Add('PInteger指針內容:' + inttostr(Integer(pp))); Add('PInteger指針指向的數(shù)據(jù):' + inttostr(Integer(pp^))); end; end;
*指針的使用(匯編)示例:簡單的指針應用 說明: 對于編譯器來說,指針的類型可以用來標明地址所指向區(qū)域的大小、所指向的類型(整型、對象、方法),以及進行指針運算時指針偏移的長度。對于有類型指針的操作最終反映到編譯器的可執(zhí)行代碼中,如果不參考可執(zhí)行代碼,單憑一個內存地址,我們并無法判斷地址所指向的數(shù)據(jù)類型。 代碼: procedure TForm1.Button2Click(Sender: TObject); var a:array[0..1] of Integer; b: Integer; p: ^Integer; begin a[0] := 1; a[1] := 2; //--- p := @a[0]; b := p^; //--- Inc(p); b := p^; end; 匯編: 編譯器根據(jù)源代碼中聲明的變量類型分配內存如下: [ebp - $04]存儲為self,大小為4字節(jié),因為self聲明為對象指針 [ebp - $08]標識為a[1],大小為4字節(jié),因為a[0]聲明為Integer [ebp - $0c]標識為a[0] [ebp - $10]標識為b,大小為4字節(jié),因為b聲明為Integer [ebp - $14]標識為p,大小為4字節(jié),因為p聲明為Integer指針 [ebp - $18]存儲為Sender,大小為4字節(jié),因為Sender聲明為對象指針 其中“ebp - $04”為內存地址,“[ebp - $04]”為地址所指向的數(shù)據(jù),ebp、eax為32位寄存器可以用于存取一個4字節(jié)數(shù)據(jù)。
代碼: procedure TForm1.Button2Click(Sender: TObject); var a:array[0..1] of Byte; b: Byte; p: ^Byte; begin a[0] := 1; a[1] := 2; //--- p := @a[0]; b := p^; //--- Inc(p); b := p^; end; 匯編: 編譯器根據(jù)源代碼中聲明的變量類型分配內存如下: [ebp - $04]存儲為self,大小為4字節(jié),因為self聲明為對象指針 [ebp - $05]標識為a[1],大小為4字節(jié),因為a[0]聲明為Integer [ebp - $06]標識為a[0] [ebp - $07]標識為b,大小為4字節(jié),因為b聲明為Integer [ebp - $0c]標識為p,大小為4字節(jié),因為p聲明為Integer指針 [ebp - $10]存儲為Sender,大小為4字節(jié),因為Sender聲明為對象指針 其中編譯器為了實現(xiàn)數(shù)據(jù)對齊,所以申請了大小為$10的空間。
*復雜數(shù)據(jù)類型指針(代碼)示例:字符串指針 代碼: procedure TForm1.Button2Click(Sender: TObject); type buffer = string[255]; ptr = ^buffer; var b1: buffer; b2: ptr; begin with self.Memo1.Lines do begin b1 := 'zhang'; Add('數(shù)據(jù)內存大小:' + IntToStr(SizeOf(b1))); Add('數(shù)據(jù)內容大?。?/span>' + IntToStr(PByte(@b1)^)); //--- b2 := @b1; Add('指針大小:' + IntToStr(SizeOf(b2))); Add('指針指向數(shù)據(jù)內存大?。?/span>' + IntToStr(SizeOf(b2^))); end; end; 說明: 首先,ptr是一個指針類型,而b2是這個指針類型的變量。 其次,ptr是一個指向255長度的短字符串類型的指針,加上一個看不見的計數(shù)字節(jié),共256B。當ptr沒有分配內存空間,也沒初始化時指向的地址是隨機的,隨意引用可能會出問題。但是只要它指向了某個地址,就代表它指向了256B的空間,它可以指向任何有效的字符串變量,但是無論被指向的字符串有多長,b2^的長度始終是256。
*記錄指針(代碼)示例:記錄指針的應用 代碼: procedure TForm1.Button2Click(Sender: TObject); type PTMyRecord = ^TMyRecord; TMyRecord = record Data1:Integer; Data2:Integer; end; var ARecord: TMyRecord; pRecord: PTMyRecord; begin pRecord := @ARecord; ARecord.Data1 := 11; pRecord^.Data2 := 22; pRecord.Data2 := 33; end;
*過程指針(代碼)示例:方法指針的結構 說明: (1)、過程/函數(shù)指針是一個32位的指針。 代碼: procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = function(X: Integer): Integer; var F: TMyMethod; begin Self.Memo1.Lines.Add('函數(shù)指針大小:' + IntToStr(SizeOf(TMyMethod))); end;
示例:過程指針的應用 說明: (1)、通過賦值語句“過程變量 := 過程”,使得過程變量指向過程或者函數(shù)入口地址。 (2)、在賦值語句“過程變量 := 過程”中,左邊變量的類型決定了右邊的過程或者方法指針解釋。 (3)、可以使用過程變量引用聲明的過程或者函數(shù)。 (4)、無論何時一個過程變量(procedural variable)出現(xiàn)在一個表達式中,它表示調用所指向的函數(shù)或者過程。 (5)、對于過程變量P, @P把P轉換成一個包含地址的無類型的指針變量(即@P等價于無類型指針P),此時可以把一個無類型的指針值賦給過程變量P。 代碼: function SomeFunction(X: Integer): Integer; begin ShowMessage(IntToStr(X)); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function(X: Integer): Integer; I: Integer; begin F := SomeFunction; //--給f賦值 I := F(4); //--調用所指向的函數(shù) end;
procedure TForm1.Button2Click(Sender: TObject); var F: function(X: Integer): Integer; I: Integer; begin F := SomeFunction; //--給f賦值 I := F(4); //--調用所指向的函數(shù) //--- Self.Memo1.Lines.Add('函數(shù)指針大小' + IntToStr(SizeOf(@SomeFunction))); end;
示例:過程指針的應用 代碼: function SomeFunction: Integer; begin Result := 0; ShowMessage('0'); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; P:Pointer; begin P := @SomeFunction; //--給P賦值 @F := P; //--給f賦值 F; end; 說明: 可以使用@操作符,把一個無類型的指針值賦給一個過程變量,如調用“@過程變量 := 無類型指針值”,此時過程變量指向這個值。
示例:過程指針的應用 代碼: function SomeFunction: Integer; begin Result := 0; ShowMessage('0'); end;
procedure TForm1.Button2Click(Sender: TObject); var F,G: function: Integer; I: Integer; begin F := SomeFunction; //--給F賦值 G := F; //--把F的值拷貝給G I := G; //--調用函數(shù) end; 說明: 第一句獲得函數(shù)的入口,第二句將指針復制,第三句獲得函數(shù)的返回值。
示例:過程指針的函數(shù)調用 代碼: function SomeFunction: Integer; begin Result := 0; end;
function MyFunction: Integer; begin Result := 0; end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; I: Integer; begin F := SomeFunction; //--給f賦值 if F = MyFunction then ShowMessage('比較的函數(shù)返回值得結果'); end; 說明: 在if語句中,F的出現(xiàn)導致一個函數(shù)調用;編譯器調用F指向的函數(shù),然后調用Myfunction,比較結果。這個規(guī)則是無論何時一個過程變量出現(xiàn)在一個表達式中,它表示調用所指向的函數(shù)或者過程。有時F指向一個過程(沒有返回值),或者f指向一個需要參數(shù)的函數(shù),則前面的語句會產生一個編譯錯誤。
示例:比較過程入口地址 代碼: function SomeFunction: Integer; begin Result := 0; end;
function MyFunction: Integer; begin Result := 0; ShowMessage('123'); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; begin F := MyFunction; //--給f賦值 if @F = @MyFunction then ShowMessage('比較函數(shù)地址'); end;
procedure TForm1.Button2Click(Sender: TObject); var F: function: Integer; P:Pointer; begin F := MyFunction; //--給f賦值 P := @F; if P = @MyFunction then ShowMessage('比較函數(shù)地址'); end; 說明: @F把F轉換成一個包含地址的無類型的指針變量,@myfunction返回myfunction的地址。
*方法指針(代碼)示例:方法指針的結構 說明: (1)、方法指針指向一個結構,其結構如下 TMethod = record Code: Pointer;//指向過程/函數(shù)的指針 Data: Pointer;//指向對象的指針 end; 代碼: type TMyObject = class(TObject) public procedure Method1(Sender: TObject); end;
procedure TMyObject.Method1(Sender: TObject); begin ShowMessage('123'); end;
procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = procedure(Sender: TObject) of object; var AMyMethod: TMyMethod; AMyObject: TMyObject; begin with self.Memo1.Lines do begin Add('方法指針大?。?/span>' + IntToStr(SizeOf(TMyMethod))); //--- AMyObject := TMyObject.Create; try Add('對象指針:' + IntToHex(Integer(AMyObject),2)); Add('對象方法指針:' + IntToHex(Integer(@TMyObject.Method1),2)); //--- AMyMethod := AMyObject.Method1; //--- with TMethod(AMyMethod) do begin Add('對象指針:' + IntToHex(Integer(Data),2)); Add('對象方法指針:' + IntToHex(Integer(Code),2)); end; //--- AMyMethod(AMyObject); finally AMyObject.Free; end; end; end;
示例:方法指針的結構 代碼: type TMyObject = class(TObject) public procedure TestMethod(Value: Integer); virtual; end; TMyObject1 = class(TMyObject) public procedure TestMethod(Value: Integer); override; end;
procedure TMyObject.TestMethod(Value: Integer); begin ShowMessage('TMyObject:' + inttostr(Value)); end;
procedure TMyObject1.TestMethod(Value: Integer); begin inherited; ShowMessage('TMyObject1:' + inttostr(Value)); end;
procedure TForm1.Button2Click(Sender: TObject); type TWndMethod = procedure(Value: Integer) of object; var SomeMethod: TWndMethod; AMyObject: TMyObject1; Msg: TMessage; begin AMyObject := TMyObject1.Create; try SomeMethod := AMyObject.TestMethod; //--賦值后SomeMethod包含TestMethod和 AMyObject的指針 //SomeMethod := TMyObject1.TestMethod; //--錯誤!不能用類引用。 SomeMethod(1); //--執(zhí)行方法 finally AMyObject.Free; end; end; 說明: TWndMethod 是一種對象方法類型,它指向一個接收Integer) 類型參數(shù)的過程,但它不是一般的靜態(tài)過程,它是對象相關(object related)的。TWndMethod 在內存中存儲為一個指向過程的指針和一個對象的指針,所以占用8個字節(jié)。TWndMethod類型的變量必須使用已實例化的對象來賦值。 如果把 TWndMethod變量賦值給虛方法,這時,編譯器實現(xiàn)為 SomeMethod 指向AMyObject對象虛方法表中的TestMethod 過程的地址和AMyObject對象的地址。也就是說編譯器正確地處理了虛方法的賦值。調用 SomeMethod(1) 就等于調用AMyObject.TestMethod (Message)。 在可能被賦值的情況下,對象方法最好不要設計為有返回值的函數(shù)(function),而要設計為過程(procedure)。原因很簡單,把一個有返回值的對象方法賦值給TWndMethod 變量,會造成編譯時的二義性。
示例:方法指針指向對象方法 代碼: type TMyObject = class(TObject) public procedure Method1(Sender: TObject); end;
procedure TMyObject.Method1(Sender: TObject); begin ShowMessage('123'); end;
procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = procedure(Sender: TObject) of object; var AMyMethod: TMyMethod; AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try AMyMethod := AMyObject.Method1; AMyMethod(AMyObject); finally AMyObject.Free; end; end;
示例:方法指針指向過程/函數(shù) 說明: (1)、不能使用賦值語句“對象方法指針 := @過程/函數(shù) ”,因為對象方法指針和過程指針(“@過程/函數(shù)”返回一個過程指針)類型不同,它們各自的空間大小也不同,普通的過程指針不包含對象指針,所以不能用于給對象方法指針類型賦值。 (2)、因為在Delphi中給對象方法指針賦值必須加上對象實例,如TestObj.Hello才行,一種投機的方法是可以通過TMethod將對象方法指針重定向到一個全局函數(shù)。 (3)、由于對象方法包括一個隱含對象指針參數(shù)Self,所以定義過程/函數(shù)時必須加入一個假參數(shù)const pSelf: Pointer以保證對象指針self能夠被傳入。 代碼: type TMyObject = class(TObject) private FTestEvent: TNotifyEvent; public procedure ExecTestEvent(Sender: TObject); //--- published property TestEvent: TNotifyEvent read FTestEvent write FTestEvent; end;
//--類方法隱藏了第一個參數(shù)為對象的 Self (放在EAX中傳遞) //--故第一個參數(shù)為 Self: TObject,第二個參數(shù)對應 TNotifyEvent 的參數(shù) procedure Gproc(Self: TObject; Sender: TObject); begin ShowMessage('Test'); end;
procedure TMyObject.ExecTestEvent(Sender: TObject); begin if Assigned(FTestEvent) then FTestEvent(Sender); end;
procedure TForm1.Button2Click(Sender: TObject); var M: TMethod; //--方法記錄 AMyObject: TMyObject; begin with M do begin Code := @Gproc; //--指向方法(這里是全局過程)地址 Data := nil; //--指向類實例(該值對應著 Gproc 調用時的 Self 參數(shù)值,可為任意值,這里傳nil) end; //--- AMyObject := TMyObject.Create; try AMyObject.TestEvent := TNotifyEvent(M); AMyObject.ExecTestEvent(nil); finally AMyObject.Free; end; end; 說明: (1)、第一個參數(shù)Self: TObject;絕對不是多余的,雖然你不用它編譯也能通過,還可能執(zhí)行正確,但如果你想在過程中調用對象之真的話,沒有它可不行。 Delphi默認使用寄存器加堆棧的方式傳遞過程參數(shù),三個參數(shù)以內使用EAX、EDX、ECX的順序保存參數(shù),三個以后用堆棧。對類的方法來說,EAX恒定是類實例指針Self,這樣Sender參數(shù)實際上放在EDX中做為第二個參數(shù)傳遞,如下面的代碼: procedure MyMouseDown(Self: TObject; Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin ShowMessage(Format('Self: %d, Sender: %d, X: %d, Y: %d', [Integer(Self), Integer(Sender), X, Y])); end; procedure TForm1.FormCreate(Sender: TObject); var M: TMethod; begin M.Code := @MyMouseDown; M.Data := nil; Self.OnMouseDown := TMouseEvent(M); end; 上面的代碼: Self --> EAX Sender --> EDX Button --> CL // TMouseButton枚舉類型其實內部作Byte用 其它三個放堆棧。 如果把Self: TObject去掉,則MyMouseDown認為只有最后兩個參數(shù)放在堆棧,返回時POP EIP得到的是錯誤的指針,你將看到一個非法訪問內存錯。 (2)、M.Data := nil;我指的是語法上、運行時可為任何值而沒有錯誤。 M.Data保存了方法所對應的對象指針,用于在調用該方法時提供Self這個隱藏參數(shù),在普通過程中其實是沒有用的。難道你還想在過程中用Self來訪問一個按鈕或窗體嗎?M.Data并不是Sender,Sender是由方法(這里是過程)調用方指定的。例如VCL源碼: procedure TControl.DblClick; begin if Assigned(FOnDblClick) then FOnDblClick(Self); end; 你修改M.Data來作為Sender其實是沒用的。
(3)、定義M: TMethod只需要賦值時做一次強制類型轉換。Delphi的類型強制轉換要求類型相容或長度一致,TMethod與對象方法指針是相容的(8字節(jié)),事實上VCL在調用事件時,也是按TMethod的結構來調用的,而TNofityEvent等類型在本質上與TMethod結構相同,但在Delphi這種強類型語言中使用它們可以避免很多錯誤。
示例:方法指針指向過程/函數(shù) 代碼: procedure TestMethod(const pSelf: Pointer; AData: Integer); begin ShowMessage(IntToStr(AData)); end;
procedure TForm1.Button2Click(Sender: TObject); type TMyMethod = procedure(AData: Integer) of object; var AMyMethod: TMyMethod; begin with TMethod(AMyMethod) do begin Code := @TestMethod; Data := nil; end; //--- AMyMethod(1); end;
示例:方法指針指向過程/函數(shù) 代碼: type TMyObject = class(TObject) procedure ExecMethod(const AMsg: string); end;
procedure TMyObject.ExecMethod(const AMsg: string); begin ShowMessage(AMsg); end;
procedure TestMethod(pSelf: TObject; const AMsg: string); begin ShowMessage(AMsg); if pSelf is TMyObject then TMyObject(pSelf).ExecMethod(AMsg); end; procedure TForm1.Button2Click(Sender: TObject); var AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try TestMethod(AMyObject,'123'); finally AMyObject.Free; end; end; 代碼: type TFakeEvent = procedure(const AMsg: string) of object;
TMyObject = class(TObject) private FTestEvent: TFakeEvent; public constructor Create; //--- procedure ExecTestEvent(const AMsg: string); procedure ExecMethod(const AMsg: string); end;
procedure TestMethod(pSelf: TObject; const AMsg: string); begin ShowMessage(AMsg); if pSelf is TMyObject then TMyObject(pSelf).ExecMethod(AMsg); end;
constructor TMyObject.Create; begin with TMethod(FTestEvent) do begin Code := @TestMethod; Data := self; end; end;
procedure TMyObject.ExecTestEvent(const AMsg: string); begin if Assigned(FTestEvent) then FTestEvent(AMsg); end;
procedure TMyObject.ExecMethod(const AMsg: string); begin ShowMessage(AMsg); end;
procedure TForm1.Button2Click(Sender: TObject); var AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try AMyObject.ExecTestEvent('123'); finally AMyObject.Free; end; end;
示例:方法指針調用過程/函數(shù) 代碼: type TFakeEvent = procedure(const AMsg: string) of object;
TMyObject = class(TObject) private FTestEvent: TFakeEvent; procedure TestEvent(const AMsg: string); public constructor Create; //--- procedure ExecTestEvent(const AMsg: string); end;
procedure TestMethod(const AMsg: string); begin ShowMessage(AMsg); end;
constructor TMyObject.Create; begin FTestEvent := self.TestEvent; end;
procedure TMyObject.ExecTestEvent(const AMsg: string); begin if Assigned(FTestEvent) then FTestEvent(AMsg); end;
procedure TMyObject.TestEvent(const AMsg: string); begin TestMethod(AMsg); end;
procedure TForm1.Button2Click(Sender: TObject); var AMyObject: TMyObject; begin AMyObject := TMyObject.Create; try AMyObject.ExecTestEvent('123'); finally AMyObject.Free; end; end;
*對象指針(代碼)示例:方法指針的結構 說明:DELPHI中的對象是是一個32位的指針。 代碼: procedure TForm1.Button2Click(Sender: TObject); var AObject: TObject; begin AObject := TObject.Create; try Self.Memo1.Lines.Add('對象指針大小:' + IntToStr(SizeOf(AObject))); finally AObject.Free; end; end;
示例:對象指針的地址 代碼: procedure TForm1.Button2Click(Sender: TObject); var pEdit: ^TEdit; begin pEdit := @Edit1; //--- with Self.Memo1.Lines do begin Add('對象內容:' + IntToStr(Integer(Edit1))); Add('對象指針的內容:' + IntToStr(Integer(pEdit))); Add('對象指針指向的內容:' + IntToStr(Integer(pEdit^))); end; //--- pEdit^.Text := '123'; pEdit.Text := '123' end; 說明: 在這里還有一個有趣的現(xiàn)象,我們不但可以用“Edit1^.Text”還可以用“Edit1.Text”方式訪問對象的屬性和方法,其結果是一樣的,所以在Delphi中只要是對象都是按指針實現(xiàn)的。
|
|