Delphi指针类型浅析

时间:2021-10-08 14:49:09

参考资料

浅谈Object Pascal的指针

Delphi 指针入门

 

基本知识

指针

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。  

注意:

1)、指针对应着一个数据在内存中的地址,得到了指针就可以*地修改该数据。

2)、一个指针变量仅仅是存储一个内存的地址,为指针所指向的内容分配空间是程序员要干的工作。

3)、如果一个指针没有指向任何数据,它的值是n i l,它就被称为是零( n i l )指针或空(null) 指针。

4)、要访问一个指针所指向的内容,在指针变量名字的后面跟上^运算符。这种方法称为对指针取内容。

5)、指针的指针就是用来存放指针所在的内存地址的。

 

要搞清一个指针,需要了解以下内容:

1)、指针的类型。

2)、指针所指向的类型。

3)、指针的值(即指针所指向的内存区)

4)、指针本身所占据的内存区。

 

指针大小

指针是一个无符号整数(unsigned int),它是一个以当前系统寻址范围为取值范围的整数。指针类型变量本身要占内存,占用内存的大小与机器硬件、操作系统以及编译器都有关系,最直接的关系就是编译器,现在的编译器大都是32位(4B)的,即使你的机器和操作系统都是是64位的,所以指针类型变量一般占用4B空间(也就是可表示2^32次方的地址空间)

 

指针类型

一个指针变量指示了内存的位置。PASCAL通用指针类型的名称是PointerPointer有时又被称为无类型指针,因为它只指向内存地址,但编译器并不管指针所指向的数据,所以建议你在大部分情况下用有类型的指针。

 

任何对象、结构、变量什么的,在内存里面,实质上就是字节流,那么很有可能某一个字节数组array of char的内容刚好和某一个对象的字节流内容一样,如果一个pointer指向的内容为上述字节内容,你能区分是那个对象还是array of char的字节数组?Pointer 作为一个无类型指针,可以指向任何元素。强制转换时,Delphi 并不知道 Pointer 指向的数据是什么类型。例如TObject(p) 就是一种强制转换,用于告诉编译器指针指向的数据是TObject的实例。也就是说:编译器不能确定类型转换的正确性!你必须自己负责该指针的实际指向!总得说来,无类型指针的转换是没有安全性的,你必须明确指针的用途才可以使用。

 

有类型指针在你的应用程序的Type部分用^ (Pointer)运算符声明。对于有类型指针来说,编译器能准确地跟踪指针所指内容的数据类型,这样用指针变量,编译器就能跟踪正在进行的工作。

 

对于编译器来说,指针的类型可以用来标明地址所指向内存区域的大小、所指向的数据类型(整型、对象、方法),以及进行指针运算时指针偏移的长度。对于有类型指针的操作最终反映到编译器的可执行代码中,如果不参考可执行代码,单凭一个内存地址,我们无法判断地址的实际用途。

 

数据类型

从数据类型的简单或复杂程序上来说分为二种:

1)、简单数据类型

对于简单数据类型(或叫基础数据类型),编译器分配内存时直接为其分配存储单元!

2)、复杂数据类型

对于复杂数据类型,编译器分配内存时先在栈中分配一个指针,该指针占4个存储单元,然后再在堆中创建对象的实体!并且使栈上的指针关联到堆中对象实体的首址处!

说明:可以使用SizeOf区别简单数据类型与复杂数据类型。

 

指针运算

指针位移的字节数是以指针的类型决定的。

比如:

procedure TForm1.Button2Click(Sender: TObject);

var

    a:array[0..4] of Integer;

    p1: ^integer;

    b:array[0..1] of char;

    p2: ^char;

begin

    p1 := @a[0];

    Inc(p1); //--初始指针向后偏移4个字节,指向a[1]

    Inc(p1,2); //--指针向后偏移2*4个字节,指向a[3]

    //---

    p2 := @b[0];

    Inc(p2); //--初始指针向后偏移1个字节,指向b[1]

    Inc(p1,2); //--指针向后偏移2*1个字节,指向b[3]

