逆波兰计算器(C语言)

     源自《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,结果永远是个正数。

         

                          

 

 

 

 

 

    

你可能感兴趣的:(C)