在C语言中,有些声明看起来会十分晦涩。所以可以通过编写程序,将一些晦涩的声明翻译成通俗的语言。比如
char * (*c[10])(int **p);
char * const *(*next)();
...
对于声明,其中一个比较重要的成分是声明器,它是指标识符以及与它组合在一起的任何指针、函数括号、数组下标等。在声明中,包括类型说明符,存储类型,类型限定符。
类型说明符包括 : void、char、short、int、long、signed、unsigned、float、double、结构说明符(struct)、枚举说明符(enum)、联合说明符(union)
存储类型包括 : extern、static、register、auto、typedef
类型限定符包括 : const、volatile
了解这些概念之后,开始编写程序。
这里有个设计方案,主要的数据结构是一个堆栈,我们从左向右读取,把各个标记依次压入堆栈,直到读取标识符为止。然后我们继续向右读入一个标记,也就是标识符右边的那个标识。接着,观察标识符左边的那个标记(需要从堆栈中弹出)。
数据结构大致如下:
struct token
{
char type;
char string[MAXTOKENLEN];
};
/* 保存第一个标识之前的所有标识 */
struct token stack[MAXTOKENS];
/* 保存刚读入的那个标识 */
struct token this;
//先在这里列出大致的伪代码
实用程序------
classify_string (字符串分类)
查看当前的标记
通过this.type返回一个值,内容为"type(类型)"、 "qualifier(限定符")
或"indentifier(标识符)"
get_token(取标记)
把下一个标记读入this.string
如果是字母数字组合,调用classify_string
否则必是一个单字符标记,this.type = 该标记;用NUL结束this_string
read_to_first_identifier(读至第一个标识符)
调用get_token,并把标记压入到堆栈中,直到遇见第一个标识符
Print "identifier is (标识符是) this.string"
继续调用get_token
解析程序------
deal_with_function_args(处理函数参数)
当读取越过右括号')'后,打印"函数返回"
deal_with_arrays(处理函数数组)
当读取"[size]"后,将其打印并继续向右读取
deal_with_any_pointers(处理任何指针)
当从堆栈中读取"*"时,打印"指向...的指针"并将其弹出堆栈
deal_with_declarator(处理声明器)
if this.type is '[' deal_with_arrays
if this.type is '(' deal_with_function_args
deal_with_any_pointers
while 堆栈里还有东西
if 它是一个左括号 '('
将其弹出堆栈,并调用get_token; 应该获得右括号')'
deal_with_declarator
else 将其弹出堆栈并打印它
以下是程序代码,为了简单方便起见,对于函数的参数不作处理:
#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[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 get_token(void) /* 读取下一个标识到this */
{
char *p = this.string;
/* 略过空白字符 */
while ((*p = getchar()) == ' ')
continue;
if (isalnum (*p)) /* 读入的标识符是以a-z, A-Z, 0-9开头 */
{
while (isalnum (*++p = getchar())) /* 一直读,直到读到的字符不是a-z, A-Z, 0-9 */
continue;
ungetc (*p, stdin); /* 将最后读到的那个不是 a-z, A-Z, 0-9 的字符退回到输入流中 */
*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_identifier(void)
{
get_token();
while (this.type != IDENTIFIER)
{
push (this);
get_token();
}
printf ("%s is ", this.string);
get_token();
}
void deal_with_arrays(void)
{
while (this.type == '[')
{
printf ("array ");
get_token(); /* 数字或']' */
if (isdigit (this.string[0]))
{
printf ("0..%d ", atoi(this.string) - 1);
get_token(); /* 读取']' */
}
get_token(); /* 读取']' 之后的再一个标记 */
printf ("of ");
}
}
void deal_with_function_args()
{
while (this.type != ')')
{
get_token();
}
get_token();
printf ("function returning ");
}
void deal_with_pointers()
{
while (stack[top].type == '*')
printf ("%s ", pop.string);
}
void deal_with_declarator(void)
{
/* 处理标识符之后可能存在的数组/函数 */
switch (this.type)
{
case '[' : deal_with_arrays();
break;
case '(' : deal_with_function_args();
break;
}
deal_with_pointers();
/* 处理在读入到标识符之前压入到堆栈中的符号 */
while (top >= 0)
{
if (stack[top].type == '(')
{
pop;
get_token(); /* 读取')'之后的符号 */
deal_with_declarator();
}
else
{
printf ("%s ", pop.string);
}
}
}
int main(void)
{
/* 将堆栈压入堆栈中,直到遇见标识符 */
read_to_first_identifier();
deal_with_declarator();
printf ("\n");
return 0;
}
对于该程序,整个的思路是这样:
1. 读取输入的字符串,比如 char * ( * c[10])( int ** p)。读取函数是read_to_first_identifier,具体的读取函数是get_token,该函数会返回读取的枚举类型,若不等于IDENTIFIER,则会一直读,同时将不等于IDENTIFIER的压入栈中。
2. 当读到整个标识符时,将继续读取它的下一位,用于处理标识符之后可能存在的数组或函数。若是[字符,则执行函数deal_with_arrays,若是(字符,则执行函数deal_with_function_args,若都不是,则返回之前栈中所保存的数据,根据数据类型打印相关内容