前面我们已经学会了如何理解声明:https://www.cnblogs.com/surplusvalue/p/12123398.html
事实上,在我们读源码的时候,或许也会遇到错综复杂的声明语句,为什么不写一个程序帮助我们理解呢?接下来我们将编写一个能够分析C语言的声明并把它们翻译成通俗语言的程序。为了简单起见,暂且忽略错误处理,而且在处理结构、枚举和联合时只简单地用“struct”、“enum”和“union”来代表它们的具体内容。最后,这个程序假定函数的括号内没有参数列表(实际上我们在分析的时候,参数列表也被忽略了)。
主要的数据结构是一个堆栈,我们从左向右读取,把各个标记依次压入堆栈,直到读到标识符为止。然后我们向右读入一个标记,也就是标识符右边的那个标记。接着观察标识符左边的那个标记(需要从堆栈中弹出)。数据结构大致如下:
struct token { char type; char string[MAXTOKENLEN]; }; /* 保存第一个标识之前的所有标记 */ struct token stack[MAXTOKENS]; /* 保存刚读入的那个标记 */ struct token t;
伪码如下:
实用程序---------- classify_string(字符串分类) 查看当前标记, 通过t.type返回一个值,内容为“type(类型)”,“qualifier(限定符)”或“identifier(标识符)” gettoken(取标记) 把下一个标记读入t.string 如果是字母数字组合,调用classify_string 否则,它必是一个单字符标记,t.type=该标记;用一个null结束t.string read_to_first_identifier(读至第一个标识符) 调用gettoken,并把标记压入到堆栈中,直到遇见第一个标识符。 Print“identifier is (标识符是)”,t.string 继续调用gettoken
解析程序----------
deal_with_function_args(处理函数参数)
当读取越过右括号‘)’后,打印“函数返回”
deal_with_arrays(处理函数数组)
当你读取“[size]”后,将其打印并继续向右读取。
deal_with_any_pointers(处理任何指针)
当你从堆栈中读取“*”时,打印“指向...的指针”并将其弹出堆栈。
deal_with_declarator(处理声明器)
if t.type is '[' deal_with_arrays
if t.type is '(' deal_with_function_args
deal_with_any_pointers
while 堆栈里还有东西
if 它是一个左括号'('
将其弹出堆栈,并调用gettoken;应该获得右括号')'
deal_with_declarator
else 将其弹出堆栈并打印它
主程序----------
main
read_to_first_identifier
deal_with_declarator
代码如下:
#pragma warning( disable : 4996) #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <ctype.h> #include <stdlib.h> #define MAXTOKENS 100 #define MAXTOKENLEN 64 //标识符identifier,修饰词qualifier,类型type(具体的例子见classify_string函数) enum type_tag { IDENTIFIER, QUALIFIER, TYPE }; struct token { char type; //词素的类型 char string[MAXTOKENLEN]; //保存词素的内容 }; int top = -1; //栈顶指针 //从左到右依次读取,把各个标记依次压入堆栈;保存第一个标识之前的所有标记的堆栈 struct token stack[MAXTOKENS]; struct token t; //保存刚读入的那个标记; //出栈和入栈操作 #define pop stack[top--] #define push(s) stack[++top]=s enum type_tag classify_string(void) //推断标识符的类型 { char *s = t.string; if (!strcmp(s, "const")) {//复习strcmp,两字符串相同,返回0 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) // 读取下一个标记到 "t" { char *p = t.string; // 略过空白字符 while ((*p = getchar()) == ' '); if (isalnum(*p)) { // 读入的标识符以A-Z, 0-9开头 while (isalnum(*++p = getchar())); ungetc(*p, stdin); *p = '