这次所讲的是又一个栈的应用:算术表达式
这个程序是栈的应用的典型例子,是很好的栈的诠释,这个程序在K&R的书上也被叫做逆波兰计算器
/**********算术表达式的设计思想***********/
(1)首先,根据四则运算表达式,我们要先了解到其计算规则:括号为界限符,其优先率最高,其次为运算符*和/,最后为+和-;
(2)然后,我们根据把一个算术表达式划分为:开始('#')、运算符(加减乘除)、操作数(数值)和界限符(一对原括号)的方法,创建两个栈,
一个为Operator(运算符)栈,另一个为Operand(操作数)栈
『
<1>先将'#'push到Operator中,它将作为算术表达式的开始和结束标志
<2>我们依次把算术表达式的字符串给push如栈中,遇到数字时,就把它push到Operand中,这个比较好做,但是当遇到运算符时,我
们需要几个判断,后面会有介绍
<3>原括号的特殊处理(后面会说到)
<4>最后想说的是,我是先将整个表达式置于一个字符串中,整个计算的条件就是通过判断这个字符串有没有结束,如果到了最后,再通过
最开始我所说的'#'开始标志作为表达式的结束标志,来计算最后的结果(这是一个特殊处理,后面会说)
』
/************程序实现的几个细节***********/
我们这样就已经把这个程序的大致框架给描绘出来了,下面来说说实现这个程序的时候需要注意到的一些细节:
(栈的基本功能的实现就飘过了~~~)
(1)首先是运算符要被push或pop时的一些比较。
这是整个程序设计中最关键的步骤之一,我们给这个功能命名为Judge_Operator。有了最开始基本设计思想的讲述,这段代码应该比
较好实现了,这里要注意到是,我并没有把'('或着')'纳入判断中,因为我把原括号视为了一种特殊字符,当有圆括号出现时,就应该让
原括号里面的表达式独立出来,也就是让这个表达式单独进入一个新的循环进行这个表达式的求值,直到原括号消失在整个算术表达
式中,具体方法是这样的:
『
运算符中当是'('时,就表明其后面的必定有其他运算符(当然,不包括用户输入错误的情况,这里不做异常处理,能力有限啊。。。)
所以,我们需要把'('push到Operator中,并且其后的一个运算符也应该被push;当遇到')'就表明有一对括号已经出现,我们就需要把这一对
括号里的表达式计算出来,并且把'('给pop掉至于括号里表达式的计算请看下面的代码,由于还要考虑到括号嵌套的处理,所以我用了循环
来实现:
//'(' and ')' is a special symbol if(expression[i] == '(') { Push_Stack(&Operator,expression[i]); } else if(expression[i] == ')') { //')' is a symbol to end a part of expression,value the result and push it to Oprand Stack GetElem_Stack(&Operator,&TopElemOptr); while(TopElemOptr != '(') { Pop_Stack(&Operator,&PopElemOptr); Pop_Stack(&Operand,&PopElemOpnd1); Pop_Stack(&Operand,&PopElemOpnd2); Push_Stack(&Operand,value(PopElemOpnd1,PopElemOpnd2,PopElemOptr)); GetElem_Stack(&Operator,&TopElemOptr); } if(TopElemOptr == '(') { Pop_Stack(&Operator,&PopElemOptr); } }从代码中,可以知道处理括号嵌套的情况是用循环解决的,每一次都要判断栈顶元素是否是'(',我们只在if语句中考虑了')',并没有
考虑它出现次数,因为只要用户输入正确,有一个'('就必有一个')'剩下,所以只管利用'('判断括号是否还存在于整个表达式中,当循
环完毕之后,还剩下了一个'(',我们就需要人工地给它pop掉
』
(2)表达式结束的判断标识:'\0'和'#'
前面提过我将表达式存在了一个字符串中,当然得先以'\0'判断字符串结束没有,但是当字符串结束时,表达式的计算往往还没有结
束,字符串的结束只是代表着表达式被算了一次,也就是把相邻运算符的优先级相同的给合并了,但是还有不同的留在两个栈中,我
用来作为表达式计算开始标志的'#'作为表达式结束的标识,每计算一个小的表达式,就会pop一个运算符,到最后,肯定会只剩下一
开始就被push的'#',同样利用解决括号嵌套问题的方法来解决这个问题即可。
(3)相邻表达式的计算
这是被切成若干部分的小表达式的计算,它的关键就是通过Judge_Operator函数返回的每两个相邻运算符的优先级比较,运算符遇到
'*'或者'/'时,如果Operator的栈顶元素是'+'或者'-',就表明'*'或者'/'应该被push,如果也是'*'或者'/',也应该把它push,但是在push之前,
应该把栈顶的'/'或'*'弹出,再连续pop出两个Operand中的元素,这三个元素被弹出来再被计算出一个新值,push到操作数栈中,如果
是'(',那么就直接将'*'或者'/'push;如果运算符遇到'+'或者'-',很明显,只有遇到'('时才把它push,遇到其他的运算符,都应该把前面的
表达式计算出来再把它push,我的函数实现里是以返回'<'、'>'和'='来表示的,是一样的。
/********************特别说明*************************/
这里说明一下,这个程序我只实现了整数的算术表达式,源代码同样会放到我的代码共享里
/***********************Bug解决************************/
BUG1:
不能处理这种情况:开头就是 -2+5......等的表达式
解决:
(思想:将-2的表达式变为0.0-2即可)
只要在表达式不是数字及原括号的块里面首先判断:i == 0 &&expression[i] == '-'。
如果满足条件,就先在存放数字的栈中push一个0.0,再把'-'push到存放运算符的栈中。
BUG2:
不能处理这种情况:(-2)这种对负数有特殊要求时表达式
解决:
(思想:将(-2)变为(0.0-2))
同样,在上面解决BUG处的判断语句块后面加上专门解决这种情况的判断语句块。
判断条件:expression[i] == '-' && expression[i-1]== '('
如果满足条件,就0.0先push到存放数字的栈中,再把'-'push到运算符栈中
(解决上述两个BUG的前提是要采用我在上面将的设计思想,就是遇到一对原括号要先把这对括号里面的表达式给算出来再说)