中缀转后缀需要处理的有:
1. 操作数,操作符的提取
2. 括号等关系到运算符优先级的符号
3. 一元操作符(如 +(正), -(负)) 等
4. 操作符和操作数的匹配,括号的匹配,(函数参数的个数是否正确等)
基本思路如下:
用一个链表 List<ExpressionToken> 储存将要生成的后缀表达式
用一个栈 Stack<OperatorType> 储存操作符
判断当前节点, 如果是操作数, 直接加入后缀表达式中, 如果是操作符,则比较前一个操作符和当前操作符的优先级,
如果前一个操作符优先级较高,则将前一个操作符加入后缀表达式中,否则将操作符压入操作符栈,如果遇到反括号 ')', 则在操作符栈中反向搜索,直到遇到匹配的正括号为止,将中间的操作符依次加到后缀表达式中。
举例: 15+25*2, 共有 15, +, 25, *, 2 五个符号,下面一步步列出每一步操作
第一步: 数字加入后缀表达式
第二步:+ 号 压入操作符栈
第三步:
第四步:是一个*号,因为*号的优先级比+号高,所以直接压入操作符栈中
第五步:
第六步:没有符号了,直接将操作符栈中剩余的依次加到表达式中,最终的结果
那假如括号呢?例如 (15 + 25) * 2, 共有7个符号,每一步的操作为
第一步: 左括号直接压入操作符栈
第二步:数字加入表达式中
第三步:+号压入操作符栈
第四步:数字加入表达式中
第五步:遇到反括号了,将两个括号之间操作符依次加入到表达式中,并删除匹配的正括号
第六步:*号压入操作符栈
第七步:数字加入表达式中
第八步:没有符号了,直接将操作符栈中剩余的依次加到表达式中,最终的结果
这样就可以看出优先级对表达式的影响。
依照上述要求, 编写相应的函数:
首先定义几个变量
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#region for

for (int i = 0; i < strExpression.Length; i++)

{
char c = strExpression[i];
if (needDigit && !char.IsDigit(c))
throw new ExpressionException("一元操作符(+,-)后面必须紧跟数字");
if (char.IsWhiteSpace(c))
continue;
//如果前一个操作符是函数,则此处必须为括号
if (needParentheses && c != '(')
throw new ExpressionException("缺少函数的左括号", 1003);
switch (c)

{
//+-即可为二元操作符,也可为一元操作符
case '+':
if (!needOp)

{
//此时,将+号作为一元操作符正号来解析
needDigit = true;
stackOp.Push(OperatorType.Plus);
continue;
}
ProcOperator(i, OperatorType.Plus);
break;
case '-':
if (!needOp)

{
//此时,将-号作为一元操作符负号来解析
needDigit = true;
stackOp.Push(OperatorType.Subtract);
continue;
}
ProcOperator(i, OperatorType.Subtract);
break;
case '*':
ProcOperator(i, OperatorType.MultiPly);
break;
case '/':
ProcOperator(i, OperatorType.Divide);
break;
case '(':
ProcParentheses(i, OperatorType.L_Parentheses);
break;
case ')':
ProcParentheses(i, OperatorType.R_Parentheses);
break;
case ',': //处理逗号, 本节可忽略
ProcComma(i);
break;
default:
//数字
if (char.IsDigit(c))

{
ProcDigit(ref i, ref strExpression);
}
else //其它的字符全当成函数(本节可忽略)

{
ProcFunc(ref i, ref strExpression);
}
break;
}
}
#endregion
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, 中缀转后缀完成了, 再加上前一节的计算后缀表达式,终于可以写一个简单的计算器了。
点击此处
下载
待续。。。。。。。