Pointers and Arrays_4
2017-02-19 14:55 星星之火✨🔥 阅读(267) 评论(0) 收藏 举报1、编写程序expr,以计算从命令行输入的逆波兰表达式的值,其中每个运算符或操作数用一个单独的参数表示。例如,命令expr 2 3 4 + * 将计算表达式2×(3+4) 的值。
#include <stdio.h> #include <stdlib.h> // for atof() #define MAXOP 100 // max size of operand or operator #define NUMBER '0' // signal that a number was found int getop(char []); void ungets(char []); void push(double); double pop(void); // reverse Polish calculator; uses command line int main(int argc, char *argv[]) { char s[MAXOP]; double op2; while(--argc > 0) { ungets(" "); // push end of argument ungets(*++argv); // push an argument switch(getop(s)) { case NUMBER: push(atof(s)); break; case '+': push(pop() + pop()); break; case '*': push(pop() * pop()); break; case '-': op2 = pop(); push(pop() - op2); break; case '/': op2 = pop(); if(op2 != 0.0) push(pop() / op2); else printf("error: zero divisor"); default: printf("error: unknown command %s\n", s); argc = 1; break; } } printf("\t%.8g\n", pop()); return 0; }
这里给出的解决方案是在TCPL Reading Notes 中的逆波兰计算器的基础上得到的。它使用了push 和pop 函数。我们先利用ungets 函数把一个参数结束标记(' ',一个空格字符)和一个参数依次压入输入缓冲区。这样,我们就可以不加修改地使用getop 函数了。getop 将调用getch 读取字符并分离出下一个运算符或操作数。如果在读取参数的过程中遇到了错误,argc 将被设置为1,主函数中的while 循环while(--argc > 0) 将因条件表达式的求值结构为假而终止程序运行。如果来自命令行的是一个合法的表达式,它的计算结果就将被放在堆栈的最顶部,这个结果将在我们把输入参数全部处理完毕后被打印。
2、在TCPL Reading Notes 中第76 条复杂声明中,dcl 程序有很多限制(该程序的目的意在说明问题,并不想做的尽善尽美),它只能处理类似于char 或int 这样的简单数据类型,而无法处理函数中的参数类型或类似于const 这样的限定符。它不能处理带有不必要的空格的情况。由于没有完备的出错处理,因此它也无法处理无效的声明。这里,我们对它进行一定程度上的修改,使它能够处理输入中的错误。
#include <stdio.h> #include <string.h> #include <ctype.h> enum { NAME, PARENS, BRACKETS }; enum { NO, YES }; void dcl(void); void dirdcl(void); void errmsg(char *); int gettoken(void); extern int tokentype; // type of last token extern char token[MAXTOKEN]; // last token string extern char name[MAXTOKEN]; // identifier name extern char out[1000]; extern int prevtoken; // dcl: parse a declarator void dcl(void) { int ns; for(ns = 0; gettoken() == '*'; ) // count *'s ns++; dirdcl(); while(ns-- > 0) strcat(out, " pointer to"); } // dirdcl: parse a direct declarstor void dirdcl(void) { int type; if(tokentype == '(') // (dcl) { dcl(); if(tokentype != ')') errmsg("error: missing )\n"); } else if(tokentype == NAME) // variable name strcpy(name, token); else errmsg("error: expected name or (dcl)\n"); while((type=gettoken()) == PARENS || type == BRACKETS) if(type == PARENS) strcat(out, " function returning"); else { strcat(out, " array"); strcat(out, token); strcat(out, " of"); } } // gettoken.c 源文件 #include <ctype.h> #include <string.h> enum { NAME, PARENS, BRACKETS }; enum { NO, YES }; extern int tokentype; // type of last token extern char token[]; // last token string int prevtoken = NO; // there is no previous token // gettoken: return next token int gettoken(void) // return next token { int c, getch(void); void ungetch(int); char *p = token; if(prevtoken == YES) { prevtoken = NO; return tokentype; } while((c = getch()) == ' ' || c == '\t') ; if(c == '(') { if((c = getch()) == ')') { strcpy(token, "()"); return tokentype = PARENS; } else { ungetch(c); return tokentype = '('; } } else if(c == '[') { for(*p++ = c; (*p++ = getch()) != ']'; ) ; *p = '\0'; return tokentype = BRACKETS; } else if(isalpha(c)) { for(*p++ = c; isalnum(c = getch()); ) *p++ = c; *p = '\0'; ungetch(c); return tokentype = NAME; } else return tokentype = c; }
我们对函数dirdcl 做了一些修改,它现在能够分析出两种记号——跟在dcl 调用后的一个右括号(')')或一个变量名。如果不是这两种记号,我们将调用函数errmsg 而不是printf。errmsg 会先打印一条出错信息,然后把变量prevtoken 设置为YES 以通知gettoken 说已经读入了一个记号。gettoken 开头部分有一个新的if 语句:
if(prevtoken == YES){ prevtoken = NO; return tokentype; }
它的意思是:如果已经有了一个记号,就不要再读入一个新记号了。这个改进版本并不是十全十美的,但它已经具备一定的出错处理能力了。
3、修改TCPL Reading Notes 中第76 条复杂声明中的undcl 程序,使它在把文字描述转换为声明的过程中不会生产多余的圆括号。
#include <stdio.h> #include <string.h> #include <ctype.h> #define MAXTOKEN 100 enum { NAME, PARENS, BRACKETS }; void dcl(void); void dirdcl(void); int gettoken(void); int nexttoken(void); int tokentype; // type of last token char token[MAXTOKEN]; // last token string char out[1000]; // undcl: convert word description to declation int main(void) { int type; char temp[MAXTOKEN]; while(gettoken() != EOF) { strcpy(out, token); while((type = gettoken()) != '\n') if(type == PARENS || type == BRACKETS) strcat(out, token); else if(type == '*') { if((type = nexttoken()) == PARENS || type == BRACKETS) sprintf(temp, "(*%s)", out); else sprintf(temp, "*%s", out); } else if(type == NAME) { sprintf(temp, "%s %s", token, out); strcpy(out, temp); } else printf("invalid input at %s\n", token); printf("%s\n", out); } return 0; } enum { NO, YES }; int gettoken(void); // nexttoken: get the next token and push it back int nexttoken(void) { int type; extern int prevtoken; type = gettoken(); prevtoken = YES; return type; }
如果"x 是一个指向char 的指针",undcl 程序的输入将是:x * char。改进前的undcl 程序的输出结果是:char (*x)。这里,输出结果中的括号是多余的。事实上,只有当下一个记号是() 或[] 时,undcl 程序才有必要在自己的输出结果中使用括号。
再比如,如果"daytab 是一个指针,它指向一个有[13] 个int 元素的数组",undcl 程序的输入将会是:daytab * [13] int。改进前的程序的输出结果:int (*daytab)[13] 就是正确的。但是,如果"daytab 是一个有[13] 个元素的指针数组,数组中的每个元素分别指向一个int",undcl 程序的输入将是:daytab [13] * int,改进前的undcl 程序的输出结果:int (*daytab[13]) 中就会有多余的圆括号。
我们对undcl 进行了修改,让它检查下一个记号是不是() 或[]。如果下一个记号是() 或[],undcl 程序就必须给它加上括号;否则,输出结果中的括号就将是多余的。也就是说,我们必须根据undcl 中程序输入中的下一个记号来决定是否需要添加括号。
我们编写了一个简单的nexttoken 函数,它将调用gettoken,记录已经读入一个记号的事实并返回该记号的类型。gettoken 是我们在上一个练习中编写的一个函数,它在读入下一个记号前会先检查是否已经有一个可用的记号了。改进后的undcl 程序将不再产生多余的括号。