程序来源于《c专家编程》上作者提供的源码,在原有的基础上增加了一些注释以方便理解。
总体思路:
- 读至第一个标识符,将读取过程遇到的所有标记(包括"const"、"int"等关键字,指针符"*"和单字符标记"["、"("等,不包括空格、标识符)进行分类和说明,然后压入堆栈中
- 按照书上的声明处理顺序即操作(如下图)对标识符的左右标记进行处理,向左通过访问堆栈,向右通过输入流
源码及注释如下:
#include<stdio.h>
#include<string.h>
#define MAXTOKENLEN 64
#define MAXTOKENS 100
#define pop stack[top--]
#define push(s) stack[++top] = s
#define STRCMP(a, R, b) (strcmp(a, b) R 0)
/*标识符,限定符,类型*/
enum type_tag { IDENTIFIER, QUALIFIER, TYPE };
//标识符的数据结构
struct token {
char type; //标识符的类型
char string[MAXTOKENLEN]; //string用来保存标记输出的信息
};
struct token stack[MAXTOKENS]; //保存第一个标识之前的所有标记
struct token this; //保存刚读入的标记
int top = -1; //栈顶指针
/*实用程序---------------*/
/* 判断当前标记的类型 */
enum type_tag classify_string()
{
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, ==, "int")) return TYPE;
if(STRCMP(s, ==, "short")) return TYPE;
if(STRCMP(s, ==, "long")) return TYPE;
if(STRCMP(s, ==, "signed")) return TYPE;
if(STRCMP(s, ==, "unsigned")) 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()
{
char *p = this.string;
/*略过空白字符*/
while((*p = getchar()) == ' ');
/*读入的标识符以字母,0-9开头,下划线开头*/
if(isalnum(*p) || (*p == '_')) {
while(isalnum(*++p = getchar()) || (*p == '_'));
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 = this.string[0];
}
/*读至第一个标识符*/
void read_to_first_identifier()
{
gettoken();
while(this.type != IDENTIFIER) {
push(this);
gettoken();
}
printf("%s is ", this.string);
gettoken();
}
/*解析程序--------------*/
/*处理函数参数,直到读取')'*/
void deal_with_function_args()
{
while(this.type != ')') {
gettoken();
}
printf("function returning ");
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 ");
}
}
/*处理(多重)指针,非必要,参考 134~135行*/
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();
}
/*向左读取
处理读入到标识符之前压入到堆栈中的指针
该语句并不是必要的,因为包含在下面的else中,没有该函数,else也能正确处理指针并输出信息
maybe是为了严格按照思路来编程,遵循正确的思维模式*/
deal_with_pointers();
/*处理读入到标识符之前压入到堆栈中的符号*/
while(top >= 0){
if(stack[top].type == '('){
pop;
gettoken(); //因为处理数组和函数时会读取右括号的下一个标记,可能是')'
//这里相当于跳过了')' ,读取')'之后的符号,
deal_with_declarator();
}else {
printf("%s ", pop.string);
}
}
}
/*主程序----------*/
int main()
{
read_to_first_identifier();
deal_with_declarator();
printf("\b.\n");
return 0;
}