由於工作需要最近在研究PHP擴展,無可避免的涉及到了C語言。從出了學校以后C語言在實際工作中還沒有用到過,所以必須要先進行一點復習工作。個人認為對於熟悉一樣東西說最好的方法是上手實踐。於是便想起了當時大學的時候老師布置過的一道題目,用C語言實現簡單數學表達式的分析和求值,比較遺憾的是當初沒能把題目完成。就想着從新試一試,算是補一下當初的作業。
還記得當初的思路是,循環C字符串。用鏈表將不同的計算項存儲到鏈表中。然后在進行循環求值。如果遇到括號就遞歸調用。回憶並整理了一下當初的思路大致如下。
1.輸入 3+5*(2-6)/2
2.解析為

3.計算 通過兩次循環對不同優先級進行運算得出結果
第一次對*/進行計算,拿當前節點,和節點的next節點,求值后將值賦給next節點,並刪除自身節點
第二次對+-進行求值,直到遇到end
最初也打算按這個思路去實現的,之后發現解析的步驟會比較復雜,涉及到多次的字符串搜索,比對。再加上C本身不支持正則表達式,所以就放棄了當初的思路。找了一些相關的資料后發現了一種更簡單也更科學的方法,那就是將輸入轉換成后綴表達式再進行求值,這后綴表達式究竟是什么呢,請繼續看下去。
一、后綴表達式
百度百科介紹: 不包含括號,運算符放在兩個運算對象的后面,所有的計算按運算符出現的順序,嚴格從左向右進行(不再考慮運算符的優先規則,如:(2 + 1) * 3 , 即2 1 + 3 *
了解了后綴表達式才知道,原來我們習以為常的數學表達式被稱之為中綴表達式。花了點時間研究了一下發現后綴表達式的計算還蠻簡單的,也更符合計算機運算。
計算方法,從左往右進行計算。取運算符號前兩位數字進行運算,運算結果替代運算符以及前兩位數字,持續運算到最右邊得出結果。
這么說起來可能比較難理解,我們看幾個例子
-
最簡單的 21+
-
計算過程為 2 + 1 = 3
-
值為 3
-
普通的 325-2*+
-
計算過程 從左往右先計算 2-5=-3,-3取代前面的25-之后為 3-32*+,完整的計算步驟如下
-
計算2-5=3 計算完之后表達式為 3 -3 2 *+
-
計算-3*2=-6 計算完之后表達式為 3 -6 +
-
計算3+-6=-3 計算完之后表達式為 -3
-
值為 -3
-
稍微復雜一點的 21+3*5387-/*-
-
計算過程 從左往右先計算 2+1 = 3,4取代前面的21+之后為 33*5387-/*- 完整的計算步驟如下
-
計算2+1=3 計算完之后表達式為 3 3 *5 3 8 7 -/*-
-
計算3*3=9 計算完之后表達式為 9 5 3 8 7 -/*-
-
計算8-7=1 計算完之后表達式為 9 5 3 1 /*-
-
計算3/1=3 計算完之后表達式為 9 5 3 *-
-
計算5*3=15 計算完之后表達式為 9 15 -
-
計算9-15=-6 結果為 -6
-
值為 -6
看完了以上結果,我們會發現每一次參與計算的數字,都是最靠近運算符號的兩位數字。然后由運算出來的結果代替參與運算的數字和運算符,直到表達式只剩下一個值,計算完成。
根據這樣的規律,程序處理起來就簡單了。
1.從左往右的循環整個輸入。
2.判斷是否是數字,如果是數字就保存起來。如果遇到符號,則把保存的前兩個值取出來,計算后把本次計算結果存回去
3.循環完成之后,剩下的表達式便是計算結果了
根據如上規則不難發現每次參與計算的兩個數字都是最后存進去的,這樣一來我們便可以用棧輕松的完成這樣一個程序了,下面跟大家簡單介紹一下棧。
二、棧
百度百科的解釋比較復雜,就不摘抄了。其實棧可以簡單的理解為一個存放數據的空間,數據按照后進先出的原則進行存取。對數據的操作有push和pop,分別稱之為壓入,彈出。
由於比較簡單,所以直接用代碼實現了一個簡單的棧,包含如下四個方法。


簡單的進行測試,輸入結果為
item is : 1.120000
item is : 2.800000
2.800000
1.120000
實現了預期輸出,一個簡單的棧就搞定了,接下來就可以利用整個簡單的棧來完成求值的函數了。
三、后綴表達式求的具體實現
由於棧已經實現了,所以只需要按照后綴表達式求值的邏輯進行運算在配合棧就可以實現整個計算過程了。方法比較簡單,用while循環整字符串,在配合switch對數字和運算符做不同的處理就能夠完成一個簡單的后綴表達式求值函數了。以下是第一版的實現代碼


在第一個版本的過程中,遇到一個C語言知識點是 C語言的字符串指針指向的地址是字符串第一個字符的地址。
所以當i為0的時候,&str[i] = &str, atof 接收的是一個字符串指針,如果使用 STACKpush(atof(&str[i])),i為0時,會將整個字符串傳入進去轉換。采用了一個char變量,講str[i]拷貝出來,然后傳入&num,則可以解決這個問題。
其實這段程序里還涉及到一個指針運算的知識點,但是這里的程序里涉及還比較簡單易懂,后續還有更難的地方涉及到這個知識點,所以先放到后面再跟大家分享。
對上面的方法進行了測試,輸入我們之前分析的三個表達式,得出結果如下
printf("%f\n", calculate("21+")); //3
printf("%f\n", calculate("325-2*+")); //-3
printf("%f\n", calculate("21+3*5387-/*-")); //-6
測試通過,跟之前的計算結果一直。以上便是一個簡單的后綴表達式的計算程序了。進行了多幾次的測試發現了一個小問題,就是目前無法進行多位數的識別。因為程序沒一次都將一位數壓入站內了。思考了一下在表達式的每個計算項上加了一個空格符作為數字的區分。變為 21 3 +這種形式,於是動手將代碼做了一點小改動
在 switch 中加入了對空格的處理,以及多位數的處理
//如果遇到空格,則重置標志位

這樣一來就可以識別多位數了。經測試
printf("%f\n", calculate("21 1+")); //22
printf("%f\n", calculate("2 11+")); //13
得到了正確的結果,整個簡單計算器的第一步計算后綴表達式完成。
有興趣的可以測試一下上的代碼,如果遇到什么問題可以通過微信公眾號反饋給我。
當然這個程序還有一些有待完善的地方。如表達式合法性檢查,對小數的處理,以及對負數的處理等,暫時先預留着后續再跟大家分享如何實現以及會用到的知識點。
下一篇將為大家介紹簡單數學表達式計算的第二步,如何實現將中綴表達式轉換為后綴表達式。
歡迎大家關注微信公眾號~

