在逆波兰表示数中,所有的运算符都跟在操作数后面,例如
(1-2)×(4+5)
采用逆波兰表示法表示为:
1 2 - 4 5 + ×
逆波兰表示法不需要圆括号,只要知道每个运算符需要几个操作数就不会引起歧义
用逆波兰表示法实现计算器采用栈结构.每个操作数都被依次压到栈中;当一个运算符到达时,从栈中弹出相应的操作数,把该运算符作用于弹出的操作数,并把运算结果再压入到栈中.举个例子,对于上面的表达式,首先把1 2 压入栈中,再用再用两者之差取代它们,然后将4 5压入栈中,再用两者之和取代它们,最后从栈中取出栈顶的-1和9,并把它们的积-9压入到栈顶.到达输入行的末尾时,把栈顶的值弹出并打印
由此,我们得到了计算器的基本框架
while(下一个运算符或操作数不是文件结束指示符)
if(是数)
将该数压入到栈中
else if(是运算符)
弹出所需数目的操作数
执行运算
将结果压入栈中
else if(是换行符)
弹出并打印栈顶的值
else
出错
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> int getch(void); void ungetch(int c); #define MAXSTACK 100 /* */ /* */ double stack[MAXSTACK]; //创建栈 int sp=0; //初始化栈指针 void push(double val) //入栈函数 { if ( sp>=MAXSTACK-1 ) printf ( "error : stack full ,can't push\n" ); else stack[sp++]=val; //将数据压栈并且使栈指针指向一个空栈位 } double pop(void) { if( sp==0 ) { printf ( "error : stack empty ,can't pop\n" ); return 0.0; } else return stack[--sp]; //因为栈指针指向下一个未填入数据的栈位,故使用前缀自减 } #define NUMBER '0' /*标记输入的是数字 */ int getop(char *s) { int i,c; while ( (s[0]=c=getch())==' '||c=='\t' ) //过滤输入操作数前的空白符号 ; s[1]='\0'; //不是很理解,因为后面又继续给s[1]赋值,可能是考虑到输入第一个操作数就是错误的操作数然后直接返回,因而只有s[0]有值,故加上结束符号 if( !isdigit(c)&&c!='.' ) //如果输入的第一个数不是数字或者小数点,那么就是一个非法操作数(因为使用的是逆波兰表示法) return c; i=0; if ( isdigit(c) ) //收集整数部分 while( isdigit(s[++i]=c=getch()) ) //如果程序能运行到此处(未返回),说明第一个输入的字符是合法并且已经纪录,s[0]中已经有值存在,故使用++i ; if( c=='.' ) while( isdigit(s[++i]=c=getch()) ) ; s[i]='\0'; if ( c!=EOF ) ungetch(c); return NUMBER; } /* * 关于getch(),ungetch(),K&R C在此处主要是用于模拟后面章节提到的标准库中的getc()和ungetc() * 下面是网上找的解释: * 数据在输入输出时 要经过缓冲区 * 当调用getc()的时候 标准输入里的一个字符会被放到缓冲区中 再把缓冲区里的字符赋到变量里 * 当调用ungetc(c)时候 作为参数的c会被回放到缓冲区中(压回输入总),也就是说,下一个getc()的输入就是刚刚被压回缓冲区的c * 为什么在此要用到这两个函数呢?K&R C中的解释如下: * 程序中经常会出现这样的情况: * 程序不能确定它已经读入的输入时候足够,除非超前多读入一些输入.读入一些字符以合成一个数字的情况便是一个例子:在看到第一个非数字之前 * 已经读入的数的完整性是不能确定的.由于程序要超前读入一个字符,这样就导致最后有一个字符不属于当前所要读入的数(个人理解:为了避免最后 * 一个字符是运算符号而无法读取的错误) */ #define BUFSIZE 100 char buf[BUFSIZE]; //用于ungetch函数的缓冲区 int bufp=0; int getch(void) { return ((bufp>0)?buf[--bufp]:getchar()); } void ungetch(int c) { if ( bufp>=BUFSIZE-1 ) printf ( "ungetch: too many characters\n" ); else buf[bufp++]=c; } /* * 到现在,基本实现的简单的四则运算计算器,下面是主函数 */ #define MAXOP 100 /* 操作数或运算符的最大长度 */ int main ( int argc, char *argv[] ) { int type; double op2; char s[MAXOP]; while ( (type=getop(s))!=EOF ) { switch ( type ) { case NUMBER: push(atof(s)); //输入的是操作数,把操作数转换为double类型并入栈 break; case '+': push(pop()+pop()); break; case '-': op2=pop(); //区分减数和被减数,除法取模运算同理 push(pop()-op2); break; case '*': push(pop()*pop()); break; case '/': op2=pop(); if ( op2!=0.0 ) push(pop()/op2); else printf ( "error: zero divisor\n" ); break; case '\n': printf ( "\t%.8g\n",pop() ); break; default: printf ( "error :unknown command %s\n",s ); break; } /* ----- end switch ----- */ } return EXIT_SUCCESS; } /* ---------- end of function main ---------- */
<pre name="code" class="cpp">/* * 下面是相应课后练习 * * 练习4-3:在用了基本框架后,对计算器进行扩充就比较简单了,在该程序中加入取摸运算符号,并注意考虑负数情况 * 分析:取模运算注意是对浮点数取模.然后加入负数后getop函数需要检查紧跟在符号 - 后面的那个字符,以判断该符号到底代表的是一个减号,还是一个负数 * 比如: - 1是一个减号后面跟一个数字,而 -1.23则是一个负数 * 改进后的getop函数 */ int getop(char *s) { int c,i; while ( (s[0]=c=getch())==' '||c=='\t' ) ; s[1]='\0'; i=0; if ( !isdigit(c)&&c!='.'&&c!='-' ) return c; if ( c=='-' ) if ( isdigit(c=getch())||c=='.' ) s[++i]=c; //负数情况 else { if( c!=EOF ) ungetch(c); //减号 return '-'; } 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; } /* 练习4.5:给计算器程序增加访问sin,exp,pow等库函数的操作 * 分析:主要解决的问题是如何识别输入的sin,exp,pow等符号.库函数名都是小写字母组成,通过这一特点,我们可以从输入的第一个符号入手:字母?表示要引用一个 * 库函数,于是进入接收库函数程序部分. * 改进后的getop函数 */ #define NAME 'n' /* 标记输入的是一个库函数名 */ int getop(char *s) { int c,i; while ( (s[0]=getch())==' '||c=='\t' ) ; s[1]='\0'; i=0; if ( islower(c) ) { while ( islower(s[++i]=c=getch()) ) ; s[i]; if ( c!=EOF ) ungetch(c); if(strlen(s)>1) return NAME; else return c; } if ( !isdigi(c)&&c!='.' ) return c; if ( isdigit(c) ) while ( isdigit(s[++i]=c=getch()) ) ; if( c=='.' ) while ( isdigi(s[++i]=c=getch()) ) ; s[i]='\0'; if ( c!=EOF ) ungetch(c); return NUMBER; } /* */ case NAME: mathfnc(s); break; void mathfnc(s) { double op2; if ( strcmp(s,"sin")==0 ) push(sin(pop())); ...以此类推 } /* * 练习4.10:用getline函数读入整个输入行,在这种情况下可以不使用getch和ungetch函数 * 分析:使用getline函数后,我们使用数组保存每次输入的一整行输入行,替代了缓冲区 * getline函数K&R C中一个输入字符串返回字符长度的函数 */ int getline(char *s,int maxline) { int c,i; for ( i=0;i<maxline&&(c=getchar())!=EOF&&c!='\n' ;i++ ) { *(s+i)=c; } *s='\0'; return i; } #define MAXLINE 100 /* */ int li=0; char line[MAXLINE]; int getop(char *s) { int c,i; if ( line[li]=='\0' ) if ( getline(line,MAXLINE)==0 ) return EOF; else li=0; while ( (s[0]=c=line[i++])==' '||c=='\t' ) ; s[1]='\0'; if ( !isdigit(c)&&c!='.' ) return c; if( isdigit(c) ) while ( isdigit(s[++i]=c=line[li++]) ) ; if ( c=='.' ) while ( isdigit(s[++i]=c=line[li++]) ) ; s[i]='\0'; li--; //用于下一次判断 return NUMBER; }