标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针变量为NULL,可以给它赋一个零值。为了测试一个指针变量是否为NULL,可以将它与零值进行比较。之所以选择零这个值,是因为一种源代码约定。就机器内部而言,NULL指针的实际值可能与此不同。在这种情况下,编译器将负责零值和内部值之间的翻译转换。
照这么说的话,如果我定义一个int* a,这个a指针是不是指向NULL呢?
如果所有的指针变量(而不仅仅是位于静态内存中的指针变量)能够被自动初始化为NULL,那实在是件幸事,但事实并非如此。不论你的机器对解引用NULL指针这种行为作何反应,对所有的指针变量进行显式的初始化是种好做法。如果已经知道指针将被初始化为什么地址,就把它初始化为该地址,否则就把它初始化为NULL。风格良好的程序会在指针解引用之前对它进行检查,这种初始化策略可以节省大量的调试时间。
好吧。。。
NULL指针的概念是非常有用的,因为它给了你一种方法,可以表示某个特定的指针目前并未指向任何东西。例如,一个用于在某个数组中查找某个特定值的函数可能返回一个指向查找到的数组元素的指针。如果该数组不包含指定条件的值,函数就返回一个NULL指针。这个技巧允许返回值传达两个不同片段的信息。首先,有没有找到元素?其次,如果找到,它是哪个元素?
原来如此。
int a;
int *d = &a
指针变量可以作为左值,并不是因为它们是指针,而是因为它们是变量。对指针变量进行间接访问表示我们应该访问指针所指向的位置。间接访问指定了一个特定的内存位置,这样我们可以把间接访问表达式的结果作为左值使用。在下面这两条语句中:
*d = 10 - *d;
d = 10 - *d;
第1条语句包含了两个间接访问操作。右边的间接访问作为右值使用,所以它的值是d所指向的位置所存储的值(a的值)。左边的间接访问作为左值使用,所以d所指向的位置(a)把赋值符右侧的表达式的计算结果作为它的新值。
第2条语句是非法的,因为它表示把一个整型数量(10?*d)存储于一个指针变量中。当实际使用的变量类型和应该使用的变量类型不一致时,编译器会发出警告,帮助我们判断这种情况。这些警告和错误信息是我们的朋友,编译器通过产生这些信息向我们提供帮助。尽管被迫处理这些信息是我们很不情愿干的事情,但改正这些错误(尤其是那些不会中止编译过程的警告信息)确实是个好主意。在修正程序方面,让编译器告诉你哪里错了比你以后自己调试程序要方便得多。调试器无法像编译器那样准确地查明这些问题。
唉,要是我们上课的时候也是这么讲就好了(不对,转念一想,我上课也是做自己的事情),应该说,要是早点读到这本书就好了。
*&a = 25;
这条语句和简单地使用a=25;有什么区别吗?从功能上说,它们是相同的。但是,它涉及更多的操作。除非编译器(或优化器)知道你在干什么并丢弃额外的操作,否则它所产生的目标代码将会更大、更慢。更糟的是,这些额外的操作符会使源代码的可读性变差。基于这些原因,没人会故意使用像*&a这样的表达式。
*100 = 25;
它看上去像是把25赋值给a,因为a是位置100所存储的变量。但是,这是错的!这条语句实际上是非法的,因为字面值100的类型是整型,而间接访问操作只能作用于指针类型表达式。如果你确实想把25存储于位置100,就必须使用强制类型转换。
*(int *)100 = 25;
强制类型转换把值100从“整型”转换为“指向整型的指针”,这样对它进行间接访问就是合法的。如果a存储于位置100,那么这条语句就把值25存储于a。但是,你需要使用这种技巧的机会是绝无仅有的!为什么?前面提到,通常无法预测编译器会把某个特定的变量放在内存中的什么位置,所以无法预先知道它的地址。用&操作符得到变量的地址是很容易的,但表达式在程序执行时才会进行求值,此时已经来不及把它的结果作为字面值常量复制到源代码。
这个技巧唯一有用之处是你偶尔需要通过地址访问内存中某个特定的位置时,它并不是用于访问某个变量,而是访问硬件本身。例如,操作系统需要与输入输出设备控制器通信,它将启动I/O操作并从前面的操作中获得结果。在有些机器上,与设备控制器的通信是通过在某个特定内存地址读取和写入值来实现的。但是,与其说这些操作访问的是内存,还不如说它们访问的是设备控制器接口。这样,这些位置必须通过它们的地址来访问,此时这些地址是预先已知的。
也就是说“100”是一个指针变量,*100原本不知道存储的是什么,但现在知道存储的是25了。
int a = 12;
int *b = &a;
c = &b;
问题是c的类型是什么?显然它是一个指针,但它所指向的是什么?变量b是一个“指向整型的指针”,所以任何指向b的类型必须是指向“指向整型的指针”的指针,更通俗地说,是一个指针的指针。
它合法吗?是的!指针变量和其他变量一样,占据内存中某个特定的位置,所以用&操作符取得它的地址是合法的。
那么这个变量是怎样声明的呢?声明
int **c = &b;
其中唯一的新面孔是最后一个表达式,让我们对它进行分析。*操作符具有从右向左的结合性,所以这个表达式相当于*(*c),我们必须从里向外逐层求值。*c访问c所指向的位置,我们知道这是变量b。第2个间接访问操作符访问这个位置所指向的地址,也就是变量a。指针的指针并不难懂,你只要留心所有箭头,如果表达式中出现了间接访问操作符,就随箭头访问它所指向的位置。