如果在Delphi IDE中输入Addr然后按F1的话,帮助文件会告诉你:Addr是个返回值类型为Pointer的函数。不过我个人更倾向叫它运算符,因为在默认编译条件下,它的行为和@运算符别无二致。函数和运算符有哪些区别和特点,我是如何对它们进行定义的,会在其它文章中讲到。

    Delphi中的Pointer类型与C中的void*类型是一模一样的,它可以与任何类型的指针、对象、类变量进行相互转换,并且编译器不会给出任何警告(印象中C++中会要求从void*向其它类型转换时进行显式转换,否则会给出警告)。指针类型的空值是nil,与Pointer(0)或Ptr(0)是完全相同的。在Delphi中,甚至你可以直接把0赋值给任何一个指针,而编译器仅仅是给你个警告,告诉你已经把0变成nil了(在Free Pascal中则会出错)。直接对Pointer类型的变量进行解引用,得到的“值”的类型是“不定类型(Untyped Type? 这个似乎有点儿黑色幽默,我回头找找看有没有确定的翻译 -_-)”,它不能直接赋值给任何变量(除作为参数传递给要求不定类型的函数,相关知识会在讲参数时详细介绍)。关于Pointer类型就暂时先介绍这么多了,更加详细的会放在介绍指针的文章中。

    那么,我现在再强调一下前面介绍的两个特点:

  1. 默认编译条件下的@运算符,在对任何变量、运行期常量、函数取地址后,与Addr运算符的结果一样,返回值的类型为Pointer;
  2. Pointer类型的变量,可以与任何指针、对象、类类型变量自由转换,并且编译器不会给出任何警告。

    现在,根据这两个条件,很容易得到如下推论:当使用Addr运算符、或是默认编译条件下的@运算符时,我们可以把一个变量的地址赋值给另外一种类型的指针变量。举例来说,就是:

var { Global vars }
	I	: Integer;
	PS	: PSingle	= @I;
	PC	: PChar	= Addr(I);

    可以编译一下上面的代码,默认编译参数下是完全没问题的。

    对于一个初学者来说,可能指针类型对还是比较神秘的。这种“错误”的类型转换可能会给您带来很大的麻烦,出现意想不到的错误,并且很难调试。但如果您和我一样,眼里全是地址与数据没有类型,类型只是为了在运算的时候规定编译器的行为,那么Addr与默认编译条件下的@运算符的这个特点,却是Delphi非常方便的一个特性。

    如果细心的话,可能您已经注意到,在提到@运算符时,我都会在前面加上一个限定词——默认编译条件,而在提到Addr时则没有任何修饰。这就引出了本文的另外一个重要知识点:没错儿,可以改变编译条件,从而使@运算符与Addr运算符表现出不同的行为。
在IDE中,可以通过Project Options -> Compiler,勾上“Typed @ operator”,或是通过预编译指令$T+或$TYPEDADDRESS ON,来改变@运算符的行为。这样设置之后,上面代码中使用@I行就会出现一个编译错误,提示Single与Integer是不同的类型。
而Addr运算符的行为则不会改变,这意味着编译器认为,当你使用Addr时知道自己在做什么。

    对于初学者来说,如果对指针类型的使用没把握的话,建议打开这个选项,可以在一定程度减少出错的可能性。