这里有一个通用的例子。
int i;
int *pi;
int * *ppi;
这些声明在内存中创建了下列变量。如果它们是自动变量,我们无法猜测它们的初始值。
有了上面这些信息之后,请问下面各条语句的效果是什么呢?(1)如果 ppi 是个自动变量,它就未被初始化,这条语句将打印一个随机值。如果它是个静态变量,这条语句将打印0。
(1) printf( "%d\n", ppi );
(2) printf ( ”%d\n,,,&ppi )
(3)*ppi = 5;
(2)这条语句将把存储?如的地址作为十进制整数打印出来。这个值并不是很有用。
(3)这条语句的结果是不可预测的。对?扣不应该执行间接访问操作,因为它尚未被初始化。
接下来的两条语句用处比较大。
ppi =π
这条语句把ppi初始化为指向变量pi。以后我们就可以安全地对ppi执行间接访问操作了。
*ppi = &i;
这条语句把pi(通过ppi间接访问)初始化为指向变量i,经过上面最后两条语句之后,这些变量变成了下面这个样子:
现在,下面各条语句具有相同的效果:
i= 'a' ;
*pi='a';
**ppi='a';
在一条简单的对i赋值的语句就可以完成任务的情况下,为什么还要使用更为复杂的涉及间接访问的方法呢?这是因为简单赋值并不总是可行,例如链表的插入。在那些函数中,我们无法:(吏用简单赋值,因为变量名在函数的作用域内部是未知的。函数所拥有的只是一个指向需要修改的内存位置的指针,所以要对该指针进行间接访问操作以访问需要修改的变量。
高级声明
在使用更高级的指针类型之前,我们必须观察它们是如何声明的。前面介绍了表达式声明的思路以及C语言的变量如何通过推论进行声明。让我们通过观察一系列越来越复杂的声明进一步探索这个话题。
首先让我们来看几个简单的例子。
int f;/*一个整型变量*/
int *f; /*一个指向整型的指针*/
不过,请回忆一下第2个声明是如何工作的:它把表达式*f声明为一个整数。根据这个事实,你肯定能推断出f是个指向整型的指针。C声明的这种解释方法可以通过下面的声明得到验证。
int* f , g;
它并没有声明两个指针。尽管它们之间存在空白,但星号是作用于f的,只有f才是一个指针。g只是一个普通的整型变量。
下面是另外一个例子,你以前曾见过:
int f();
它把f声明为一个函数,它的返回值是一个整数。旧式风格的声明对函数的参数并未提供任何信息。它只声明f的返回值类型。现在我将使用这种旧式风格,这样例子看上去简单一些,后面我再回到完整的原型形式。
下面是一个新例子:
int *f();
要想推断出它的含义,你必须确定表达式*f()是如何进行求值的。首先执行的是函数调用操作符(),因为它的优先级高于间接访问操作符。因此,f是一个函数,它的返回值类型是一个指向整型的指针。
如果“推论声明”看上去令你觉得有点讨厌,你只要这样考虑就可以了:用于声明变量的表达式和普通的表达式在求值时所使用的规则相同。你不需要为这类声明学习一套单独的语法。如果你能够对一个复杂表达式求值,你同样可以推断出一个复杂声明的含义,因为它们的原理是相同的。
接下来的一个声明更为有趣:
int (* f)();
确定括号的含义是分析这个声明的一个重要步骤。这个声明有两对括号,每对的含义各不相同。第2对括号是函数调用操作符,但第1对括号只起到聚组的作用。它迫使间接访问在函数调用之前进行,使f成为一个函数指针,它所指向的函数返回一个整型值。
函数指针?是的,程序中的每个函数都位于内存中的某个位置,所以存在指向那个位置的指针是完全可能的。函数指针的初始化和使用将在本章后面详述。
现在,下面这个声明应该是比较容易弄懂了:
int *(*f)();
它和前一个声明基本相同,f也是一个函数指针,只是所指向的函数的返回值是一个整型指针,
必须对其进行间接访问操作才能得到一个整型值。
现在,让我们把数组也考虑进去。
int f[];
这个声明表示f是个整型数组。数组的长度暂时省略,因为我们现在关心的是它的类型,而不是它的长度。
下面这个声明又如何呢?
int *f[];
这个声明又出现了两个操作符。下标的优先级更高,所以1'是一个数组,它的元素类型是指向整型的指针。
f是一个函数,它的返回值是一个整型数组。这里的圈套在于这个声明是非法的——函数只能 返回标量值,不能返回数组。下面这个例子隐藏着一个圈套。不管怎样,让我们先推断出它的含义。
int f()[];
这里还有一个例子,颇费思量。现在,f似乎是一个数组,它的元素类型是返回值为整型的函数。这个声明也是非法的,因为 数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度。
int f []();
但是,下面这个声明是合法的:首先,你必须找到所有的操作符,然后按照正确的次序执行它们。同样,这里有两对括号,它 们分别具有不同的含义。括号内的表达式*圯首先进行求值,所以f是一个元素为某种类型的指针的 数组。表达式末尾的()是函数调用操作符,所以〖肯定是一个数组,数组元素的类型是函数指针, 它所指向的函数的返回值是一个整型值。
int (* f[])();
如果你搞清楚了上面最后一个声明,下面这个应该是比较容易的了:
int *(*f[])();
它和上面那个声明的唯一区别就是多了一个间接访问操作符,所以这个声明创建了一个指针数组,指针所指向的类型是返回值为整型指针的函数。
到现在为止,我使用的是旧式风格的声明,目的是为了让例子简单一些。但ANSIC要求我们使用完整的函数原型,使声明更为明确。例如:
int (*f) ( int,float );前者把f声明为一个函数指针,它所指的函数接受两个参数,分别是一个整型值和浮点型值, 并返回一个整型值。后者把f声明为一个数组,数组的元素类型是一个函数指针,它所指向的函数 接受两个参数,分别是一个整型值和浮点型值,并返回一个整型指针。尽管原型增加了声明的复杂 度,但我们还是应该大力提倡这种风格,因为它向编译器提供了一些额外的信息。
int *(*g[])( int ,float );