对复杂声明的分析


K&R C中对声明做如下定义:声明(declaration)用于说明每个标识符的含义,而并不需要为每个标识符预留存储空间。(预留存储空间的声明称为定义(definition))。声明由声明说明符和初始化声明符表构成。

char * const *(*foo)();

对这样的声明,要先看标识符,即foo,foo紧挨着一个*并且与它在一对圆括号内,说明foo是一个指针。再看圆括号外,左侧是一个*,右侧还是一对圆括号,由于圆括号的优先级高于*,因此*foo是一个函数,而foo就是指向这样函数的一个指针。那么可以看出(*foo)()的左侧是这个函数的返回值类型。现假设有这样声明的一个标识符p:

char * const *p;

可以看到,p是一个指向 指向一个char类型的数据的指针 的指针,而const关键字的位置说明*p是常量,因此上面的那句话就改为“p是一个指向 指向一个char类型的数据的常量指针 的指针”。那么这个函数返回值的类型就明确了:该函数返回一个指针,这个指针指向一个类型为char的常量指针。

到这里,对这个复杂声明的分析就结束了:该声明声明了一个指向函数的指针,该函数没有参数,返回值是一个指针,该指针指向一个类型为char的常量指针。

char *(* c[10])(int **p);

标识符c在圆括号内,先看c的左右,左边是*,右边是[],[]的优先级高于*,因此c表示一个数组,加上前面的*,说明c表示一个指针数组。圆括号外的左边是*,右边是另一个圆括号,说名c表示一个元素指向函数的指针数组,而且该函数的参数是p,返回值是char类型的指针。

综上所述,该声明声明了一个有10个元素,且元素是函数指针的指针数组,函数的返回值是char类型的指针,参数是指向int类型指针的指针。

int * const **p;

现在的问题是:谁是read-only的,是**p还是*p?

可以这样看待:(从右往左翻译》

 int * const **

指向……的指针

 int * const *

指向……的指针

 int * const

只读的

 int *

指向……的指针

 int

int类型

从上至下翻译下来就是

p 指向 *p 指向 **p 只读的 指向 ***p int类型 的指针 的指针 的指针

因此不难看出**p才是只读的。

void (*signal(int sig, void(*func)(int)))(int);

这是signal()函数原型的声明,要想理解这个声明要先理解函数指针与指针函数:函数指针是一个指针,声明时格式如下

int (*foo)(int);

这指的是一个指向这样函数的指针;如果去掉*foo外的括号,由于运算符的优先级,这句声明就变成了声明一个返回值为指针类型的函数。

现在再回头看刚刚的声明,注意到只需将foo替换为signal(int sig, void(*func)(int))即可看到函数指针的形式。再结合foo的位置,不难看出这个函数signal的返回值是函数指针。

这时我们就可以很容易地分析出signal函数原型究竟说了什么:singal函数的参数有两个,一个是int类型,一个是函数指针;它的返回值也是函数指针。

五 题外话

显然上面signal函数的原型中有两个函数指针是重复的,因此在《C专家编程》一书中,作者使用了以下的语句来简化函数

typedef void(*ptr_to_func) (int); //简化后的函数: ptr_to_func signal(int, ptr_to_func);

可以看到,这个用于简化的语句也是晦涩难懂,在我们的认知中,typedef应该是这样用的

typedef int my_int; //就像#define语句一样 #define MY_INT int

然而实际上在K&R C中,对typedef的作用是这样定义的:typedef声明按照普通的声明方式将一个类型指派给其声明符中的每个名字,此后,类型定义名在语法上就等价于相关类型的类型说明符关键字。

在上面简化signal函数的例子中,声明符声明的标识符是ptr_to_func,这个标识符就是所谓的类型定义名。这个被指派的类型就是这个重复使用的函数指针类型。因此我们也可以这么说,因为ptr_to_func在这个位置上,所以它有了这样的意义。而使#define是做不到这一点的。


← Prev 补码转换为无符号数与截断补码数值 | 关于C语言结构体 Next →