我创建了一个使用 RDP(递归下降解析器)的计算器来解析和评估数学表达式“例如:5cos(30) -5(3+5)”。问题是我还尝试包含像“config”这样的命令,它可以让用户配置角度单位(Gra、Rad、Deg)。而这里我发现了代码设计的问题。
调用堆栈的简化示例:
int main()
{
parseexpr()
{
parseterm()
{
parsefactor()
{
parsecommand() or parsefct() or parsenumber();
}
}
}
}
现在当用户输入“config”命令时。我想让他配置计算器(配置位于 .txt 文件内),然后跳回“main()”(他会被要求再次输入表达式),因为通过调用返回会出现问题堆栈,因为我需要一个返回值,而“parsecommand()”无法返回。
我的问题是如何清理调用堆栈,以便我可以通过“goto”语句直接返回到 main ?
我的问题是如何清理调用堆栈,以便我可以通过“goto”语句直接返回到 main ?
不要这样做。这其实就是所谓的“XY问题”。
正确的做法是这样的:
将所有这些功能放在自己的模块中,我们称之为
parse.h / parse.c
。
在
parse.h
中提出一个由该模块中所有适用函数使用的结果/错误类型。比如:
typedef enum
{
/* whatever results that may make sense: */
PARSE_OK,
PARSE_BAD_FORMAT,
PARSE_DIV_BY_ZERO,
...
} parse_result_t;
适用的功能定义为:
parse_result_t parse_expr (/* parameters */);
任何需要返回的值都作为指针参数传递。
在嵌套的多个函数调用中,每个函数都会检查结果,如果不正确,则向调用者返回错误代码。通常,在出现错误时不修改值参数被认为是良好的做法。
不正确、非常糟糕的做法是使用
setjmp
/longjmp
,它们是实际存储和恢复调用堆栈和其他类似变量的低级函数。因此理论上可以用标准 C 实现,但这些函数是不好的做法,因为:
jmp_buf
parse.c
,我还必须链接main.c
。这种紧密耦合的依赖关系既不可移植也不可维护,几乎在所有可以想象的方面都是糟糕的。自 20 世纪 60 年代以来,无条件跳转到程序中不相关的部分就被认为是糟糕的编程(“GoTo 被认为是有害的”),并被称为“意大利面条编程”——比喻你的程序将开始像一盘意大利面条,其中一根链可能最终到达其他地方的某个意想不到的、不相关的位置。阅读和维护极其困难。setjmp
longjmp
带有一长串危险和潜在的未定义行为。