To see how pointers work, look at
the following example.
1 var
2 X, Y:
Integer; // X and Y are
Integer variables
3 P:
^Integer; // P
points to an Integer
4 begin
5 X := 17;
// assign a value to X
6 P := @X;
// assign the address of X to P
7 Y := P^;
// dereference P; assign the result to Y
8 end;
Line 2 declares X and Y as
variables of type Integer. Line 3 declares P as a pointer to an Integer
value; this means that P can point to the location of X or Y. Line 5 assigns a
value to X, and line 6 assigns the address of X (denoted by @X) to P. Finally,
line 7 retrieves the value at the location pointed to by P (denoted by ^P) and
assigns it to Y. After this code executes, X and Y have the same value, namely
17.
The @ operator, which we
have used here to take the address of a variable, also operates on functions
and procedures. For more information, see The @ operator and
Procedural
types in statements and expressions.
The symbol ^ has two
purposes, both of which are illustrated in our example. When it appears before
a type identifier
^typeName
it denotes a type that represents pointers to variables of type typeName. When it appears after a pointer variable
pointer^
it dereferences the pointer; that
is, it returns the value stored at the memory address held by the pointer.
Our example may seem like a
roundabout way of copying the value of one variable to another something that
we could have accomplished with a simple assignment statement. But pointers are
useful for several reasons. First, understanding pointers will help you to
understand Object Pascal, since pointers often operate behind the scenes in
code where they don’t appear explicitly. Any data type that requires large,
dynamically allocated blocks of memory uses pointers. Long-string variables,
for instance, are implicitly pointers, as are class variables. Moreover, some
advanced programming techniques require the use of pointers.
Finally, pointers are sometimes
the only way to circumvent Object Pascal’s strict data typing. By referencing a
variable with an all-purpose Pointer, casting the Pointer to a
more specific type, and then dereferencing it, you can treat the data stored by
any variable as if it belonged to any type. For example, the following code
assigns data stored in a real variable to an integer variable.
type
PInteger = ^Integer;
var
R: Single;
I: Integer;
P: Pointer;
PI: PInteger;
begin
...
P := @R;
PI := PInteger(P);
I := PI^;
end;
Of course, reals and integers are
stored in different formats. This assignment simply copies raw binary data from
R to I, without converting it.
In addition to assigning the
result of an @ operation, you can use several standard routines to give
a value to a pointer. The New and GetMem procedures assign a
memory address to an existing pointer, while the Addr and Ptr functions return
a pointer to a specified address or variable.
Dereferenced pointers can be
qualified and can function as qualifiers, as in the expression P1^.Data^.
The reserved word nil is a special constant that can be assigned to any pointer. When nil is assigned to a pointer, the pointer doesn’t reference anything.
首先看下面的范例中指针是如何工作的:
1 var
2 X, Y:
Integer; // X和Y是整数类型的变量
3 P:
^Integer; // P是指向一个整数的指针
4 begin
5 X := 17;
// 向X赋值
6 P := @X;
// 把X的地址赋给P
7 Y := P^;
// 对指针P解除参照,将结果赋给Y
8 end;
第2行声明了Integer类型的变量X和Y。第3行声明了指向Integer值的指针P,意味着指针P可以指向变量X或Y的位置。第5行向X赋值,第6行把X的地址(表示为@X)赋给指针P。最后,第7行重新找回指针P所指的位置的值(表示为P^,原文显然有误:^P)并赋给Y。这些代码执行后,X和Y具有相同的值,即17。
在这里用于获得变量地址的地址运算符(@),也可以作用于函数和过程。更多信息见地址(@)运算符和语句和表达式中的程序型类型。
符号 ^ 有两种用途,在本例中都说明了。当它出现在类型标识符之前,如
^typeName
表示一个类型,该类型表示指向typeName类型变量的指针。当它出现在指针变量之后,如
pointer^
该符号对指针解除参照,也就是说,返回存储在内存地址(该地址保存在指针中)的值。
上面的例子看起来好象为了复制一个整数值绕了不少弯路,因为完全可以直接用简单的赋值语句达到相同目的。(这里为了说明指针的基本用法,的确是这样。)但是,指针的确有用。首先,理解指针有助于理解Object Pascal,因为指针常常在其没有直接出现的地方在后台代码中操作。所有需要大容量或动态分配的内存快都使用了指针。例如,长串变量就是隐式指针,类变量也是。此外,一些高级编程技术也需要使用指针。
最后,指针有时是超越Object
Pascal精确数据分类的唯一途径。用可以指向所有数据类型的一般指针Pointer引用一个变量,将其转换成为一个更特殊的类型,然后对其接触参照,这是可以把存储在任意变量中的数据视为属于任意类型。例如,下面的代码将保存在实数变量中的数据赋给了整数变量(不是数字值的复制),
type
PInteger = ^Integer;
var
R: Single;
I: Integer;
P: Pointer;
PI: PInteger;
begin
...
P := @R;
PI := PInteger(P);
I := PI^;
end;
当然,实数和整数的存储格式不同。这里的赋值只是简单地将R中的二进制数据复制到I中,而没有做任何变换。
除了将地址运算符(@)的操作结果赋给指针以外,还可以使用几个标准例程把变量传递给指针。New和GetMem过程可以将内存地址赋给存在的指针;另外,Addr和Ptr函数向指定的地址或变量返回一个指针。
解除参照的指针可以被限定词限定,也可以作为限定词,如表达式P1^.Data^。
保留字nil是一个特殊的常量,可以被赋值到任何指针。当nil被赋给一个指针时,指针不能引用任何实体。
编者注
标准函数Addr和Ptr的一般用法可以参考如下代码:
{Addr的用法示例:}
var
X: Integer;
P: ^Integer;
begin
X := 123;
P := Addr(X);//等价于 P :=
@X;
P^ := 234;//执行后X的值为234
...
end;
{Ptr的用法示例:}
var
X: Integer;
P, Q: ^Integer;
A: Integer;
begin
X := 123;
P := Addr(X);
A := Integer(P);//将指针转换为用整数类型表示的地址
Q := Ptr(A);
Q^ := 234;//执行后X的值为234
...
end;
可以看出,Addr函数等价于地址运算符(@),该函数接受任何有效标识符,包括变量、常量、函数、过程等,返回指向给定实体地址的指针。而Ptr函数只接受值为整数类型的地址表达式(例子中直接传递了整数A),返回指定地址的指针。在上面的例子中,使用了类型转换,即 A := Integer(P); 语句中,将指针转换为用整数类型表示的地址。