支持函数,变量的算术表达式计算(二、中缀转后缀)

中缀转后缀需要处理的有:
1. 操作数,操作符的提取
2. 括号等关系到运算符优先级的符号
3. 一元操作符(如 +(正), -(负)) 等
4. 操作符和操作数的匹配,括号的匹配,(函数参数的个数是否正确等)

基本思路如下:
用一个链表 List<ExpressionToken> 储存将要生成的后缀表达式
用一个栈 Stack<OperatorType> 储存操作符
判断当前节点, 如果是操作数, 直接加入后缀表达式中, 如果是操作符,则比较前一个操作符和当前操作符的优先级,
如果前一个操作符优先级较高,则将前一个操作符加入后缀表达式中,否则将操作符压入操作符栈,如果遇到反括号 ')', 则在操作符栈中反向搜索,直到遇到匹配的正括号为止,将中间的操作符依次加到后缀表达式中。
举例:  15+25*2, 共有 15, +, 25, *, 2 五个符号,下面一步步列出每一步操作
第一步: 数字加入后缀表达式
 操作符栈  空
 后缀表达式  15
第二步:+ 号 压入操作符栈
 操作符栈  +
 后缀表达式  15
第三步:
 操作符栈  +
 后缀表达式  15 25
第四步:是一个*号,因为*号的优先级比+号高,所以直接压入操作符栈中
 操作符栈  + *
 后缀表达式  15 25
第五步:
 操作符栈  + *
 后缀表达式  15 25 2
第六步:没有符号了,直接将操作符栈中剩余的依次加到表达式中,最终的结果
 操作符栈  空
 后缀表达式  15 25 2 * +

