昨天花了一天的时间弄计算器。也算是做出来了,还是简易的(怀疑猿生!!)。在此先感谢昨天被我骚扰的朋友。
先贴一张界面看看
其实健壮性还是挺差的,用户体验也是极差的。比如说用户输入了不合理运算式子,我就直接抛出一个异常完事了,因为要在原来的算法里加判断实在晕乱。所以趁热打铁,希望在写博客的时候再把思路理理,完善不足。
思路一:
因为计算的是四则混合运算,比如2*6-4/(2+3)。我们最开始得到的是一个表达式字符串,计算机是不会帮你计算的。而四则混合运算有优先等级的计算,那么该怎么计算呢?于是问了问度娘,度娘说你可以用逆波兰式计算。于是我二话不说看了看逆波兰式子,果然高明。下面是贴一下逆波兰式计算步骤:
/// <summary> /// 使用逆波兰表示法求四则混合运算 /// 首先,需要两个栈 /// 栈s1用于临时存储运算符(含一个结束符号),此运算符在栈内遵循越往栈顶优先级越高的原则; /// 栈s2用于输入逆波兰式; /// 为方便起见,栈s1需要放入一个优先级最低的运算符,在这里假定为“#”; /// 读取运算式入栈的步骤 /// 1.若x是操作数,则分析出完整的运算数,压入栈s2; /// 2.若x是运算符,则分情况而定: /// 若x是‘(’,则直接压入栈s1 /// 若x是‘)’,则将距离栈s1栈顶的最近的‘(’之间的运算符,逐个出栈,依次压入栈s2,此时抛弃‘(’ /// 若x是除了‘(’和‘)’以外的运算符,则再分如下情况 /// 若当前的栈顶元素是‘(’,则直接将x压入栈s1 /// 若当前的栈顶元素不是‘(’,则将x与栈s1的栈顶元素进行对比,如果优先级比较高,则压入栈,如果优先级低,则把栈s1的栈顶元素弹出压入栈s2,直到栈s1的栈顶元素优先级低于x, /// 或则栈s2的栈顶运算符为‘(’,此时再将x压入栈s1 /// 3.进行完以上操作之后,检查栈s1是否为空,若不为空,则将栈中元素依次弹出并压入栈s2中。 /// </summary>
把上面的2*6-4/(2+3)转成逆波兰式是这样的:-/+324*62。注意,转换完之后的逆波兰式是没有括号的。
思路二:
首先想到的是写一个函数,实现这一转换。但是在写的时候会发现,①会用到判断是否是操作数还是操作符,②以及符号的优先级。因为符号很多,写在一个判断里,代码看起来会很长,所以就先把这两个判断写成函数,以方便使用,增强代码可读性。
①判断是数字还是符号函数如下:
// //判断是操作数还是操作符 // static bool IsNumber(string str) { if (str == "(" || str == ")" || str == "*" || str == "/" || str == "-" || str == "+") return false; else return true; }
②定义优先等级。在这里,我用到的是泛型集合Dictionary。(我上一篇博文提到过这个集合)
// //定义优先等级,数字越大,优先等级越高 // static void DefinePriority() { Dictionary<string, int> dic = new Dictionary<string, int>(); dic.Add("(", 7); dic.Add(")", 6); dic.Add("*", 5); dic.Add("/", 5); dic.Add("-", 4); dic.Add("+", 4); dic.Add("#", 3); }
然后接着写转换逆波兰式的函数:
// //接受一个字符串数组,转逆波兰式子 // public void ReverseToPolish(string[] str) { stack1.Push("#"); //栈1压入# if (str != null) //如果字符串数组不为空,执行判断 { //因为是处理栈堆,很容易出现内存分配读取异常,加一个try catch try { for (int i = 0; i < str.Length; i++) { if (IsNumber(str[i])) //如果是数字,直接压入栈2 stack2.Push(str[i]); else { if (dic[str[i]] == 7) //如果是“(”,直接压入栈1 { stack1.Push(str[i]); } else if (dic[str[i]] == 6) //如果是“)”,将栈顶元素依次压入栈2,直到遇到“(” { while (stack1.Peek() != "(") { stack2.Push(stack1.Pop()); } stack1.Pop(); //移除栈顶元素“(” } else if (dic[str[i]] == 5 || dic[str[i]] == 4) //除了“(”和“)”的情况 { if (stack1.Peek() == "(") //如果栈顶元素是“(”,直接压入栈1 { stack1.Push(str[i]); } //若当前的栈顶元素不是‘(’,则将x与栈s1的栈顶元素进行对比,如果优先级比较高,则压入栈 //如果不是,则把栈s1的栈顶元素弹出压入栈s2,直到栈s1的栈顶元素优先级低于x else { while (dic[str[i]] <= dic[stack1.Peek()]) //如果优先等级不高于栈顶 { stack2.Push(stack1.Pop()); } stack1.Push(str[i]); } }//end else if }//end else }//end for //进行完以上操作,检查栈1是否为“#”,不是,则把栈顶元素依次压入栈2 while (stack1.Peek() != "#") { stack2.Push(stack1.Pop()); } } catch { stack2.Push("0"); } } //检查下是否正确 foreach (var item in stack2) Console.Write(item); Console.WriteLine(); }
下面贴下整个类的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 简易计算器 { class ReversePolish { Dictionary<string, int> dic = new Dictionary<string, int>(); //字典 Stack<string> stack1 = new Stack<string>(); //栈1 Stack<string> stack2 = new Stack<string>(); //栈2 public ReversePolish() { DefinePriority(); } // //接受一个字符串数组,转逆波兰式子 // public void ReverseToPolish(string[] str) { stack1.Push("#"); //栈1压入# if (str != null) //如果字符串数组不为空,执行判断 { //因为是处理栈堆,很容易出现内存分配读取异常,加一个try catch try { for (int i = 0; i < str.Length; i++) { if (IsNumber(str[i])) //如果是数字,直接压入栈2 stack2.Push(str[i]); else { if (dic[str[i]] == 7) //如果是“(”,直接压入栈1 { stack1.Push(str[i]); } else if (dic[str[i]] == 6) //如果是“)”,将栈顶元素依次压入栈2,直到遇到“(” { while (stack1.Peek() != "(") { stack2.Push(stack1.Pop()); } stack1.Pop(); //移除栈顶元素“(” } else if (dic[str[i]] == 5 || dic[str[i]] == 4) //除了“(”和“)”的情况 { if (stack1.Peek() == "(") //如果栈顶元素是“(”,直接压入栈1 { stack1.Push(str[i]); } //若当前的栈顶元素不是‘(’,则将x与栈s1的栈顶元素进行对比,如果优先级比较高,则压入栈 //如果不是,则把栈s1的栈顶元素弹出压入栈s2,直到栈s1的栈顶元素优先级低于x else { while (dic[str[i]] <= dic[stack1.Peek()]) //如果优先等级不高于栈顶 { stack2.Push(stack1.Pop()); } stack1.Push(str[i]); } }//end else if }//end else }//end for //进行完以上操作,检查栈1是否为“#”,不是,则把栈顶元素依次压入栈2 while (stack1.Peek() != "#") { stack2.Push(stack1.Pop()); } } catch { stack2.Push("0"); } } //检查下是否正确 foreach (var item in stack2) Console.Write(item); Console.WriteLine(); } // //判断是操作数还是操作符 // private bool IsNumber(string str) { if (str == "(" || str == ")" || str == "*" || str == "/" || str == "-" || str == "+") return false; else return true; } // //定义优先等级,数字越大,优先等级越高 // private void DefinePriority() { dic.Add("(", 7); dic.Add(")", 6); dic.Add("*", 5); dic.Add("/", 5); dic.Add("-", 4); dic.Add("+", 4); dic.Add("#", 3); } } }
笔者已经检测过,逻辑是没有问题的。总之我真的是写了很久,因为在写的时候会遇到如下的问题:就是当接受了加括号的一元运算符比如:1+(-2)。转换得到的式子是不能正确计算的。
下次的博文我会分享解决上述问题的方法以及笔者自己的关于如何判断用户是否输入正确的式子的方法
Tip:关于这些逻辑性比较强的代码,可能写过一段时间到回去看就会看不懂了。所以,笔者的经验是:写的时候一定要严谨,想周到点。写完如果不确定是否正确,就进行测试,亲测几次没问题之后就不要管它了,把它缩起来,以后拿来用就好。写的时候注释一定要详细点,万一要到回去看呢!!!!