4.7 指向指针(类型)的指针
我们知道,指针类型是以它所指向的类型为特征的,可以指向任何类型。那位说了,既然指针也是一种数据类型,且指针可以指向任何类型,那有没有指向指针类型的指针呢?可以明确地告诉你,有的。在下面的程序中就有一个变量ppi,它的类型就是指向指针的指针。
/***************c0412.c**************/ int main(void) { int i, * pi = & i, * * ppi = & pi; * * ppi = 10086; //S1 * * ppi = * * ppi + 1; //S2 int j; pi = & j; //S3 * * ppi = 10010; //S4 int * pj = & j; ppi = & pj; //S5 ++ * * ppi; //S6 }
在这个程序里,我们声明了变量i、pi和ppi,其类型分别是int、指向int的指针和指向“指向int的指针”的指针。对变量ppi的声明使用了两个星号,这个声明的解读方法如图4-9所示。首先从标识符ppi开始,然后向左读:ppi的类型是指针,指向的类型是“指向int的指针”,或者说,ppi是指向“指向int的指针”的指针。如果使用类型名,则变量ppi的类型是int * *。
图4-9 解读“指向指针的指针”的声明
在声明中,变量pi的类型是int *,而表达式& i的类型也是int *,类型一致,可以用后者的值初始化前者。
进一步地,因pi是一个int *类型的左值,故表达式& pi的类型是int * *,这与变量ppi的类型一致,可用前者的值初始化后者。
如图4-10所示,现在,变量ppi的值指向变量pi,而变量pi的值又指向变量i。通过变量pi可以访问变量i,这是我们已经熟悉的。但事实上,通过变量ppi也可以访问到变量i,语句S1就做到了这一点。
图4-10 程序中的变量及指针的指向关系
在语句S1中,表达式* * ppi等价于*(* ppi),这是因为一元*运算符是从右往左结合的。左值ppi经左值转换后得到一个int * *类型的值(这个值实际上指向变量pi),一元*运算符作用于它,得到一个int *类型的左值(代表变量pi)。这个左值继续执行左值转换,得到一个int *类型的值(这个值实际上指向变量i),最左边的一元*运算符作用于它,得到一个int类型的左值(代表变量i)。最终得到的这个左值是赋值运算符的左操作数,不执行左值转换,且被赋值为10086。赋值后,变量i的值是10086。
同理,在语句S2中,表达式* * ppi是左值,代表变量i。但赋值运算符左边的* *ppi不执行左值转换,而用于接受赋值;赋值运算符右边的* * ppi执行左值转换,转换后的结果与整数1相加。这条语句执行后,变量i的值是10087。
指针可以灵活地指向不同的变量,但这也意味着通过该指针所访问的变量也不一样。在语句S3中,变量pi的值被改为指向另一个不同的变量j。于是在语句S4中,尽管子表达式* ppi的结果是左值,依然代表变量pi,但表达式* * ppi的结果却是代表变量j的左值。所以这条语句是改变了变量j的存储值,赋值后,变量j的值为10010。
语句S5用于修改变量ppi的值,令其指向另一个指针类型的变量pj。而在此之前,变量pj被声明为指向int的指针,且被初始化为指向变量j。
在语句S6中,子表达式* ppi的结果是一个左值,代表变量pj,而变量pj的值现在是指向变量j的。所以表达式* * ppi的结果是代表变量j的左值。前缀递增运算符作用于这个左值,将递增它所代表的那个变量(变量j)的存储值。
练习4.13
指针讲到这里,你应该对指针变量的声明技巧有所领悟,知道圆括号具有结合和分隔的作用。如果可能的话,请尝试声明一个变量ppf,其类型为指向“指向参数类型为void,返回类型为int的函数的指针”的指针。