那假如括号呢?例如 (15 + 25) * 2, 共有7个符号,每一步的操作为
第一步: 左括号直接压入操作符栈
 操作符栈  (
 后缀表达式  空
第二步:数字加入表达式中
 操作符栈  (
 后缀表达式  15
第三步:+号压入操作符栈
 操作符栈  ( +
 后缀表达式  15
第四步:数字加入表达式中
 操作符栈  ( +
 后缀表达式  15 25
第五步:遇到反括号了,将两个括号之间操作符依次加入到表达式中,并删除匹配的正括号
 操作符栈  空
 后缀表达式  15 25 +
第六步:*号压入操作符栈
 操作符栈  *
 后缀表达式  15 25 +
第七步:数字加入表达式中
 操作符栈  *
 后缀表达式  15 25 + 2
第八步:没有符号了,直接将操作符栈中剩余的依次加到表达式中,最终的结果
 操作符栈  空
 后缀表达式  15 25 + 2 *
这样就可以看出优先级对表达式的影响。

依照上述要求, 编写相应的函数:
首先定义几个变量
        bool needOp = false;    //此时需要操作符(如果不是,则说明操作符丢失)
        bool needParentheses = false;//前一个操作符是函数,所以下一个操作符必须是左括号
        bool needDigit = false//前一个操作符是 -(负号),所以下一个操作符必须是数字

1. 操作数的提取
   操作数的提取只需要注意一点,就是正负号的问题(+/-),它们不但可以当作二元操作符,还能当一元操作符, 比如
   15 + -9 ,此时的-号就应该是一元操作符
        /// <summary>
        
/// 操作数
        
/// </summary>
        
/// <param name="index">表达式当前字符的索引</param>
        
/// <param name="expression">当前表达式</param>

        public void ProcDigit(ref int index, ref string expression)
        
{
            
if (needOp)
                
throw new ExpressionException("缺少操作符"1001);

            StringBuilder str 
= new StringBuilder();
            
for (int i = index; i < expression.Length; i++)
            
{
                
char c = expression[i];
                
if (char.IsDigit(c) || c == '.')
                    str.Append(c);
                
else
                
{
                    
break;
                }

                index 
= i;
            }

            needOp 
= true;
            
decimal data = Convert.ToDecimal(str.ToString());
            
//如果前一个是一元操作符(-) 负号,则须对数值进行处理
            if (needDigit)
            
{
                
if (stackOp.Pop() == OperatorType.Subtract)
                    data 
= -data;
                needDigit 
= false;
            }

            lstExp.Add(
new ExpressionToken(TokenType.Numeric
                , data));
        }


2. 操作符的提取
        /// <summary>
        
/// 提取操作符
        
/// </summary>
        
/// <param name="index"></param>
        
/// <param name="op"></param>

        public void ProcOperator(int index, OperatorType op)
        
{
            
if (!needOp)
                
throw new ExpressionException("缺少操作数"1002);
            
if (stackOp.Count > 0)
            
{
                
while (stackOp.Count > 0)
                
{
                    OperatorType opPrev 
= stackOp.Peek();
                    
if (!IsBaseOperator(opPrev) || PRI(opPrev, op) < 0)
                        
break;
                    
//如果前一个操作符的优先级比当前操作符高,则需将前一个操作符
                    
//加入到表达式中,并从操作符栈中清除
                    lstExp.Add(new ExpressionToken(TokenType.Operator, opPrev));
                    stackOp.Pop();
                }

                stackOp.Push(op);
            }

            
else
            
{
                stackOp.Push(op);
            }

            needOp 
= false;
        }

3. 括号的处理
 括号的处理分正括号和反括号两种, 正括号处理比较简单,直接压入操作符栈,反括号就比较麻烦,需要考虑多个因素,代码如下
        /// <summary>
        
/// 括号的处理
        
/// </summary>
        
/// <param name="index"></param>
        
/// <param name="op"></param>

        public void ProcParentheses(int index, OperatorType op)
        
{
            
if (op == OperatorType.L_Parentheses) //左括号
            {
                needOp 
= false;
                needParentheses 
= false;
                stackOp.Push(op);
            }

            
else if (op == OperatorType.R_Parentheses) //右括号
            {
                
bool find = false;
                
//如果是函数,则此处为函数的参数个数
                
//通过统计逗号的个数得出参数的个数
                int funcParam = 1;  
                
while (stackOp.Count > 0)
                
{
                    
if (stackOp.Peek() != OperatorType.L_Parentheses)
                    
{
                        OperatorType opPop 
= stackOp.Pop();
//此处代码用于判断函数参数个数,本节可忽略
                        if (opPop == OperatorType.Comma)
                        
{
                            funcParam
++;
                        }

                        
else
                            lstExp.Add(
new ExpressionToken(TokenType.Operator, opPop));
                    }

                    
else
                    
{
                        stackOp.Pop();
                        find 
= true;
                        
break;
                    }

                }

                
if (!find)
                    
throw new ExpressionException("括号不匹配.(缺少左括号)"1003);
            }

        }


4.比较两个函数的优先级,比较简单

        /// <summary>
        
/// 比较两个操作符的优先级
        
/// </summary>

        public int PRI(OperatorType op1, OperatorType op2)
        
{
            
return (int)op1 - (int)op2;
        }

5. 逐字节解析算术表达式,然后分别调用上述几个函数,具体分析见注释
        /// <summary>
        
/// 将算术表达式转换为逆波兰表达式
        
/// </summary>

        public void ConvertExpression()
        
{
            
for
            
if (!needOp)
            
{
                
//最后一个操作符必须是数字或变量
                throw new ExpressionException("缺少操作数"1002);
            }

            
while (stackOp.Count > 0)
            
{
                
if (stackOp.Peek() == OperatorType.L_Parentheses)
                    
throw new ExpressionException("括号不匹配(缺少右括号)"1004);
                lstExp.Add(
new ExpressionToken(TokenType.Operator, stackOp.Pop()));
            }

        }

OK, 中缀转后缀完成了, 再加上前一节的计算后缀表达式,终于可以写一个简单的计算器了。


点击此处下载

待续。。。。。。。
原文地址:https://www.cnblogs.com/michaelhuwei/p/1023105.html