在C语言中,复杂的声明需要仔细分析。但一旦掌握了技巧,任何复杂的分析都显而易见。为了更清晰明白声明的分析过程,将详细分析声明的思路及函数。分析声明的过程如下:
下边是对第三章章尾的源码,及分析:
#include
#include
#include
#include
#defineMAXTOKENS 100
#defineMAXTOKENLEN 64
enumtype_tag{
IDENTIFIER,
QUALIFIER,
TYPE
};
structtoken{
char type;
char string[MAXTOKENLEN];
};
inttop = -1;
structtoken stack[MAXTOKENS];
structtoken this;
/****利用数组实现栈结构****/
#definepop stack[top--]
#definepush(s) stack[++top] = s
/*****将标记进行分类,将解释信息存入对应标记的解释字符串中,并返回标记的类型****/
enumtype_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;
}
/*****
函数功能:从标准输入中取一个字符,并进行类型标记
取到空格舍弃;
若为字母或数字开头,则为标识符、限定符或类型,一直将紧随其后的字母或数字取出,假定标识符、限定符或类型全由字母和数字组成;
如果是*,则表示是一个指向...的指针;
若为其他,则将该字符作为其类型
*****/
voidgettoken(void)
{
char *p =this.string;
//从标准输入中取一个不为空的字符,放入this标记中
while((*p = getchar()) == ' ');
//判断是否是字母或数字,若是,则判断类型
if(isalnum(*p))
{
while(isalnum(*++p = getchar()));
ungetc(*p, stdin); //将最后一个非字母、非数字字符返回输入流
*p = '\0';
this.type = classify_string();
return;
}
if(*p == '*')
{
//将指针的解释信息存入相应标记字符串
strcpy(this.string, "pointerto");
this.type = '*';
return;
}
this.string[1] = '\0';
this.type = *p;
return;
}
/****
读取数据直到读出标识符;
打印出 标识符is
并从标准输入中继续读取下一个字符;
****/
voidread_to_first_identifier(void)
{
gettoken();
while(this.type != IDENTIFIER)
{
push(this);
gettoken();
}
printf("%s is ",this.string);
gettoken();
}
/******
由于是数组,则首先打印 array
然后从标准输入中取一个字符,由于在gettoken中会将连续数字取出,
并标记为标识符(这个标识符类型在这里无所谓,由于下边没判断,直接判断是不是字),
如果字符串是字,则将字符串变为int型数值,减一是因为数组的最大存储下标比数组的大小小1.
在操作完之后,会读取]之后的下一个标记,进行后续函数处理,并打印出 of
******/
voiddeal_with_arrays(void)
{
while(this.type == '[')
{
printf("array ");
gettoken();
if(isdigit(this.string[0]))
{
printf("0..%d",atoi(this.string)-1);
gettoken();
}
gettoken();
printf("of ");
}
}
/******
如果是函数,则将参数忽略,直到读出)
再读出)后的下一个标记,进行后续函数处理,
打印出function returning
******/
voiddeal_with_function_args(void)
{
while(this.type != ')')
{
gettoken();
}
gettoken();
printf("function returning ");
}
/****
判断前一个标记是否为*
若为,则打印出pointer to
****/
voiddeal_with_pointers(void)
{
while(stack[top].type == '*')
{
printf("%s ",pop.string);
}
}
/****
该函数紧跟read_to_first_identifier,由于在read_to_first_identifier读取到标识符后又取了一个字符,
判断紧随标识符其后的字符的类型,进行处理;
若为 [,则说明为数组,调用deal_with_arrays;
若为 (,则说明为函数,调用deal_with_function_args;这两个函数都会在操作后再取一个标记;
然后判断前一个标记是否为指针;
然后判断前一个标记是否为(,若是,则忽略之前从标准输入中取得标记,认为为)
将这部分作为整体,再次取一个标记,再次进行处理判断;
若不是,则打印出弹出的标记注释
*****/
voiddeal_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;
gettoken();
deal_with_declarator();
}
else
{
printf("%s ",pop.string);
}
}
}
intmain(void)
{
read_to_first_identifier();
deal_with_declarator();
printf("\n");
return 0;
}
该源码没有进行错误判断,仅对正确的声明处理,且在涉及多个括号的包含时,如果不从最里层开始处理,可能出现错误,如:
由于对’(‘匹配的是右边第一个’)’,所以当匹配不对时,如前三个例子,总是解析错误。而对于最后一个,由于这个例子正好使得匹配括号正确,所以解析正确。
故对源代码进行修改,对’(‘进行计数,使得都能匹配正确的’)’。可以看以下结果。
其中在解决上述问题时,修改的代码部分如下:
/******
如果是函数,则将参数忽略,直到读出)
再读出)后的下一个标记,进行后续函数处理,
打印出 function returning
******/
void deal_with_function_args(void)
{
/****代码更改开始****/
int symbol_num = 1; //记录'('的数量,由于进入这个函数本身就需要匹配一个'('
gettoken();
while( symbol_num != 0)
{
if( this.type == ')' )
symbol_num--;
if( this.type == '(' )
symbol_num++;
gettoken();
}
/****代码更改结束****/
/***原代码****/
/****
while(this.type != ')')
{
gettoken();
}
gettoken();
****/
printf("function returning ");
}
int main(void)
{
while(1)
{
read_to_first_identifier();
deal_with_declarator();
printf("\r\n***************\r\n");
fflush(stdin);
}
return 0;
}
修改后的代码,对正确的声明可以解析(如果还有bug,可以评论区交流)。但该代码未对错误格式的声明的解析进行错误处理,解析的结果也根据输入的错误声名有关。
总过此程序,将深入理解C语言的声明。
在学习这个代码的过程中,发现了自己学C语言的一个错误,至于怎么错就不误扰大家了,下边说明以下两个声明的解释方式。
int * const * point_of_point
point_point 是一个指针,该指针指向一个只读指针,这个只读指针指向int型数据。
int * * const point_of_point
point_point 是一个只读指针,该只读指针指向一个指针,这个指针指向int型数据。