昨天花了一天的時間弄計算器。也算是做出來了,還是簡易的(懷疑猿生!!)。在此先感謝昨天被我騷擾的朋友。
先貼一張界面看看

其實健壯性還是挺差的,用戶體驗也是極差的。比如說用戶輸入了不合理運算式子,我就直接拋出一個異常完事了,因為要在原來的算法里加判斷實在暈亂。所以趁熱打鐵,希望在寫博客的時候再把思路理理,完善不足。
思路一:
因為計算的是四則混合運算,比如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:關於這些邏輯性比較強的代碼,可能寫過一段時間到回去看就會看不懂了。所以,筆者的經驗是:寫的時候一定要嚴謹,想周到點。寫完如果不確定是否正確,就進行測試,親測幾次沒問題之后就不要管它了,把它縮起來,以后拿來用就好。寫的時候注釋一定要詳細點,萬一要到回去看呢!!!!
