c++标准库无eval(),std::stod仅解析单个数字;带优先级、括号、负号歧义的表达式必须手写词法分析+递归下降或shunting yard来构建ast。

为什么不能直接用 std::stod 或 eval() 解析带括号和优先级的表达式
因为 C++ 标准库根本没有 eval();std::stod 只能转换单个数字字符串,遇到 "2+3*4" 会停在 '+' 处并抛出 std::invalid_argument。真实算术表达式含运算符优先级、嵌套括号、负号歧义(如 "-5" vs "3-5"),必须手写解析逻辑——绕不开词法分析 + 递归下降或运算符优先级驱动(Shunting Yard)。
如何用递归下降解析器构建 AST 节点而非仅求值
关键在于把每个语法层级映射为具体 AST 节点类型,而不是边解析边计算。比如 Expr → Term (('+' | '-') Term)* 对应 BinaryOpNode,而 Term → Factor (('*' | '/') Factor)* 也生成 BinaryOpNode,但运算符不同。节点设计需区分:数字字面量(NumberNode)、一元负号(UnaryMinusNode)、二元运算(BinaryOpNode)。
实操建议:
- 定义基类
AstNode,派生类都重载evaluate(),但构造时只存子节点指针,不触发计算 - 解析
primary()时识别左括号后递归调用expr(),自然形成嵌套结构 - 负号处理要分场景:开头的
'-'是一元运算,中间的'-'是二元运算——靠当前上下文(是否在表达式起始位置)或前一个 token 类型判断 - 避免裸指针:用
std::unique_ptr<astnode></astnode>管理所有权,防止内存泄漏
Shunting Yard 算法生成后缀表达式再建 AST 的坑在哪
它适合求值,但转 AST 时容易丢失结构信息。比如 "1+2*3" 转后缀是 1 2 3 * +,重建时若按栈顺序弹出操作数,会错误地先建 2*3 节点,再以该节点为右操作数建 1+ 节点——这本身没错;但遇到 "(1+2)*3",后缀是 1 2 + 3 *,若未严格按操作数入栈/出栈顺序构造,可能把 3 当作 + 的右操作数。
要点:
- 用栈存
std::unique_ptr<astnode></astnode>,每遇到运算符就 pop 两个节点(注意顺序:先 pop 的是右操作数) - 一元运算符(如
'-')只 pop 一个节点 - 括号不进栈也不生成节点,仅控制运算符入栈时机
- 最终栈中只剩一个节点——就是根节点;栈空或元素数 ≠ 1 表示语法错误
AST 求值阶段如何安全处理除零、溢出和 double 精度误差
AST 的 evaluate() 方法是最后执行的纯计算环节,这里才真正面对运行时异常。C++ 不自动捕获除零,1.0/0.0 得到 inf,但整数除零是未定义行为(UB),可能 crash。
建议做法:
- 所有除法前检查右操作数绝对值是否
(浮点)或是否为 <code>0(整型);报错并 throw 自定义异常如std::runtime_error("division by zero") - 整数运算用
std::mul_overflow/std::add_overflow(C++23)或手动检查符号位与边界;避免依赖INT_MAX + 1这种 UB - 比较浮点结果时不用
==,改用std::abs(a - b) - 不要在 AST 节点里缓存
double值——保留原始 token 字符串或long double,减少中间精度损失
最易被忽略的是负号和减号的 token 区分:同一个字符 '-' 在不同位置语义完全不同,词法分析阶段就要打上 MINUS 或 NEGATE 标签,否则语法分析时无法分支处理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











