最近要写一个工资管理软件,不可避免的要用到公式的定义及求值等问题。对于数学表达式的计算,虽然也有直接对表达式进行扫描并按照优先级逐步计算的方法,但感觉还是不如将中缀表达式转换为逆波兰表达式更容易处理。
使用逆波兰表达式,则有以下几件工作需要去做:
1.对中缀表达式进行语法分析,或称合法性检查。
2.将中缀表达式转换为逆波兰表达式。
3.计算逆波兰表达得到我们想要的值。
以下是我为实现该功能而写的一个简单的类:
using System;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;

namespace Seaking


{
public class RpnExpression

{

检查中缀表达式的合法性#region 检查中缀表达式的合法性

/**//// <summary>
/// 检查中缀表达式是否合法。
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
public static bool IsRight(string exp)

{
string pMatch=@"\([^\(^\)]+\)";//匹配最“内”层括号及表达式
string numberMatch=@"\d+(\.\d+)?";//匹配数字
string exMatch=@"^0([-+*/]0)*$";//匹配无括号的、用0替换所有的数字后的表达式
exp=Regex.Replace(exp,numberMatch,"0");//为简化检测,用0替换所有的数字
while(Regex.IsMatch(exp,pMatch))

{
foreach(Match match in Regex.Matches(exp,pMatch))

{
string tmp=match.Value;
tmp=tmp.Substring(1,tmp.Length-2);//去掉 "("和 ")"
if(!Regex.IsMatch(tmp,exMatch)) return false;
}
exp=Regex.Replace(exp,pMatch,"0");//将最内层的括号及括号内表达式直接用一个0代替
}

return Regex.IsMatch(exp,exMatch);
}
#endregion


生成逆波兰表达式#region 生成逆波兰表达式

/**//// <summary>
/// 获取逆波兰表达式。
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
public static string RpnExp(string exp)

{
if(!IsRight(exp)) throw new ApplicationException("非法的中缀表达式。");
Stack skOp=new Stack();//定义操作符堆栈
StringBuilder rpn=new StringBuilder();//逆波兰表达式
char[] charExp=exp.ToCharArray();//将中缀表达式转换为char数组
string digit=string.Empty;//数字字符串

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

{
char chr=charExp[i];

if(char.IsDigit(chr) || chr=='.')//如果是数字或小数点,添加到数字字符串中

{
digit+=chr;
}
else if("+-*/".IndexOf(chr)>=0)//如果是运算符

{
if(digit.Length>0)

{
rpn.Append("<" + digit + ">");//首先将数字添加到逆波兰表达式
digit=string.Empty;
}

//弹出操作符并添加到逆波兰表达式,直至遇到左括号或优先级较低的操作符
while(skOp.Count>0)

{
char opInStack=(char)skOp.Pop();
if(opInStack=='(' || Power(opInStack)<Power(chr))

{
skOp.Push(opInStack);
break;
}
else

{
rpn.Append(opInStack);
}
}

skOp.Push(chr);//将当前操作符压入堆栈中
}
else if(chr=='(')//遇到左括号,直接压入堆栈中

{
skOp.Push(chr);
}
else if(chr==')')//遇到右括号

{
if(digit.Length>0)//先将数字添加到逆波兰表达式

{
rpn.Append("<" + digit + ">");
digit=string.Empty;
}

while(skOp.Count>0)//弹出运算符并添加到逆波兰表达式,直至遇到左括号

{
char opInStack=(char)skOp.Pop();
if(opInStack=='(')

{
break;
}
else

{
rpn.Append(opInStack);
}
}
}
}

//到达字符串末尾后,首先将数字添加到逆波兰表达式
if(digit.Length>0)

{
rpn.Append("<" + digit + ">");
}
//弹出所有操作符并添加到逆波兰表达式
while(skOp.Count>0)

{
char opInStack=(char)skOp.Pop();
rpn.Append(opInStack);
}

return rpn.ToString();
}


/**//// <summary>
/// 获取操作符的优先级。
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
private static int Power(char o)

{
switch(o)

{
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
#endregion


计算逆波兰表达式的值#region 计算逆波兰表达式的值

/**//// <summary>
/// 获取中缀表达式的值。
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
public static double GetValue(string exp)

{
return GetValueByRpn(RpnExp(exp));
}


/**//// <summary>
/// 获取逆波兰表达式的值。
/// </summary>
/// <param name="rpnExp"></param>
/// <returns></returns>
public static double GetValueByRpn(string rpnExp)

{
Stack stack=new Stack();
char[] expChar=rpnExp.ToCharArray();
string digit=string.Empty;
double result=0;
for(int i=0;i<expChar.Length;i++)

{
char c=expChar[i];
if(c=='<')

{
digit=string.Empty;
}
else if(c=='>')

{
stack.Push(digit);
}
else if(char.IsDigit(c) || c=='.')

{
digit+=c.ToString();
}
else if(c=='+' || c=='-' || c=='*' || c=='/')

{
double d2=Convert.ToDouble(stack.Pop());
double d1=Convert.ToDouble(stack.Pop());
result=math(d1,d2,c);
stack.Push(result);
}
}
return result;
}


/**//// <summary>
/// 四则运算。
/// </summary>
/// <param name="d1"></param>
/// <param name="d2"></param>
/// <param name="o"></param>
/// <returns></returns>
private static double math(double d1,double d2,char o)

{
switch(o)

{
case '+':
return d1+d2;
case '-':
return d1-d2;
case '*':
return d1*d2;
case '/':
return d1/d2;
default:
return 0d;
}
}
#endregion
}
}
由于只是工资计算,该类只支持+-×/ 四种运算,而且不支持负数(太麻烦,呵呵),有兴趣的朋友可以自己扩充一下。