目录

上一篇文章是关于运算符的优先级的,这篇文章则来说说关于C语言中的复杂声明。C语言中的声明是不能从左往右读的,时而往左时而往右,有时候弄得人头大,什么数组指针、指针数组傻傻弄不清楚,函数指针更是模棱两可。复杂声明更是令人敬而远之了,常常被作为例子的就是系统调用里的signal函数了,它在signal.h中声明的,声明如下

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

是不是需要琢磨一番呢,其实在经典的C语言书籍中都很清楚地讲过这个问题了,C专家编程里讲得十分的好,而且这本书也十分的好,作者还时不时的小幽默一把。

1. 声明是如何形成的

首先看一些C语言的术语以及一些能组合成一个声明的单独语法成分。其中一个非常重要的成分就是声明器(declarator)——它是所有声明的核心。简单的说,声明器就是标识符以及与它组合在一起的任何指针、函数括号、数组下标等。

声明器语法如下所示

指针 opt 直接声明器

详细可见下表

数量                C语言中的名字           C语言中的出现形式
-------------------------------------------------------------
零个或多个          指针                    下列形式之一:
                                            * const volatile
                                            * volatile
                                            *
                                            * const
                                            * volatile const
-------------------------------------------------------------
有且只有一个        直接声明器                   标识符
                                         或:标识符[下标]
                                         或:标识符(参数)
                                         或:(声明器)

一个声明由下表所示的各个部分组成(并非所有组合形式都是合法 的)。声明确定了变量的基本类型以及初始值(如果有)。

数量                C语言中的名字           C语言中出现的名字
-------------------------------------------------------------
至少一个类型说明符     类型说明符            void char short int long
(并非所有组合都合法)  (type-specifier)    signed unsigned float double
                                        结构说明符(struct-specifier)
                                        枚举说明符(enum-specifier)
                                        联合说明符(union-specifier)
                    存储类型              extern static register
                    (storage-class)     auto typedef
                    类型限定符            const volatile
                    (type-qualifier)
-------------------------------------------------------------
有且只有一个          声明器                参见上表
-------------------------------------------------------------
零个或更多            更多的声明器          ,声明器
-------------------------------------------------------------
一个                 分号                ;

在合法的声明中存在限制条件,不可以像下面这样做:

  • 函数的返回值不能是一个函数

  • 函数的返回值不能是一个数组

  • 数组里面不能有函数

但下面的是合法的:

  • 函数的返回值允许是一个函数指针

  • 函数的返回值允许是一个指向数组的指针

  • 数组里面允许有函数指针

  • 数组里面允许有其他数组

2. 优先级规则

上一节是讲声明的各个组成部分,本节描述了一种方法,用通俗的语言把声明分解开来,分别解释各个组成部分。要理解一个声明,必须要懂得其中的优先级规则(没错,又是优先级),语言律师们最喜欢这种形式,它高度简洁,可惜即不直观。

理解C语言声明的优先级规则

A   声明从它的名字开始读取,然后按照优先级顺序依次读取。
B   优先级从高到低依次是:
    B.1 声明中被括号括起来的部分
    B.2 后缀操作符:
        括号()表示这是一个函数,而
        方括号[]表示这是一个数组。
    B.3 前缀操作符:`*`表示“指向...的指针”
C   如果const和(或)volatile关键字的后面紧跟类型说明符(如int, long等),那么它作用于
    类型说明符。在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。

可以试着用优先级规则分析一下

char * const *(*next)();

3. 通过图表分析C语言的声明

本节通过一张里面标明了分析步骤的图,按图索骥,从第一步开始,顺着箭头逐步往下分析,无论多么复杂的C语言声明都可以迎刃而解,都可以用最通俗的语言来解释。

解析C语言的声明

开始我们从左向右寻找,直到找到第一个标识符。当声明中的某个符号与图中所示匹配时,便把它从声明中处理掉,以后不再考虑。在具体的每一步骤上,我们首先查看右边的符号,再看左边的。

当所有的符号都被处理完毕后,便宣告大功告成。

可以试着用图表分析法分析上边的那个例子。

4. 一个例子

现在,我们再回头看一下之前提到的signal函数的声明。

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

首先标识符是signal,它右边紧跟了括号,则表示“signal是一个返回…的函数”;

这个函数有两个参数:一个是int型,一个是void (*)型的,即返回值为void的函数指针。

它的返回类型则是void (*)(int)型,即一个返回值为void,有一个int型参数的函数指针。

说着这么绕呢,其实现在看来这个声明还是挺清晰的了。signal函数的第二个参数就是信号处理函数的函数指针,信号处理函数接受一个int型参数,表示接到的是几号信号。


其实可以编写一个能够分析C语言的声明,并把它们翻译成通俗语言的程序。可以通过堆栈实现,也可以用有限状态机来实现,都可以加深对复杂声明的理解,而且很好的练习编码。

参考书籍

  • C专家编程

  • C陷阱与缺陷

  • C程序设计语言