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; //前一个操作符是 -(负号),所以下一个操作符必须是数字
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));
}
/// 操作数
/// </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;
}
/// 提取操作符
/// </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);
}
}
/// 括号的处理
/// </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;
}
/// 比较两个操作符的优先级
/// </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()));
}
}
/// 将算术表达式转换为逆波兰表达式
/// </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, 中缀转后缀完成了, 再加上前一节的计算后缀表达式,终于可以写一个简单的计算器了。
点击此处下载
待续。。。。。。。