最近看了不少C方面的书,对C的了解了加深了不少。发现好几本书对C的声明器都做了详细的介绍,于是把《C专家编程》中实现的C声明照敲了一遍,权当加深一点印象吧。
虽然是按照《C专家编程》码的代码,但是个人觉得《征服C指针》中对声明器的讲解更有利于程序员来分析。《C专家编程》中讲解的更学术一些,感觉是按照计算机的思考方式写的,所以更容易用编程语言来实现。《征服C指针》中的讲解时按照程序员的思考方式来写了,看了以后可以很容易读懂声明。两本书对声明的讲解各有千秋吧。
先看看《C专家编程》
第一就是C语言中声明的优先级规则:
A 声明从它的名字开始读取,然后按照优先级顺序依次读取; B 优先级从高到底依次是: B.1声明中被括号括起来的那部分; B.2后缀操作符: 括号()表示这是个函数,而 方括号[]表示这是个数组; B.3前缀操作符:星号*表示“指向...的指针” C 如果const和(或)volatile关键字的后面紧跟着类型说明符(如int,long等),那么它作用于类型说明符。在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
实际上C声明的解读就是按照上面的优先级规则解读就行了,换一种方式使用图来描述上面的优先级规则如下:
上面这个图也是《C专家编程》中的,只不过对于其中的箭头我用不同的颜色标注了一下,感觉这样各个流程会更清晰一些,具体跳转到那步也加上了下划线。先往右读一直读到标识符,然后优先检查右边的方括号和圆括号,检查完毕后再向左读,每当碰到左边的圆括号"("表示又要递归开始检查与“(”对应的“)”右边的方括号和圆括号了。
然后是程序的代码,《C专家编程》中还有伪代码,伪代码就不列出来了,我以注释的形式将一些关键伪代码加到代码中了。
#include <stdio.h> #include <string.h> #include <ctype.h> #include <stdlib.h> #define MAXTOKENS 100 #define MAXTOKENLEN 64 /*标记的类型,包括标识符,限定符,类型*/ enum type_tag {IDENTIFIER,QUALIFIER,TYPE}; /*标记的数据结构,包括类型和内容*/ struct token{ char type; char string[MAXTOKENS]; }; int top=-1; struct token stack[MAXTOKENS];/*保存第一个标识符之前的所有标记*/ struct token this;/*保存刚读入的那个标记*/ #define pop stack[top--] /*出栈*/ #define push(s) stack[++top]=s /*入栈*/ /*理解所有分析过程的代码段*/ void read_to_first_identifer();/*读至第一个标识符*/ /*分析字符串的类型*/ enum type_tag classify_string(void);/*字符串分类*/ void gettoken(void);/*取标记*/ void deal_with_declarator();/*处理声明器*/ void deal_with_arrays(); void deal_with_function_args(); void deal_with_pointers(); int main(){ /*将标记压入堆栈中,直到遇见标识符*/ read_to_first_identifer(); deal_with_declarator(); printf("\n"); return 0; } enum type_tag classify_string(void){ /*推断标示符的类型*/ char *s=this.string; if(!strcmp(s,"const")){ strcpy(s,"read-only "); return QUALIFIER; } if(!strcmp(s,"volatile")) return QUALIFIER; if(!strcmp(s,"void")) return TYPE; if(!strcmp(s,"char")) return TYPE; if(!strcmp(s,"signed")) return TYPE; if(!strcmp(s,"unsigned")) return TYPE; if(!strcmp(s,"short")) return TYPE; if(!strcmp(s,"int")) return TYPE; if(!strcmp(s,"long")) return TYPE; if(!strcmp(s,"float")) return TYPE; if(!strcmp(s,"double")) return TYPE; if(!strcmp(s,"struct")) return TYPE; if(!strcmp(s,"union")) return TYPE; if(!strcmp(s,"enum")) return TYPE; return IDENTIFIER; } void gettoken(void) /*读取下一个标记到"this"中*/ { char *p=this.string; /*略过空白符*/ while((*p=getchar())== ' '); if(isalnum(*p)){ /*读入的标识符以A-Z,0-9开头*/ while(isalnum(*++p=getchar())); ungetc(*p,stdin); *p='\0'; this.type=classify_string(); return; } if(*p=='*'){ strcpy(this.string,"pointer to "); this.type='*'; return ; } this.string[1]='\0'; this.type=*p; return ; } /*理解所有分析过程的代码段*/ void read_to_first_identifer(){ gettoken(); while(this.type!=IDENTIFIER){ push(this); gettoken(); } printf("%s is ",this.string); gettoken(); } void deal_with_arrays(){ while(this.type=='['){ printf("array "); gettoken(); /*数字或']'*/ if(isdigit(this.string[0])){ printf("0..%d " ,atoi(this.string)-1); gettoken(); /*读取']'*/ } gettoken();/*注意:读取了']'后继续向后读取了一个标记*/ printf("of "); } } void deal_with_function_args(){ while(this.type!=')'){ gettoken(); } gettoken();/*注意:读取越过了')',即读取了')'后的下一个标记*/ printf("function returning "); } void deal_with_pointers(){ while(stack[top].type=='*'){ printf("%s ",pop.string); } } void deal_with_declarator(){ /*优先处理右边的可能的符号[和(*/ /*处理标识符之后可能存在的数组,函数*/ switch(this.type){ case '[':deal_with_arrays();break; case '(':deal_with_function_args(); } /*然后处理左边的符号*,也就是在堆栈中的符号*/ deal_with_pointers(); /*处理在读入到标识符之前压入到堆栈中的符号*/ while(top>=0){ if(stack[top].type=='('){ pop; gettoken();/*读取')'之后的符号*/ deal_with_declarator(); }else{ printf("%s",pop.string); } } }
下面是使用gcc测试的两个例子:
注意两个错误的声明及其解释:
int f()[];
f是一个返回值为数组的函数。错误!!!
原因是函数只能返回标量,不能返回数组。
int f[]();
f是一个返回值为int的函数的数组。这个声明也是非法的,因为数组元素必须具有相同的长度,但不同的函数明显可能具有不同的长度。
诸如int (*f[])();
的声明是正确的:f是一个数组,这个数组的元素是指向返回值为int的函数的指针,指针的长度是相同的。
使用有限状态机实现cdecl.c程序。
有限状态机(FSM)是一个数学概念,如果把它应用到程序中,可以发挥很大的作用,它是一种协议,用于有限数量的子程序(状态)的发展变化。每个子程序进行一些处理并选择下一种状态(通常取决于下一段输入)。
有限状态机可以用作程序的控制结构。它对于那些基于输入的在几个不同的可选动作中进行循环的程序尤其合适。
它的基本思路是用一张表保存所有可能的状态,并列出进入每个状态时可能执行的所有动作,其中最后一个动作就是计算(通常在当前状态和下一次输入字符的基础上,另外再经过一次表查询)一个应该进入的状态。你从一个“初始状态”开始。在这一过程中,翻译表可能会告诉你进入了一个错误的状态,表示一个预期之外的或错误的输入。你不停的在各个状态间进行转换,知道到达结束状态。
在c语言中,有几种方法可以用来表达有限状态机,但它们绝大多数都是基于函数指针数组。
下面是使用有限状态机编写的cdel程序:
#include <stdio.h> #include <string.h> #include <ctype.h> #define MAXTOKENS 100 #define MAXTOKENLEN 64 enum type_tag{IDENTIFIER,QUALIFIER,TYPE}; struct token{ char type; char string[MAXTOKENLEN]; }; int top=-1; struct token stack[MAXTOKENS]; struct token this; #define pop stack[top--] #define push(s) stack[++top]=s enum type_tag classify_string(void){ char *s=this.string; if(!strcmp(s,"const")){ strcpy(s,"read-only "); return QUALIFIER; } if(!strcmp(s,"volatile")) return QUALIFIER; if(!strcmp(s,"void")) return TYPE; if(!strcmp(s,"char")) return TYPE; if(!strcmp(s,"signed")) return TYPE; if(!strcmp(s,"unsigned")) return TYPE; if(!strcmp(s,"short")) return TYPE; if(!strcmp(s,"int")) return TYPE; if(!strcmp(s,"long")) return TYPE; if(!strcmp(s,"float")) return TYPE; if(!strcmp(s,"double")) return TYPE; if(!strcmp(s,"struct")) return TYPE; if(!strcmp(s,"union")) return TYPE; if(!strcmp(s,"enum")) return TYPE; return IDENTIFIER; } void gettoken(void){ char * p=this.string; while((*p=getchar())==' '); if(isalnum(*p)){ while(isalnum(*++p=getchar())); ungetc(*p,stdin); *p='\0'; this.type=classify_string(); return ; } this.string[1]='\0'; this.type=*p; return; } void initialize(),get_array(),get_params(),get_lparen(),get_ptr_part(),get_type(); void (*nextstate)(void)=initialize; int main(){ /*用有限状态机实现的cdecl*/ while(nextstate!=NULL) (*nextstate)(); return 0; } void initialize(){ gettoken(); while(this.type!=IDENTIFIER){ push(this); gettoken(); } printf("%s is ",this.string); gettoken(); nextstate=get_array; } void get_array(){ nextstate=get_params; while(this.type=='['){ printf("array "); gettoken();/*一个数字或']'*/ if(isdigit(this.string[0])){ printf("0..%d ",atoi(this.string)-1); gettoken();/*读取']'*/ } gettoken();/*在']'之后读取*/ printf("of "); nextstate=get_lparen; } } void get_params(){ nextstate=get_lparen; if(this.type=='('){ while(this.type!=')') { gettoken(); } gettoken(); printf("function returning "); } } void get_lparen(){ nextstate=get_ptr_part; if(top>=0){ if(stack[top].type=='('){ pop; gettoken();/*在')'之后读取*/ nextstate=get_array; } } } void get_ptr_part(){ nextstate=get_type; if(stack[top].type=='*'){ printf("pointer to "); pop; nextstate=get_lparen; }else if(stack[top].type==QUALIFIER){ printf("%s ",pop.string); nextstate=get_lparen; } } void get_type(){ nextstate=NULL; /*处理在读入标识符之前被放在堆栈里的所有标记*/ while(top>=0){ printf("%s ",pop.string); } printf("\n"); }