附件资料
*指针的使用(代码)
示例:简单的指针应用
代码:
procedure TForm1.Button2Click(Sender: TObject);
var
a: Integer;
p: ^Integer;
begin
with self.Memo1.Lines do
begin
a := 100;
Add('数据内容:' + inttostr(a));
//---
p := @a;
Add('数据地址:' + inttostr(Integer(p)));
//---
Add('数据地址指向的内容:' + 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数据内容:' + inttostr(a));
//---
p := @a;
Add('Integer指针内容:' + inttostr(Integer(p)));
Add('Integer指针指向的数据:' + inttostr(p^));
//---
pp := @p;
Add('PInteger指针内容:' + inttostr(Integer(pp)));
Add('PInteger指针指向的数据:' + inttostr(Integer(pp^)));
end;
end;
*指针的使用(汇编)
示例:简单的指针应用
说明:
对于编译器来说,指针的类型可以用来标明地址所指向区域的大小、所指向的类型(整型、对象、方法),以及进行指针运算时指针偏移的长度。对于有类型指针的操作最终反映到编译器的可执行代码中,如果不参考可执行代码,单凭一个内存地址,我们并无法判断地址所指向的数据类型。
代码:
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;
汇编:
编译器根据源代码中声明的变量类型分配内存如下:
[ebp - $04]存储为self,大小为4字节,因为self声明为对象指针
[ebp - $08]标识为a[1],大小为4字节,因为a[0]声明为Integer
[ebp - $0c]标识为a[0]
[ebp - $10]标识为b,大小为4字节,因为b声明为Integer
[ebp - $14]标识为p,大小为4字节,因为p声明为Integer指针
[ebp - $18]存储为Sender,大小为4字节,因为Sender声明为对象指针
其中“ebp - $04”为内存地址,“[ebp - $04]”为地址所指向的数据,ebp、eax为32位寄存器可以用于存取一个4字节数据。

代码:
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;
汇编:
编译器根据源代码中声明的变量类型分配内存如下:
[ebp - $04]存储为self,大小为4字节,因为self声明为对象指针
[ebp - $05]标识为a[1],大小为4字节,因为a[0]声明为Integer
[ebp - $06]标识为a[0]
[ebp - $07]标识为b,大小为4字节,因为b声明为Integer
[ebp - $0c]标识为p,大小为4字节,因为p声明为Integer指针
[ebp - $10]存储为Sender,大小为4字节,因为Sender声明为对象指针
其中编译器为了实现数据对齐,所以申请了大小为$10的空间。

*复杂数据类型指针(代码)
示例:字符串指针
代码:
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('数据内存大小:' + IntToStr(SizeOf(b1)));
Add('数据内容大小:' + IntToStr(PByte(@b1)^));
//---
b2 := @b1;
Add('指针大小:' + IntToStr(SizeOf(b2)));
Add('指针指向数据内存大小:' + IntToStr(SizeOf(b2^)));
end;
end;
说明:
首先,ptr是一个指针类型,而b2是这个指针类型的变量。
其次,ptr是一个指向255长度的短字符串类型的指针,加上一个看不见的计数字节,共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)、过程/函数指针是一个32位的指针。
代码:
procedure TForm1.Button2Click(Sender: TObject);
type
TMyMethod = function(X: Integer): Integer;
var
F: TMyMethod;
begin
Self.Memo1.Lines.Add('函数指针大小:' + IntToStr(SizeOf(TMyMethod)));
end;
示例:过程指针的应用
说明:
(1)、通过赋值语句“过程变量 := 过程”,使得过程变量指向过程或者函数入口地址。
(2)、在赋值语句“过程变量 := 过程”中,左边变量的类型决定了右边的过程或者方法指针解释。
(3)、可以使用过程变量引用声明的过程或者函数。
(4)、无论何时一个过程变量(procedural variable)出现在一个表达式中,它表示调用所指向的函数或者过程。
(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); //--调用所指向的函数
end;
procedure TForm1.Button2Click(Sender: TObject);
var
F: function(X: Integer): Integer;
I: Integer;
begin
F := SomeFunction; //--给f赋值
I := F(4); //--调用所指向的函数
//---
Self.Memo1.Lines.Add('函数指针大小' + 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; //--调用函数
end;
说明:
第一句获得函数的入口,第二句将指针复制,第三句获得函数的返回值。
示例:过程指针的函数调用
代码:
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('比较的函数返回值得结果');
end;
说明:
在if语句中,F的出现导致一个函数调用;编译器调用F指向的函数,然后调用Myfunction,比较结果。这个规则是无论何时一个过程变量出现在一个表达式中,它表示调用所指向的函数或者过程。有时F指向一个过程(没有返回值),或者f指向一个需要参数的函数,则前面的语句会产生一个编译错误。
示例:比较过程入口地址
代码:
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('比较函数地址');
end;
procedure TForm1.Button2Click(Sender: TObject);
var
F: function: Integer;
P:Pointer;
begin
F := MyFunction; //--给f赋值
P := @F;
if P = @MyFunction then
ShowMessage('比较函数地址');
end;
说明:
@F把F转换成一个包含地址的无类型的指针变量,@myfunction返回myfunction的地址。
*方法指针(代码)
示例:方法指针的结构
说明:
(1)、方法指针指向一个结构,其结构如下
TMethod = record
Code: Pointer;//指向过程/函数的指针
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('方法指针大小:' + 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); //--执行方法
finally
AMyObject.Free;
end;
end;
说明:
TWndMethod 是一种对象方法类型,它指向一个接收Integer) 类型参数的过程,但它不是一般的静态过程,它是对象相关(object related)的。TWndMethod 在内存中存储为一个指向过程的指针和一个对象的指针,所以占用8个字节。TWndMethod类型的变量必须使用已实例化的对象来赋值。
如果把 TWndMethod变量赋值给虚方法,这时,编译器实现为 SomeMethod 指向AMyObject对象虚方法表中的TestMethod 过程的地址和AMyObject对象的地址。也就是说编译器正确地处理了虚方法的赋值。调用 SomeMethod(1) 就等于调用AMyObject.TestMethod (Message)。
在可能被赋值的情况下,对象方法最好不要设计为有返回值的函数(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;
示例:方法指针指向过程/函数
说明:
(1)、不能使用赋值语句“对象方法指针 := @过程/函数 ”,因为对象方法指针和过程指针(“@过程/函数”返回一个过程指针)类型不同,它们各自的空间大小也不同,普通的过程指针不包含对象指针,所以不能用于给对象方法指针类型赋值。
(2)、因为在Delphi中给对象方法指针赋值必须加上对象实例,如TestObj.Hello才行,一种投机的方法是可以通过TMethod将对象方法指针重定向到一个全局函数。
(3)、由于对象方法包括一个隐含对象指针参数Self,所以定义过程/函数时必须加入一个假参数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;
//--类方法隐藏了第一个参数为对象的 Self (放在EAX中传递)
//--故第一个参数为 Self: TObject,第二个参数对应 TNotifyEvent 的参数
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 参数值,可为任意值,这里传nil)
end;
//---
AMyObject := TMyObject.Create;
try
AMyObject.TestEvent := TNotifyEvent(M);
AMyObject.ExecTestEvent(nil);
finally
AMyObject.Free;
end;
end;
说明:
(1)、第一个参数Self: TObject;绝对不是多余的,虽然你不用它编译也能通过,还可能执行正确,但如果你想在过程中调用对象之真的话,没有它可不行。
Delphi默认使用寄存器加堆栈的方式传递过程参数,三个参数以内使用EAX、EDX、ECX的顺序保存参数,三个以后用堆栈。对类的方法来说,EAX恒定是类实例指针Self,这样Sender参数实际上放在EDX中做为第二个参数传递,如下面的代码:
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认为只有最后两个参数放在堆栈,返回时POP EIP得到的是错误的指针,你将看到一个非法访问内存错。
(2)、M.Data := nil;我指的是语法上、运行时可为任何值而没有错误。
M.Data保存了方法所对应的对象指针,用于在调用该方法时提供Self这个隐藏参数,在普通过程中其实是没有用的。难道你还想在过程中用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字节),事实上VCL在调用事件时,也是按TMethod的结构来调用的,而TNofityEvent等类型在本质上与TMethod结构相同,但在Delphi这种强类型语言中使用它们可以避免很多错误。
示例:方法指针指向过程/函数
代码:
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;
示例:方法指针指向过程/函数
代码:
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;
示例:方法指针调用过程/函数
代码:
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;
说明:
在这里还有一个有趣的现象,我们不但可以用“Edit1^.Text”还可以用“Edit1.Text”方式访问对象的属性和方法,其结果是一样的,所以在Delphi中只要是对象都是按指针实现的。
本文详细阐述了Delphi中指针的使用,包括简单指针应用、复杂数据类型指针(如字符串指针)、记录指针、过程指针及方法指针的应用。文章通过代码示例和汇编指令解析,展示了指针如何在不同类型场景下发挥作用,旨在帮助开发者更深入地理解和运用指针特性。

1352

被折叠的 条评论
为什么被折叠?



