源自《The C Programming Language》 P62 ex4.3:
计算例如:(1 - 2) * (4 + 5)的值,采用逆波兰表示法(即后缀表示法)
代码:
main.c
#include #include //为了使用库函数atof #include //使用sin, exp, pow等数学函数 #include //使用strcmp, strlen等字符串函数 #include "getop.h" #define MAXOP 100 //操作数或运算符的最大长度(待处理字符串的最大长度) #define NUMBER '0' //标识找到一个数 #define NAME 'n' //标示找到一个数学函数 void push(double ); double pop(); //void printStack(double []); void clear(); void mathfnc(char []); //extern double val[]; //如果声明为extern val[]; 则报错:变量val被重定义 //extern sp; //逆波兰计算器 int main() { int type; double op2; double op1; //double tmp; char s[MAXOP]; while((type = getop(s)) != EOF) { switch(type) { case NUMBER: //当待处理字符串是数值字符串时,将其转换,并压栈 push(atof(s)); break; case '+': push(pop() + pop()); break; case '*': push(pop() * pop()); break; case '-': op2 = pop(); push(pop() - op2); //push(pop() - pop());是错误的,虽然算法运算符中操作数的结合方式是从左到右 //但是不能确定push参数中左边的pop函数一定比右边的pop函数先执行 break; case '/': op2 = pop(); if(op2 != 0.0) push(pop() / op2); else { printf("error: divide 0.0!"); return -1; } break; case '%': op2 = pop(); if(op2 != 0.0) push(fmod(pop(), op2)); else printf("error: mod 0.0!"); break; case '/n': //当键入换行符时,打印输出栈顶元素 /*if(sp > 0) printStack(val); else printf("error: stack empty!/n"); */ printf("the result = %.8g/n", pop()); break; case 'p': //不出栈的情况下,打印栈顶元素 op2 = pop(); printf("the top element of stack = %f/n", op2); push(op2); break; case 'd': //复制栈顶元素 op2 = pop(); //tmp = op2; //printf("the duplication of top element = %f/n", op2); push(op2); push(op2); printf("the duplication of top element = %f/n", op2); break; /*case 'S': push(sin(pop())); break; case 'E': push(exp(pop())); break; case 'P': op2 = pop(); push(pow(pop(), op2)); break; */ case NAME: //处理数学函数分支,这样比上面分别用每个命令来定义一个函数要通用,并容易扩展 mathfnc(s); break; case 's': //交换栈顶元素 op2 = pop(); op1 = pop(); push(op2); push(op1); break; case 'c': //清空堆栈 clear(); break; default: printf("error: unknown command %s", s); break; } } return 0; } #define MAXVAL 100 //栈val的最大深度 int sp = 0; //栈中的下一个空闲的位置 double val[MAXVAL]; //值栈 void push(double f) //把f压入值栈中 { if(sp < MAXVAL) val[sp++] = f; else printf("error: stack full, can't push %g/n", f); } double pop() //从值栈中弹出并返回栈顶的值 { if(sp > 0) return val[--sp]; else { printf("error: stack empty, can't pop/n"); return 0.0; } } /*void printStack(double* val) { printf("top of stack = %f/n", val[sp-1]); } */ void clear() //清空值栈 { sp = 0; return; } void mathfnc(char s[]) //数学函数处理的通用接口 { double op2; if(strcmp(s, "sin") == 0) push(sin(pop())); else if(strcmp(s, "cos") == 0) push(cos(pop())); else if(strcmp(s, "exp") == 0) push(exp(pop())); else if(strcmp(s, "pow") == 0) { op2 = pop(); push(pow(pop(), op2)); } else printf("error: %s not supported!/n", s); }
getop.c
#include #include #include #include "getop.h" //extern NUMBER; #define NUMBER '0' #define NAME 'n' int getop(char s[]) //获取下一个运算符或操作数 { int i; int c; while((s[0] = c = getch()) == ' ' || c == '/t') ; s[1] = '/0'; i = 0; if(c != '-' && !islower(c) && !isdigit(c) && c != '.') //判断是否属于这四种情况,如不是,下面分别对这四种情况处理 return c; //当是运算符时,返回此运算符的ASCII值 if(c == '-') if(isdigit(c = getch()) || c == '.') s[++i] = c; else { if(c != EOF) ungetch(c); return '-'; } if(islower(c)) { while(islower(s[++i] = c = getch())) ; s[i] = '/0'; if(c != EOF) ungetch(c); if(strlen(s) > 1) return NAME; else return s[0]; //错误:return c; 例:s = "v ",则 //返回空格,而本意是返回v } if(isdigit(c)) while(isdigit(s[++i] = c = getch())) //收集整数部分 ; if(c == '.') while(isdigit(s[++i] = c = getch())) //收集小数部分 ; s[i] = '/0'; if(c != EOF) ungetch(c); return NUMBER; //当是操作数时,返回NUMBER,标识这种情况 } #define BUFSIZE 100 //缓冲区的最大长度 //int buf[BUFSIZE]; //这样可以正确处理压回EOF(-1)及其他任何负数的情况 char buf[BUFSIZE]; //用于ungetch函数的缓冲区 int bufp = 0; //buf中下一个空闲位置 int getch() //取一个字符(可能是要压回的字符) { return (bufp > 0) ? buf[--bufp] : getchar(); } void ungetch(int c) //把字符压回到输入(缓冲区)中 { if(bufp >= BUFSIZE) printf("ungetch: too many characters/n"); else buf[bufp++] = c; }
getop.h
#ifndef _GETOP_H_ #define _GETOP_H_ //#include //#include //#include //#define MAXOP 100 //#define NUMBER '0' int getch(); void ungetch(int); int getop(char []); #endif
分析:
1, 程序设计:在设计本程序时,首先进行模块划分,
main.c:main函数 实现操作数压栈,出栈,算术运算,数学运算,打印 复制 交换栈顶元素等基本操作;
push函数 实现将double型数据压入值栈val中;
pop函数 实现将值栈val中的栈顶元素出栈;
clear函数 实现清空值栈val;
mathfnc函数 实现sin,cos, exp, pow等数学操作(调用math.h中的这些库函数来处理val中的数据
并非自定义上述函数);
getop.c: getop函数 实现从输入中获取一个操作数或操作符(* + - / % sin d 等操作符);
getch函数 实现从自定义的输入缓冲区(buf)或操作系统定义的输入缓冲区中读入一个字符;
ungetch函数 实现将字符压回到自定义的输入缓冲区(buf)中;
getop.h: 声明getop getch ungetch函数。
2, main函数中通过while((type = getop(s)) != EOF)处理每次从待处理的输入字符串中获取的s,这是程序的主干部分
在确定type != EOF时,通过switch - case 语句分别处理当type为 NUMBER + - * / % /n p d NAME s c default
等情况。
3, 对于 - / % 情况不能像 + * 情况直接使用push(pop() - pop()),因为不满足交换律,
虽然算法运算符中操作数的结合方式是从左到右,但是不能确定push参数中左边的pop函数一定比右边的pop函数先执行
4, getop函数,通过while((s[0] = c = getch()) == ' ' || c == '/t') ; 来跳过s头部的空白字符(空格,水平制表符),
每次对于第一个字符c通过判断(c != '-' && !islower(c) && !isdigit(c) && c != '.')这四种情况来分别处理,
若上述条件成立,表明c是一个例如 + - * /等单字符的操作符;
然后分四种情况:c == '-', islower(c), isdigit(c), c == '.' 进行处理。
注意:将最后一个读入的不符合条件的字符压回到自定义的输入缓冲区中。
getch函数: (bufp > 0) ? buf[--bufp] : getchar(); 从自定义输入缓冲区或OS定义的输入缓冲区中读入一个字符
ungetch函数:把字符压回到自定义的输入缓冲区中
5, 在合适的位置定义变量,例如:getop.c中BUFSIZE,buf,bufp在getop函数没有用到,而只在getch及ungetch函数
中用到,故其定义的位置在getop函数之后,而在getch函数之前,这样就可以防止在getop函数中出现无意修改上述变量
的可能。
确定某些文件用到哪些头文件,例如在在getop.c中用到isdigit等判断字符的函数,故在它里面添加ctype.h头文件,而在
main.c中不用到ctype.h中的库函数,故在main.c中不添加ctype.h头文件。
6, 如果想要ungetch函数正确处理压回的EOF或其他任何负数,则将输入缓冲区buf设置为int buf[BUFSIZE],即缓冲区的
数据类型为int型而不是char型。
C语言不要求char变量是signed或unsigned类型的,当把一个char型变量转换成int型变量,结果可能为正也可能为负,
例如,十进制的-1被表示为十六进制为0XFFFF(假定为一台16位机),当把0XFFFF保存到一个char型变量里去时,实际
被保存的数字是0XFF,当把0XFF转换成一个int型数据时,它可能被转换成0X00FF(255),也可能被转换成0XFFFF(-1)
所以打算对待像其他字符那样对待EOF时,应该把输入缓冲区buf声明成一个int型数组。
注:在某些机器上,如果一个char型变量的最高(左)二进制位为1,那么把它转换成一个int型数据时,就会在它的高位上
添加一系列1,这样得到的结果为负数;
在另一些机器上,当需要把一个char型变量转换成一个int型数据时,系统会在它的高位上添加一系列0,这样不管被
转换的char型变量的最高位是1还是0,结果永远是个正数。