直接写 parse_expression() 易崩因未分层处理优先级,须按+−、*/、因子分三级函数,用循环消除左递归,配合token流定位与位置信息。

为什么直接写 parse_expression() 容易崩在加减优先级上
递归下降解析器不是把文法照抄成函数就完事——数学表达式里 + 和 * 优先级不同,如果让 parse_expression() 一把抓所有运算符,很快会把 1 + 2 * 3 解成 (1 + 2) * 3 == 9,而不是正确的 1 + (2 * 3) == 7。本质是文法没分层:expression → term (+|−) expression 和 term → factor (*|/) term 必须拆成不同层级函数,否则结合性和优先级全乱。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 按优先级从低到高设计函数链:最外层
parse_expression()只处理+/−,内部调用parse_term();parse_term()只处理*//,内部调用parse_factor() - 每个函数都采用「左递归消除」写法:用 while 循环吃掉连续同级运算符,不真写递归调用自己(C++ 栈深度有限,且左递归会无限展开)
- 提前校验 token 类型,比如
parse_factor()开头必须是数字、(或一元-,否则报错并返回默认值或抛异常
std::string 做输入时怎么安全取下一个 token
很多人直接用 std::string::substr() 切片拼新字符串,结果每次解析一个 token 就拷贝一次子串,表达式长一点性能断崖下跌。更糟的是,若 token 边界判断出错(比如把 12.34 当成两个整数),后续全错。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用索引指针
size_t pos在原std::string上滑动,只在需要时用std::string_view(C++17)或string.substr(pos, len)提取 token 值,避免中间拷贝 - 数字 token 解析必须支持整数和浮点:先读整数部分,遇到
.就继续读小数部分,再检查是否带指数(e/E);别用std::stod()直接转——它不报告解析截止位置,无法更新pos - 跳过空白用
std::isspace(static_cast<unsigned char>(s[pos]))</unsigned>,避免 locale 问题;遇到非法字符(如@、$)立刻报错,不要静默跳过
遇到 Unexpected token ')' 时怎么快速定位括号匹配问题
错误信息本身没用——它只告诉你“现在看到 ),但不该在这儿”。真正问题是:前面的 ( 没被正确消费,或者 parse_factor() 进入 ( 分支后,没严格要求结尾必须是 )。
实操建议:
立即学习“C++免费学习笔记(深入)”;
-
parse_factor()中识别到(后,必须调用parse_expression(),然后**立刻**检查下一个 token 是否为);如果不是,说明括号内表达式结束得太早或根本没闭合 - 维护一个轻量级 token 流包装器,带
peek()(看下一个不消耗)和consume()(确认消耗),这样在报错时能打印「expected), got+at position 15」,比笼统的 “unexpected” 有用得多 - 测试用例一定要覆盖嵌套:如
((1 + 2) * 3)、1 + (2 * (3 + 4)),单层括号掩盖不了匹配逻辑漏洞
C++11 能用但别硬扛的坑:异常、移动语义与 AST 构建
想返回解析树?别在每层函数里 new 一堆 ASTNode*——裸指针管理极易泄漏,而 C++11 不支持 std::make_unique 的完美转发(直到 C++14)。更现实的路径是:先做计算器式解析器(直接计算值),验证逻辑正确后再升级为构建 AST。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 初期用
double parse_expression()返回数值,函数内局部变量存中间结果;等全部运算符、括号、一元负号都跑通了,再考虑引入struct Expr { enum Type; std::unique_ptr<expr> left, right; double value; };</expr> - 一元负号(
-5)必须和二元减号(3 - 5)区分:在parse_factor()开头检查是否为-,若是则递归调用自身并取负,**不能**塞进parse_term()里处理 - 除零不抛异常,返回
std::numeric_limits<double>::quiet_NaN()</double>,由上层决定是否检查;C++11 的std::isnan()可用,比 try/catch 轻量
最常被跳过的细节是:没有为每个 token 维护位置信息(行/列),导致报错时只能给偏移量。真要调试复杂表达式,这个信息比语法树还先得有。


















