實現函數 double evaluate(const char *expr),輸入合法算術表達式字符串 (如 "(1+2)*3-4"),支持運算符 + - * / ( ),操作數為非負整數,返回計算結果 (double)。
* / > + -1-2-3 = (1-2)-3"123" → 123文法定義 (EBNF):
expression = term { ('+' | '-') term } ;
term = factor { ('*' | '/') factor } ;
factor = number | '(' expression ')' ;
number = digit { digit } ;
每條產生式對應一個函數,自然體現優先級。
#include
#include
#include
typedef struct {
const char *str;
int pos;
} Parser;
double parse_expression(Parser *p);
double parse_term(Parser *p);
double parse_factor(Parser *p);
double parse_number(Parser *p);
void skip_spaces(Parser *p) {
while (p->str[p->pos] && isspace(p->str[p->pos])) p->pos++;
}
double parse_number(Parser *p) {
skip_spaces(p);
double val = 0;
int start = p->pos;
while (isdigit(p->str[p->pos])) {
val = val * 10 + (p->str[p->pos] - '0');
p->pos++;
}
if (p->pos == start) { fprintf(stderr, "Error: Expected number at pos %d\n", p->pos); exit(1); }
return val;
}
double parse_factor(Parser *p) {
skip_spaces(p);
if (p->str[p->pos] == '(') {
p->pos++; // consume '('
double val = parse_expression(p);
skip_spaces(p);
if (p->str[p->pos] != ')') { fprintf(stderr, "Error: Missing ')' at pos %d\n", p->pos); exit(1); }
p->pos++; // consume ')'
return val;
}
return parse_number(p);
}
double parse_term(Parser *p) {
double val = parse_factor(p);
while (1) {
skip_spaces(p);
char op = p->str[p->pos];
if (op != '*' && op != '/') break;
p->pos++;
double rhs = parse_factor(p);
if (op == '*') val *= rhs;
else {
if (rhs == 0) { fprintf(stderr, "Error: Division by zero\n"); exit(1); }
val /= rhs;
}
}
return val;
}
double parse_expression(Parser *p) {
double val = parse_term(p);
while (1) {
skip_spaces(p);
char op = p->str[p->pos];
if (op != '+' && op != '-') break;
p->pos++;
double rhs = parse_term(p);
if (op == '+') val += rhs;
else val -= rhs;
}
return val;
}
double evaluate(const char *expr) {
Parser p = {expr, 0};
double result = parse_expression(&p);
skip_spaces(&p);
if (p.str[p.pos] != '\0') { fprintf(stderr, "Error: Unexpected char '%c' at pos %d\n", p.str[p.pos], p.pos); exit(1); }
return result;
}
int main() {
printf("%f\n", evaluate("(1+2)*3-4")); // 5.000000
printf("%f\n", evaluate("10/3")); // 3.333333
printf("%f\n", evaluate("2*(3+4)/5")); // 2.800000
return 0;
}
適合需顯式構建 AST、支持更多運算符/函數的場景。
// 核心邏輯 (偽代碼)
輸出隊列: 數值
運算符棧: 運算符
掃描 token:
若數值 -> 輸出隊列
若運算符 o1:
while 棧頂運算符 o2 且 (o2 優先級 > o1 或 (同級且左結合) 且 o2 != '('):
弹出 o2 到輸出隊列
壓入 o1
若 '(' -> 壓入棧
若 ')' -> 弹出運算符到輸出隊列 直到遇到 '(',彈出 '('
掃描結束 -> 彈出剩餘運算符到輸出隊列
// 後綴求值
掃描後綴 token:
若數值 -> 壓值棧
若運算符 -> 彈出兩值 計算 壓回結果
最終值棧頂即結果
| 維度 | 遞歸下降 | Shunting Yard |
|---|---|---|
| 代碼複雜度 | 低 (文法直映函數) | 中 (雙棧管理) |
| 錯誤定位 | 精確 (行/列號易集成) | 較難 (需額外記錄位置) |
| 擴展性 (函數/一元運算符/賦值) | 極強 (修改文法即可) | 中等 (需調整優先級表、棧邏輯) |
| 性能 | 遞歸開銷微小,現代編譯器優化良好 | 迭代無遞歸,極大表達式略優 |
| 適用場景 | **解析器/編譯器前端、配置語言、DSL、教學** | 表達式計算器、舊系統移植、無遞歸環境 |
; 或 )) 繼續解析,報告多錯誤。map,factor 增加標識符分支。factor 識別 sin( cos( 等,調用 math.h。參數列表解析 expression {',' expression}。**遞歸下降解析器是實現表達式求值的首選**:代碼與文法一一對應,可讀性極強,擴展性無敵。原始問題中給出的 PHP eval 方案**極其危險** (代碼注入風險)、不可移植、性能差,**嚴禁生產環境使用**。掌握手寫解析器,是理解編譯器前端、配置語言設計、DSL 實現的核心入門技能。