end;

当执行Inc(p1)时,编译器会产生让p1前进sizeof(integer)步长的汇编代码,之后p1将指向a[1]Inc(p1,2)这句使得p1前进2sizeof(integer)大小的步长,之后p1将指向a[3]

 

操作符@

操作符@用来返回变量的内存中的存储地址, 或者是返回过程、函数和方法的入口地址。

说明:

1)、对于变量X@X返回的是X的地址。如果编译选项{$T-}没有打开,则返回的是一个通用的指针,如果编译选项打开,则返回的是X的类型对应的指针。

2)、对于例程F (过程/函数)@F返回的是F的入口点,@F的类型是一个指针。

3)、当@用在类的方法中时,则方法的名称必须有类名,例如@TMyclass.Dosomething指针指向TMyclassdosomething方法。  

4)、对于过程变量P @PP转换成一个包含地址的无类型的指针变量(即@P等价于无类型指针P),此时可以把一个无类型的指针值赋给过程变量P。获得一个过程变量的内存地址使用@@。例如,@@P返回P的地址。 

 

操作符^

操作符^有两个目标:

1)、当它出现在类型定义的前面时如 ^typename 表示指向这种类型的指针;  

2)、当它出现在指针变量后边时,如 point^ 返回指针指向的变量的值;  

 

记录指针

Record记录变量和记录指针的用法有类似的地方,不过记录变量不是由指针来实现的,所以大家在某些时候要小心记录指针的使用。

 

过程/函数/方法指针

说明:

1)、当一个过程变量在赋值语句的左边时,编译器期望一个过程值在赋值语句的右边。这种赋值使得左边的变量可以指向右边定义的过程或者函数入口点。换句话说,可以通过该变量来引用声明的过程或者函数,可以直接使用参数的引用。  

2)、在赋值语句“过程变量 := 过程值”中,左边变量的类型决定了右边的过程或者方法指针解释。

3)、无论何时一个过程变量(procedural variable)出现在一个表达式中,它表示调用所指向的函数或者过程。

4)、任何过程变量可以赋成nil,表示指证什么也不指向。但是试图调用一个nil值的过程变量导致一个错误,为了测试一个过程变量是否可以赋值,用标准的赋值函数Assigned。如下所示:

   if    Assigned(OnClick)    then    OnClick(X);  

 

结构:

1)、过程/函数指针是一个32位的指针

2)、方法指针指向对象方法的指针,实现上是一个对象指针加上一个过程/函数指针组成。方法指针结构如下

 TMethod = record

  Code: Pointer;//指向过程/函数的指针

     Data: Pointer;//指向对象的指针

 end;

 

类指针

DELPHI中的类是一个指针,这个指针指向类在内存中所占据的一块空间。类指针与VMT指针地址相同。

 

类的类型

DELPHI中我们用TObjectTComponent等等标识符表示类,它们在DELPHI的内部实现为各自的VMT数据。而用class of保留字定义的类的类型,实际就是指向相关类的指针。例如“ TClass = class of TObject”,从概念上说,TClassTObject类的类型,实际上TClass就是指向TObject的指针类型!

有了类的类型,我们就可以将类赋值给使用“类的类型”声明的变量(即类变量),从而将类作为变量来使用。

 

对象指针

DELPHI中的对象是一个指针,这个指针指向该对象在内存中所占据的一块空间。我们可以试着用sizeof函数获取对象的大小,结果是4字节,这正是一个32位指针的大小。而对象的真正大小应该用MyObject.InstanceSize获得。

注意:

对象虽然是用指针实现,具有指针所有特性,但毕竟定义为Class,所以只能用“对象.成员、对象.方法”的方式来调用,而不能用“对象^.成员、对象^.方法”的方式来调用。

例如我们最熟悉的Form,当我们调用Form1.Edit1.Text时,其实Form1Edit1都是指针,但是只能用Form1.Edit1.Text来调用,而不能用Form1^.Edit^.Text