指针是存放指定类型(或未定义类型)变量内存地址的变量,因此指针间接引用一个值。
指针可以分为两大类:无类型指针(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