1. 声明是如何形成的
声明器(declarator) 是C语言中所有声明的核心,简单的说:声明器就是标识符以及与它组合在一起的任何指针、函数括号、数组下表等,如下表所示:
上表可以简化为一个公式:声明器 = 直接声明器( 标识符 or 标识符[下标] or 标识符(参数) or (声明器) ) + (零个或多个指针)
声明器只是C语言声明的一个部分,C语言中的声明 = 至少一个类型说明符 + 一个声明器 + 零个或多个其他声明器 + 一个分号,如下表所示:
2.优先级规则
要理解一个声明,必须要懂得其中的优先级规则,它高度简洁,可惜极不直观。下面给出一种方法,用通俗的语言把声明分解开来,分别解释各个组成部分。
理解C语言声明的优先级规则:
- A 声明从它的名字开始读取,然后按照优先级顺序依次读取
- B 优先级从高到低依次是:
- 声明中被括号括起来的部分
- 后缀操作符:括号()表示这是一个函数,[]则表示这是一个数组。
- 前缀操作符:星号* 表示"指向…的指针“
- C 如果const或volatile关键字后面紧跟类型说明符(如int,
long等),那么它作用于类型说明符,在其他情况下,const或volatile关键字作用于它左边紧邻的指针星号。
按照上面的优先级规则,可以用下图直观的解析。C语言中的声明读起来并没有固定的方向,一开始,我们从左边开始向右找寻,直到找到第一个标识符,当声明中的某个符号与图中所示匹配时,便把它从声明中处理掉,以后不再考虑。在具体的每一步骤上,我们先查看右边的符号,然后再看左边,直到所有的符号都处理完毕。
下面给出一个例子,用上图所示的方法分析这个声明:
char * const *(*next)();
##3.把C语言声明翻译成通俗语言的程序
由于声明解析并非顺序进行,使用堆栈存储各个标记更方便处理。我们从左向右读取,把各个标记依次压入堆栈,直到读到标识符为止,然后继续向右读取一个标记,判断是否为数组或函数。接着观察标识符左边的标记(需要从堆栈中弹出)。一个标记需要存储本身字符及其类型,故使用一个结构体存储一个标记,数据结构大致如下:
#define MAXTOKENLEN 64 //每个标记的最长长度
#define MAXTOKENS 100 //一条声明中的最大标记数量
/*定义类型标签 标识符 限定符 类型*/
enum type_tag{
IDENTIFIER, QUALIFIER, TYPE};
/*声明一个标记结构*/
struct token{
char type;
char string[MAXTOKENLEN];
};
struct token stack[MAXTOKENS]; /*保存第一个标识符之前的所有标记*/
struct token this; /*保存刚读入的那个标记*/
int top = -1; /*栈顶索引*/
#define pop stack[top--] /*出栈*/
#define push(s) stack[++top] = s /*入栈*/
定义好数据结构后,main函数主程序要做的第一步是找出标识符,然后按上面的解析流程图处理标识:
int main(void)
{
read_to_first_identifer(); //将标记压入堆栈中,直到遇到第一个标识符
deal_with_declarator(); //处理标识
printf(".\n");
return 0;
}
找出第一个标识符的函数read_to_first_identifer()可以由更通用的获取下一个标记到this的函数void gettoken(void)实现,且需要判断标记为类型描述符、限定符还是标识符,判断标记类型可以由enum type_tag classify_string(void)函数实现,代码如下:
/*宏声明一种更直观的字符串表示格式*/
#define STRCMP(a,R,b) (strcmp(a,b) R 0)
/*推断标记的类型*/
enum type_tag classify_string(<