原文地址:蘋果梨的博客html
今天要給咱們的「計算器」加上乘、除和取模三種運算,而且加上對括號的優先級處理。git
若是不是採用遞歸方式解析表達式的話,能夠參考下調度場算法,這是一個利用隊列和堆棧來解決計算優先級的經典算法。github
用遞歸方式解析的話,只要深入理解了上一章的知識,這一章的都是小意思,那麼咱們開始。算法
首先咱們列出乘除法的文法:bash
term -> term '*' num | term '/' num | num
num -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
複製代碼
這裏的term
是項的意思,是指在加減法裏運算符左右的兩個項,先不要糾結具體是什麼意思,一會就會用到了。函數
咱們以幾個例子來人肉分析一下,這個文法和加減法的文法怎麼結合,才能得到正確的優先級:spa
expr(1+2*3) = num(1) '+' term(2*3)
expr(1*2+3) = term(1*2) '+' num(3)
expr(1*2+3*4) = term(1*2) '+' term(3*4)
複製代碼
而在上面乘除法的文法裏能夠看到,實際上term
是能夠推導爲num
的,因此上述例子又能夠變成:指針
expr(1+2*3) = term(1) '+' term(2*3)
expr(1*2+3) = term(1*2) '+' term(3)
expr(1*2+3*4) = term(1*2) '+' term(3*4)
複製代碼
是否是寫到這裏就豁然開朗了,若是乘除法的優先級更高,則讓乘除法的解析先進行,乘除法的結果當作加減法的項就能夠了。在文法裏的表現就是,優先級越高的產生式會越靠後,結果是這樣:code
expr -> expr '+' term | expr '-' term | term
term -> term '*' num | term '/' num | num
num -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
複製代碼
具體的驗證你們本身試試就能夠了,絕對能夠按照正確的優先級進行解析。htm
文法已經準備好了,原本能夠開始寫遞歸邏輯了,可是咱們得先解決前一章代碼的隱患。先看看以前的代碼:
int expr(const char *expStr)
{
解析number,讀取expStr下一個字符
while(expStr是加減號) {
解析加減號,讀取expStr下一個字符
解析number,讀取expStr下一個字符
}
}
複製代碼
看上去邏輯是沒什麼問題的,可是這是由於number
確定是單個字符的,若是按照以前的代碼繼續實現本章的文法,就會獲得這樣的難題:
int expr(const char *expStr)
{
解析term,該讀取expStr後的第幾個字符?
while(expStr是加減號) {
解析加減號,讀取expStr下一個字符
解析term,該讀取expStr後的第幾個字符?
}
}
複製代碼
因此將字符串指針後移的操做,應該交給解析了字符或者說消化掉字符的角色來作,那麼咱們要用指針的指針來先改進一下上一章的代碼:
int number(const char **expStr) {
int result = **expStr - '0';
(*expStr)++;
return result;
}
int expr(const char **expStr) {
int result = number(expStr);
while (**expStr == '+' || **expStr == '-') {
char op = **expStr;
(*expStr)++;
if (op == '+') {
result += number(expStr);
} else {
result -= number(expStr);
}
}
return result;
}
複製代碼
你們有了文法,也掌握了消除左遞歸的方法,還知道怎麼轉換成遞歸代碼,我感受其實都不太須要把代碼給你們貼出來了。😂
乘除法的實現思路和上一章加減法的思路徹底一致,咱們順帶把取餘也加上就行了:
int term(const char **expStr) {
int result = number(expStr);
while (**expStr == '*' || **expStr == '/' || **expStr == '%') {
char op = **expStr;
(*expStr)++;
if (op == '*') {
result *= number(expStr);
} else if (op == '/') {
result /= number(expStr);
} else {
result %= number(expStr);
}
}
return result;
}
複製代碼
下一步就是把加減法裏的number
解析都換成term
解析,換完以後的expr
函數會長這樣:
int expr(const char **expStr) {
int result = term(expStr);
while (**expStr == '+' || **expStr == '-') {
char op = **expStr;
(*expStr)++;
if (op == '+') {
result += term(expStr);
} else {
result -= term(expStr);
}
}
return result;
}
複製代碼
實驗下,確認咱們的表達式解析器能夠按照正確的優先級計算加減乘除了。
括號的運算優先級是比乘除法還要高的,因此咱們新增一個非終結符factor
(因子),用來在文法中描述它:
expr -> expr '+' term | expr '-' term | term
term -> term '*' factor | term '/' factor | factor
factor -> number | '(' expr ')'
number -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
複製代碼
嘿嘿,此次就真的不給你們貼代碼了,相信此次你們應該能夠熟練的搞定代碼,畢竟已是個成熟的程序猿了。
完整的代碼存放在SlimeExpressionC,須要的同窗能夠自取,今天的入門課就到這裏。