在Delphi计算器中实现运算优先级(PEMDAS)的技术问询
Delphi计算器实现运算优先级(PEMDAS)的可行方案
核心方案:双栈法
用两个栈分别存储操作数和运算符,通过遍历表达式Token,根据运算符优先级决定是入栈还是先执行运算,完美适配你的TStringList存储结构。
处理逻辑
- 遍历拆分后的表达式Token列表(比如
["5", "+", "2", "*", "6"]) - 遇到操作数直接压入操作数栈
- 遇到运算符时:
- 循环检查运算符栈顶的优先级,如果栈顶运算符优先级≥当前运算符,弹出栈顶运算符和两个操作数(注意顺序:后弹出的是左操作数)
- 计算结果后将结果压回操作数栈
- 把当前运算符压入运算符栈
- 遍历结束后,处理运算符栈中剩余的所有运算符,依次计算直到栈为空
- 操作数栈最后剩余的元素就是最终结果
示例流程(5 + 2 * 6)
- 压入操作数
5→ 操作数栈:[5] - 运算符
+入栈 → 运算符栈:[+] - 压入操作数
2→ 操作数栈:[5,2] - 运算符
*优先级高于栈顶的+,直接入栈 → 运算符栈:[+,*] - 压入操作数
6→ 操作数栈:[5,2,6] - 遍历结束,处理剩余运算符:
- 弹出
*,弹出6和2,计算2*6=12,压回操作数栈 → 操作数栈:[5,12] - 弹出
+,弹出12和5,计算5+12=17,压回操作数栈 → 操作数栈:[17]
- 弹出
- 最终结果为
17
伪代码实现(适配Delphi TStringList)
// 假设ExpressionTokens是已拆分好的表达式Token列表(操作数、运算符交替) var OperandStack: TStringList; // 模拟操作数栈:Add压栈,Delete(Count-1)弹栈,Last取栈顶 OperatorStack: TStringList; // 模拟运算符栈 I: Integer; CurrentToken: string; Op1, Op2, ResultVal: Double; OpTop: string; begin OperandStack := TStringList.Create; OperatorStack := TStringList.Create; try for I := 0 to ExpressionTokens.Count - 1 do begin CurrentToken := ExpressionTokens[I]; // 判断是否为操作数(数字) if TryStrToDouble(CurrentToken, ResultVal) then begin OperandStack.Add(CurrentToken); end // 判断是否为运算符 else if CurrentToken in ['+', '-', '*', '/', '(', ')'] then begin if CurrentToken = '(' then begin // 左括号直接入栈 OperatorStack.Add(CurrentToken); end else if CurrentToken = ')' then begin // 右括号:计算到左括号为止 while (OperatorStack.Count > 0) and (OperatorStack[OperatorStack.Count-1] <> '(') do begin OpTop := OperatorStack[OperatorStack.Count-1]; OperatorStack.Delete(OperatorStack.Count-1); // 弹出两个操作数(注意顺序:先弹右,后弹左) Op2 := StrToDouble(OperandStack[OperandStack.Count-1]); OperandStack.Delete(OperandStack.Count-1); Op1 := StrToDouble(OperandStack[OperandStack.Count-1]); OperandStack.Delete(OperandStack.Count-1); // 计算并压回结果 case OpTop of '+': ResultVal := Op1 + Op2; '-': ResultVal := Op1 - Op2; '*': ResultVal := Op1 * Op2; '/': if Op2 <> 0 then ResultVal := Op1 / Op2 else raise Exception.Create('除数不能为0'); end; OperandStack.Add(FloatToStr(ResultVal)); end; // 弹出左括号(不参与计算) if OperatorStack.Count > 0 then OperatorStack.Delete(OperatorStack.Count-1); end else begin // 处理普通运算符:优先级判断 while (OperatorStack.Count > 0) and (GetPriority(OperatorStack[OperatorStack.Count-1]) >= GetPriority(CurrentToken)) do begin OpTop := OperatorStack[OperatorStack.Count-1]; OperatorStack.Delete(OperatorStack.Count-1); Op2 := StrToDouble(OperandStack[OperandStack.Count-1]); OperandStack.Delete(OperandStack.Count-1); Op1 := StrToDouble(OperandStack[OperandStack.Count-1]); OperandStack.Delete(OperandStack.Count-1); case OpTop of '+': ResultVal := Op1 + Op2; '-': ResultVal := Op1 - Op2; '*': ResultVal := Op1 * Op2; '/': if Op2 <> 0 then ResultVal := Op1 / Op2 else raise Exception.Create('除数不能为0'); end; OperandStack.Add(FloatToStr(ResultVal)); end; // 当前运算符入栈 OperatorStack.Add(CurrentToken); end; end; end; // 处理剩余的运算符 while OperatorStack.Count > 0 do begin OpTop := OperatorStack[OperatorStack.Count-1]; OperatorStack.Delete(OperatorStack.Count-1); Op2 := StrToDouble(OperandStack[OperandStack.Count-1]); OperandStack.Delete(OperandStack.Count-1); Op1 := StrToDouble(OperandStack[OperandStack.Count-1]); OperandStack.Delete(OperandStack.Count-1); case OpTop of '+': ResultVal := Op1 + Op2; '-': ResultVal := Op1 - Op2; '*': ResultVal := Op1 * Op2; '/': if Op2 <> 0 then ResultVal := Op1 / Op2 else raise Exception.Create('除数不能为0'); end; OperandStack.Add(FloatToStr(ResultVal)); end; // 输出最终结果 if OperandStack.Count = 1 then ShowMessage('计算结果:' + OperandStack[0]) else raise Exception.Create('表达式格式错误'); finally OperandStack.Free; OperatorStack.Free; end; end; // 辅助函数:获取运算符优先级 function GetPriority(const Op: string): Integer; begin if Op in ['*', '/'] then Result := 2 else if Op in ['+', '-'] then Result := 1 else if Op = '(' then Result := 0 // 左括号优先级最低,避免提前计算括号内内容 else Result := -1; end;
关键注意事项
- 确保表达式已正确拆分为Token(比如
"5+2*6"要拆成["5", "+", "2", "*", "6"],可以通过遍历字符拆分实现) - 处理除数为0的异常情况,避免程序崩溃
- 扩展指数运算的话,只需给
^或**设置更高的优先级(比如3),并在计算分支中添加指数运算逻辑
内容的提问来源于stack exchange,提问作者nahic
相关产品推荐
相关产品推荐

