在前面的学习之中,我感觉对这个问题理解得不够透彻,随着一次又一次的理解,越来越明了了,所以再整理一下。
使用栈进行表达式计算
表达式计算,就是所谓的我们生活中常见的
1+5*(45+6)
这类的式子
表达式计算的核心原理就是 ****中缀表达式转后缀表达式***,在我们平时见到的计算式中我们遇到的都是中缀表达式,例如 “1+45(3-2)” ,后缀表达式看起来陌生但是我们在计算起来采用的都是后缀表达式,根据运算符号的优先级,我们可以将前式转换成后缀表达式:“1 45 3 2 - * + ” 在处理后缀表达式表达式遵循的规律为 遇到符号,将符号前两个数进行符号所对应的运算。
中缀表达式和后缀表达式的转换
当然上面只是解决大概,还有很多细节待处理,中缀转后缀表达式需要遵守一定的规则,这是由运算符的优先级来和原表达式书写顺序决定,这一步可以由栈完成,处理方法是:(下面是中缀表达式转后缀表达式的算法描述)
我们准备一个栈来处理符号
-
逐个按中缀表达式的顺序处理每一个元素(符号和数)
-
遇到数值直接入追加到后缀表达式的尾部
-
遇到符号跟当前栈顶元素(当栈为空时,那么当前符号优先级大),如果栈顶符号优先级大,符号出栈追加到后缀表达式的尾,反之追加到后缀表达式的尾
-
重复123的过程直到中缀表达式字串处理完毕
优先级的表达
优先级,如何在计算机体现呢?我们是这样处理的 “从左到右 先乘除 后加减,先算括号里面的”,这句话其实无法完全反应优先级!我们再仔细分析一下我们日常计算的表达式中的优先级:
****计算先从左计算到右****:这句话可以描述成,****同一个运算符优先级也分左右,右运算符大于左运算符(相对操作数左右而言)****,比如1+2+3 ,我们是先计算1+1呢还是先计算2+3?答案是先计算1+2,为啥?其实这里旧隐藏了一个问题,同一个运算符也分左右,1+2中的+优先级比2+3中的高。(注:这里严格按照从左到右计算,无视交换律和结合律)
****去括号,先处理括号内部的运算****:其实这里也好理解,****让左括号的优先级比任何符号的优先级别高,同时让左括号的优先级小于右括号****,这样就可以优先计算括号内部的了。但是这里仅仅做到了优先计算括号内的,但是括号有没有对应的计算,我们需要将它去除。****其实将括号作为一个特例,左括号和右括号优先级相等,并且左括号不进入符号栈****,这样如果我们栈顶元素是右括号的时候,表明括号内部的已经优先处理,所以’(’出栈,不追加到后缀表达式尾。
先乘除再加减,这个就是本来的意即可。
至于前面所说的优先级,如何使用C语言表达出来呢?If or case语句?这一样像的太复杂了,我们完全可以使用数组保存一个对照表即可,如下
const char Prior[7][7] = {
// + - * / ( ) #
{'>','>','<','<','<','>','>'},//+
{'>','>','<','<','<','>','>'},//-
{'>','>','>','>','<','>','>'},//*
{'>','>','>','>','<','>','>'},// /
{'<','<','<','<','<','=','$'},//(
{'>','>','>','>','$','>','>'},//)
{'<','<','<','<','<','$','='}// #
};
确定表达式遍历已经结束
确定表达已经结束,简单的办法就是是否再处理\0
,当然这个不是最优的方案?为了而我们能够处理“)5+1”这类的错误,我们在****中缀表达式开始和结尾加上 #
作为休止符。****我们在处理优先级的时候规定 #比所有符号的优先级低,这样就不会干扰符号优先级,#与优先级相等,当我们遇到栈顶为#遍历符号为#的时候说明表达式已经结束。为了能够处理“)5+1”简单一点可以规定 #遇到)比较就出错。
浮点数字串转浮点数
其实这个调用C语言的函数atoi()
即可,我们只需找到这个浮点数的开始和结束即可
错误处理
比如1/0 和“+1=%2”这类错误,我们可以使用c++的try catch语句抛出对应的错误即可。
实际流程
当然上述说明的是后缀表达式和中缀表达式的转化,当然这个方法很多,比如使用而二叉树就可以做到。在实际计算中,我们无需转换之后进行计算,我们完全可以边转换再计算:
- ****栈顶符号优先级大****: 新的操作符 没有旧的运算符高的时候 ,数据栈出栈提取两个操作数 进行旧的操作符运算 并且结果入数据栈 新符号入栈
- *栈顶符号优先级小* 符号入符栈
- *栈顶符号优先级等* 两个括号相遇 去括号 或者 表达运算结束
- 当栈顶和遍历符号都为# 表达计算完成
- 错误处理
具体代码
double expreetor(std::string str)throw (Exception)
{
stack<int> OPTR; //符号栈
stack<double> OPND;// 数值栈
OPTR.push(isOperator('#'));//符号栈存入起始符
str.push_back('#');//插入截至符号
double result = 0; //结果
int i = 0, op1 = -1, op2 = -1;//遍历字串 和 新操作符 旧操作符
char c = str[0];
double num1 = 0, num2 = 0;//操作数
std::string num;
while (c != '#'||OPTR.top()!=6)//表达没有结束
{
if (c >= '0' && c <= '9')//得到操作数
{
while ((c >= '0' && c <= '9') || c == '.')
{
num.push_back(c);
c = str[++i];
}
OPND.push(atof(num.c_str()));
num.clear();
}
else//对符号进行处理
{
//取出操作符
op1 = isOperator(c);
op2 = OPTR.top();
switch (pritor(op2, op1))
{
case '<'://符号入符栈 并且 处理下一个字符
OPTR.push(op1);
c = str[++i];
break;
case '='://取括号 符号栈出栈 并且 处理下一个字符
OPTR.pop();
c = str[++i];
break;
case '>'://数据栈出栈提取两个操作数 进行旧的操作符运算 并且结果入数据栈 新符号入栈
if (OPND.size() < 2 || OPTR.size() < 1)
{
Exception e("表示式有误\n");
throw e;
}
num1 = OPND.top();
OPND.pop();
num2 = OPND.top();
OPND.pop();
op2 = OPTR.top();
OPTR.pop();
result = cctor(op2, num2, num1);//注意!!num2 / - num1 取出的顺序式反的
OPND.push(result);
break;
default:
Exception e("表示式有误\n");
throw e;
break;
}//switch
}//else
}
return OPND.top();
}