這次所講的是又一個棧的應用:算術表達式
這個程序是棧的應用的典型例子,是很好的棧的詮釋,這個程序在K&R的書上也被叫做逆波蘭計算器
/**********算術表達式的設計思想***********/
(1)首先,根據四則運算表達式,我們要先了解到其計算規則:括號爲界限符,其優先率最高,其次爲運算符*和/,最後爲+和-;
(2)然後,我們根據把一個算術表達式劃分爲:開始('#')、運算符(加減乘除)、操作數(數值)和界限符(一對原括號)的方法,創建兩個棧,
一個爲Operator(運算符)棧,另一個爲Operand(操作數)棧
『
<1>先將'#'push到Operator中,它將作爲算術表達式的開始和結束標誌
<2>我們依次把算術表達式的字符串給push如棧中,遇到數字時,就把它push到Operand中,這個比較好做,但是當遇到運算符時,我
們需要幾個判斷,後面會有介紹
<3>原括號的特殊處理(後面會說到)
<4>最後想說的是,我是先將整個表達式置於一個字符串中,整個計算的條件就是通過判斷這個字符串有沒有結束,如果到了最後,再通過
最開始我所說的'#'開始標誌作爲表達式的結束標誌,來計算最後的結果(這是一個特殊處理,後面會說)
』
/************程序實現的幾個細節***********/
我們這樣就已經把這個程序的大致框架給描繪出來了,下面來說說實現這個程序的時候需要注意到的一些細節:
(棧的基本功能的實現就飄過了~~~)
(1)首先是運算符要被push或pop時的一些比較。
這是整個程序設計中最關鍵的步驟之一,我們給這個功能命名爲Judge_Operator。有了最開始基本設計思想的講述,這段代碼應該比
較好實現了,這裏要注意到是,我並沒有把'('或着')'納入判斷中,因爲我把原括號視爲了一種特殊字符,當有圓括號出現時,就應該讓
原括號裏面的表達式獨立出來,也就是讓這個表達式單獨進入一個新的循環進行這個表達式的求值,直到原括號消失在整個算術表達
式中,具體方法是這樣的:
『
運算符中當是'('時,就表明其後面的必定有其他運算符(當然,不包括用戶輸入錯誤的情況,這裏不做異常處理,能力有限啊。。。)
所以,我們需要把'('push到Operator中,並且其後的一個運算符也應該被push;當遇到')'就表明有一對括號已經出現,我們就需要把這一對
括號裏的表達式計算出來,並且把'('給pop掉至於括號裏表達式的計算請看下面的代碼,由於還要考慮到括號嵌套的處理,所以我用了循環
來實現:
//'(' and ')' is a special symbol
if(expression[i] == '(')
{
Push_Stack(&Operator,expression[i]);
}
else if(expression[i] == ')')
{
//')' is a symbol to end a part of expression,value the result and push it to Oprand Stack
GetElem_Stack(&Operator,&TopElemOptr);
while(TopElemOptr != '(')
{
Pop_Stack(&Operator,&PopElemOptr);
Pop_Stack(&Operand,&PopElemOpnd1);
Pop_Stack(&Operand,&PopElemOpnd2);
Push_Stack(&Operand,value(PopElemOpnd1,PopElemOpnd2,PopElemOptr));
GetElem_Stack(&Operator,&TopElemOptr);
}
if(TopElemOptr == '(')
{
Pop_Stack(&Operator,&PopElemOptr);
}
}
從代碼中,可以知道處理括號嵌套的情況是用循環解決的,每一次都要判斷棧頂元素是否是'(',我們只在if語句中考慮了')',並沒有
考慮它出現次數,因爲只要用戶輸入正確,有一個'('就必有一個')'剩下,所以只管利用'('判斷括號是否還存在於整個表達式中,當循
環完畢之後,還剩下了一個'(',我們就需要人工地給它pop掉
』
(2)表達式結束的判斷標識:'\0'和'#'
前面提過我將表達式存在了一個字符串中,當然得先以'\0'判斷字符串結束沒有,但是當字符串結束時,表達式的計算往往還沒有結
束,字符串的結束只是代表着表達式被算了一次,也就是把相鄰運算符的優先級相同的給合併了,但是還有不同的留在兩個棧中,我
用來作爲表達式計算開始標誌的'#'作爲表達式結束的標識,每計算一個小的表達式,就會pop一個運算符,到最後,肯定會只剩下一
開始就被push的'#',同樣利用解決括號嵌套問題的方法來解決這個問題即可。
(3)相鄰表達式的計算
這是被切成若干部分的小表達式的計算,它的關鍵就是通過Judge_Operator函數返回的每兩個相鄰運算符的優先級比較,運算符遇到
'*'或者'/'時,如果Operator的棧頂元素是'+'或者'-',就表明'*'或者'/'應該被push,如果也是'*'或者'/',也應該把它push,但是在push之前,
應該把棧頂的'/'或'*'彈出,再連續pop出兩個Operand中的元素,這三個元素被彈出來再被計算出一個新值,push到操作數棧中,如果
是'(',那麼就直接將'*'或者'/'push;如果運算符遇到'+'或者'-',很明顯,只有遇到'('時才把它push,遇到其他的運算符,都應該把前面的
表達式計算出來再把它push,我的函數實現裏是以返回'<'、'>'和'='來表示的,是一樣的。
/********************特別說明*************************/
這裏說明一下,這個程序我只實現了整數的算術表達式,源代碼同樣會放到我的代碼共享裏
/***********************Bug解決************************/
BUG1:
不能處理這種情況:開頭就是 -2+5......等的表達式
解決:
(思想:將-2的表達式變爲0.0-2即可)
只要在表達式不是數字及原括號的塊裏面首先判斷:i == 0 &&expression[i] == '-'。
如果滿足條件,就先在存放數字的棧中push一個0.0,再把'-'push到存放運算符的棧中。
BUG2:
不能處理這種情況:(-2)這種對負數有特殊要求時表達式
解決:
(思想:將(-2)變爲(0.0-2))
同樣,在上面解決BUG處的判斷語句塊後面加上專門解決這種情況的判斷語句塊。
判斷條件:expression[i] == '-' && expression[i-1]== '('
如果滿足條件,就0.0先push到存放數字的棧中,再把'-'push到運算符棧中
(解決上述兩個BUG的前提是要採用我在上面將的設計思想,就是遇到一對原括號要先把這對括號裏面的表達式給算出來再說)