C与指针 七 探讨指向指针的指针

这里有一个通用的例子。
int i;
int *pi;
int * *ppi;

这些声明在内存中创建了下列变量。如果它们是自动变量,我们无法猜测它们的初始值。


有了上面这些信息之后,请问下面各条语句的效果是什么呢?
(1) printf( "%d\n", ppi );

(2) printf ( ”%d\n,,,&ppi ) 

(3)*ppi = 5;

        (1)如果 ppi 是个自动变量,它就未被初始化,这条语句将打印一个随机值。如果它是个静态变量,这条语句将打印0。
(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'是一个数组,它的元素类型是指向整型的指针。

下面这个例子隐藏着一个圈套。不管怎样,让我们先推断出它的含义。

int f()[];
        f是一个函数,它的返回值是一个整型数组。这里的圈套在于这个声明是非法的——函数只能 返回标量值,不能返回数组。
这里还有一个例子,颇费思量。
int f []();
        现在,f似乎是一个数组,它的元素类型是返回值为整型的函数。这个声明也是非法的,因为 数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度。
但是,下面这个声明是合法的:
int (* f[])();
        首先,你必须找到所有的操作符,然后按照正确的次序执行它们。同样,这里有两对括号,它 们分别具有不同的含义。括号内的表达式*圯首先进行求值,所以f是一个元素为某种类型的指针的 数组。表达式末尾的()是函数调用操作符,所以〖肯定是一个数组,数组元素的类型是函数指针, 它所指向的函数的返回值是一个整型值。
如果你搞清楚了上面最后一个声明,下面这个应该是比较容易的了:
int *(*f[])();

        它和上面那个声明的唯一区别就是多了一个间接访问操作符,所以这个声明创建了一个指针数组,指针所指向的类型是返回值为整型指针的函数。

       到现在为止,我使用的是旧式风格的声明,目的是为了让例子简单一些。但ANSIC要求我们使用完整的函数原型,使声明更为明确。例如:

int (*f) ( int,float );
int *(*g[])( int ,float );
       前者把f声明为一个函数指针,它所指的函数接受两个参数,分别是一个整型值和浮点型值, 并返回一个整型值。后者把f声明为一个数组,数组的元素类型是一个函数指针,它所指向的函数 接受两个参数,分别是一个整型值和浮点型值,并返回一个整型指针。尽管原型增加了声明的复杂 度,但我们还是应该大力提倡这种风格,因为它向编译器提供了一些额外的信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值