肯尼斯·里科《C和指针》第13章 高级指针话题(1)进一步探讨指向指针的指针&变量的高级声明

13.1 进一步探讨指向指针的指针

上一章使用了指向指针的指针,用于简化向单链表插入新值的函数。另外还存在许多领域,指向指针的指针可以在其中发挥重要的作用。这里有一个通用的例子:

肯尼斯·里科《C和指针》第13章 高级指针话题(1)进一步探讨指向指针的指针&变量的高级声明_第1张图片

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

有了上面这些信息之后,请问下面各条语句的效果是什么呢?

肯尼斯·里科《C和指针》第13章 高级指针话题(1)进一步探讨指向指针的指针&变量的高级声明_第2张图片

①如果ppi是个自动变量,它就未被初始化,这条语句将打印一个随机值。如果它是个静态变量,这条语句将打印0。

②这条语句将把存储ppi的地址作为十进制整数打印出来。这个值并不是很有用。

③这条语句的结果是不可预测的。对ppi不应该执行间接访问操作,因为它尚未被初始化。

接下来的两条语句用处比较大。

ppi = π

这条语句把ppi初始化为指向变量pi。以后就可以安全地对ppi执行间接访问操作了。

*ppi = &i;

这条语句把pi(通过ppi间接访问)初始化为指向变量i。执行完上面最后两条语句之后,这些变量变成了下面这个样子:

肯尼斯·里科《C和指针》第13章 高级指针话题(1)进一步探讨指向指针的指针&变量的高级声明_第3张图片

现在,下面各条语句具有相同的效果:

i='a';
*pi='a';
**ppi='a';

在一条简单的对i赋值的语句就可以完成任务的情况下,为什么还要使用更为复杂的涉及间接访问的方法呢?这是因为简单赋值并不总是可行,例如链表的插入。在那些函数中,无法使用简单赋值,因为变量名在函数的作用域内部是未知的。函数所拥有的只是一个指向需要修改的内存位置的指针,所以要对该指针进行间接访问操作以访问需要修改的变量。

在前一个例子中,变量i是一个整数,pi是一个指向整型的指针。但ppi是一个指向pi的指针,所以它是一个指向整型的指针的指针。假定我们需要另一个变量,它需要指向ppi。那么,它的类型当然是“指向整型的指针的指针的指针”,而且它应该像下面这样声明:

int  ***pppi;

间接访问的层次越多,需要用到它的次数就越少。但是,一旦真正理解了间接访问,无论出现多少层间接访问,我们应该都能十分轻松地应付。

只有当确实需要时,才应该使用多层间接访问。不然的话,程序将会变得更庞大、更缓慢并且更难于维护。

批注:上面的描述看懂并不困难,但是需要结合具体例子来理解吧。

13.2 高级声明

在使用更高级的指针类型之前,我们必须观察它们是如何声明的。前面的章节介绍了表达式声明的思路以及C语言的变量如何通过推论进行声明。我们在第8章声明指向数组的指针时已经看到过一些推论声明的例子。现在通过观察一系列越来越复杂的声明进一步探索这个话题。

首先来看几个简单的例子:

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[];

这个声明又出现了两个操作符。下标的优先级更高,所以f是一个数组,它的元素类型是指向整型的指针

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

int        f()[];

f是一个函数,它的返回值是一个整型数组。这里的圈套在于这个声明是非法的——函数只能返回标量值,不能返回数组

这里还有一个例子,颇费思量。

int        f[]();

现在,f似乎是一个数组,它的元素类型是返回值为整型的函数。这个声明也是非法的,因为数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度。

但是,下面这个声明是合法的:

int        (*f[])();

首先,必须找到所有的操作符,然后按照正确的次序执行它们。同样,这里有两对括号,它们分别具有不同的含义。括号内的表达式*f[ ]首先进行求值,所以f是一个元素为某种类型的指针的数组。表达式末尾的( )是函数调用操作符,所以f肯定是一个数组,数组元素的类型是函数指针,它所指向的函数的返回值是一个整型值。

如果大家搞清楚了上面最后一个声明,下面这个应该是比较容易的了:

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

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

int        (*f)( int, float );
int        *(*g[])( int, float );

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

你可能感兴趣的:(C语言,c语言,开发语言,学习,其他,笔记,数据结构)