
本教程将详细介绍如何在php中不使用`eval()`函数,安全有效地计算包含运算符优先级的数学表达式。核心方法是采用调度场算法将中缀表达式转换为逆波兰表示法(rpn),随后利用栈结构对rpn表达式进行求值,从而实现对复杂数学运算的精确处理。
在PHP开发中,直接使用eval()函数来执行用户提供的数学表达式存在严重的安全隐患,因为它允许执行任意的PHP代码。为了避免这种风险,同时又能灵活地处理带有运算符优先级的数学表达式,我们需要一种自定义的解析与计算方案。本教程将深入探讨如何通过将中缀表达式转换为逆波兰表示法(Reverse Polish Notation, RPN)并对其进行求值来实现这一目标。
处理数学表达式通常涉及两个主要步骤:解析和求值。
我们日常使用的数学表达式,如 2 + 3 * 4,被称为中缀表达式。它的特点是运算符位于操作数之间,并且需要考虑运算符优先级和括号。
逆波兰表示法(RPN),也称为后缀表达式,是一种没有括号的表达式形式,其中运算符位于其操作数之后。例如,中缀表达式 2 + 3 * 4 对应的 RPN 形式是 2 3 4 * +。RPN 的优点在于,它在求值时无需考虑运算符优先级,只需按照从左到右的顺序处理即可,这大大简化了计算逻辑。
调度场算法是 Dijkstra 提出的一种将中缀表达式转换为 RPN 的经典算法。它利用两个栈:一个用于存储运算符(运算符栈),另一个用于存储输出(输出栈或队列,最终形成 RPN 序列)。
算法的核心规则如下:
我们将通过一系列 PHP 函数来实现数学表达式的解析和计算。
calculate() 函数是整个流程的入口,它负责协调中缀表达式到 RPN 的转换以及 RPN 表达式的求值。
SuperDesign
开源的UI设计AI智能体
216
查看详情
<?php
/**
* 计算中缀数学表达式的结果
* @param string $exp 中缀数学表达式字符串
* @return float 表达式计算结果
*/
function calculate($exp) {
return calculate_rpn(mathexp_to_rpn($exp));
}
// ... 后续辅助函数和核心函数 ...
?>这个函数实现了调度场算法,将中缀表达式字符串转换为 RPN 数组。
<?php
/**
* 将中缀表达式转换为逆波兰表示法 (RPN)
* @param string $mathexp 中缀数学表达式
* @return array 逆波兰表示法数组
*/
function mathexp_to_rpn($mathexp) {
// 定义运算符优先级
$precedence = array(
'(' => 0, // 括号的优先级最低,用于控制弹出
'+' => 3,
'-' => 3,
'*' => 6,
'/' => 6,
'%' => 6
);
$i = 0;
$final_stack = array(); // 存储 RPN 结果的栈
$operator_stack = array(); // 存储运算符的栈
while ($i < strlen($mathexp)) {
$char = $mathexp{$i};
// 1. 处理数字
if (is_number($char)) {
$num = readnumber($mathexp, $i);
array_push($final_stack, (float)$num); // 将数字转换为浮点数并推入结果栈
$i += strlen($num); // 跳过已读取的数字长度
continue;
}
// 2. 处理运算符
if (is_operator($char)) {
// 当运算符栈不为空,且栈顶不是左括号,且当前运算符优先级小于等于栈顶运算符优先级时
while (!empty($operator_stack) && end($operator_stack) != '(' && $precedence[$char] <= $precedence[end($operator_stack)]) {
array_push($final_stack, array_pop($operator_stack)); // 弹出栈顶运算符到结果栈
}
array_push($operator_stack, $char); // 将当前运算符推入运算符栈
$i++;
continue;
}
// 3. 处理左括号
if ($char == '(') {
array_push($operator_stack, $char);
$i++;
continue;
}
// 4. 处理右括号
if ($char == ')') {
// 弹出运算符直到遇到左括号
while (!empty($operator_stack) && ($operator = array_pop($operator_stack)) != '(') {
array_push($final_stack, $operator);
}
$i++;
continue;
}
$i++; // 跳过空格或其他未知字符
}
// 表达式处理完毕,将运算符栈中剩余的所有运算符弹出到结果栈
while ($oper = array_pop($operator_stack)) {
array_push($final_stack, $oper);
}
return $final_stack;
}
/**
* 从字符串中读取一个完整的数字
* @param string $string 原始字符串
* @param int $i 当前读取位置的索引(引用传递)
* @return string 读取到的数字字符串
*/
function readnumber($string, &$i) {
$number = '';
$start_i = $i; // 记录开始位置
while ($i < strlen($string) && is_number($string{$i})) {
$number .= $string{$i};
$i++;
}
$i = $start_i; // 恢复 $i 到数字开始位置,因为外部循环会重新增加 $i
return $number;
}
/**
* 判断字符是否为运算符
* @param string $char 待判断字符
* @return bool
*/
function is_operator($char) {
static $operators = array('+', '-', '/', '*', '%');
return in_array($char, $operators);
}
/**
* 判断字符是否为数字或小数点
* @param string $char 待判断字符
* @return bool
*/
function is_number($char) {
return (($char == '.') || ($char >= '0' && $char <= '9'));
}
?>代码解析要点:
这个函数接收 RPN 数组,并使用一个栈来计算表达式的结果。
<?php
/**
* 计算逆波兰表示法 (RPN) 表达式的结果
* @param array $rpnexp 逆波兰表示法数组
* @return float 表达式计算结果
*/
function calculate_rpn($rpnexp) {
$stack = array(); // 存储操作数的栈
foreach ($rpnexp as $item) {
if (is_operator($item)) {
// 如果是运算符,弹出两个操作数进行计算
$j = array_pop($stack);
$i = array_pop($stack);
switch ($item) {
case '+':
array_push($stack, $i + $j);
break;
case '-':
array_push($stack, $i - $j);
break;
case '*':
array_push($stack, $i * $j);
break;
case '/':
// 避免除以零
if ($j == 0) {
throw new InvalidArgumentException("Division by zero.");
}
array_push($stack, $i / $j);
break;
case '%':
// 取模操作数必须为整数
if (!is_int($i) || !is_int($j)) {
throw new InvalidArgumentException("Modulo operator requires integer operands.");
}
if ($j == 0) {
throw new InvalidArgumentException("Modulo by zero.");
}
array_push($stack, $i % $j);
break;
}
} else {
// 如果是数字,直接推入栈
array_push($stack, $item);
}
}
return $stack[0]; // 最终结果在栈顶
}
?>代码解析要点:
将以上所有函数组合在一个文件中,即可进行测试。
<?php
// 辅助函数
function readnumber($string, &$i) {
$number = '';
$start_i = $i;
while ($i < strlen($string) && is_number($string{$i})) {
$number .= $string{$i};
$i++;
}
$i = $start_i;
return $number;
}
function is_operator($char) {
static $operators = array('+', '-', '/', '*', '%');
return in_array($char, $operators);
}
function is_number($char) {
return (($char == '.') || ($char >= '0' && $char <= '9'));
}
// 核心函数
function calculate($exp) {
return calculate_rpn(mathexp_to_rpn($exp));
}
function calculate_rpn($rpnexp) {
$stack = array();
foreach($rpnexp as $item) {
if (is_operator($item)) {
$j = array_pop($stack);
$i = array_pop($stack);
switch ($item) {
case '+': array_push($stack, $i + $j); break;
case '-': array_push($stack, $i - $j); break;
case '*': array_push($stack, $i * $j); break;
case '/':
if ($j == 0) throw new InvalidArgumentException("Division by zero.");
array_push($stack, $i / $j);
break;
case '%':
if (!is_int($i) || !is_int($j)) throw new InvalidArgumentException("Modulo operator requires integer operands.");
if ($j == 0) throw new InvalidArgumentException("Modulo by zero.");
array_push($stack, $i % $j);
break;
}
} else {
array_push($stack, $item);
}
}
return $stack[0];
}
function mathexp_to_rpn($mathexp) {
$precedence = array(
'(' => 0,
'-' => 3,
'+' => 3,
'*' => 6,
'/' => 6,
'%' => 6
);
$i = 0;
$final_stack = array();
$operator_stack = array();
while ($i < strlen($mathexp)) {
$char = $mathexp{$i};
if (is_number($char)) {
$num = readnumber($mathexp, $i);
array_push($final_stack, (float)$num);
$i += strlen($num); continue;
}
if (is_operator($char)) {
while (!empty($operator_stack) && end($operator_stack) != '(' && $precedence[$char] <= $precedence[end($operator_stack)]) {
array_push($final_stack, array_pop($operator_stack));
}
array_push($operator_stack, $char);
$i++; continue;
}
if ($char == '(') {
array_push($operator_stack, $char);
$i++; continue;
}
if ($char == ')') {
while (!empty($operator_stack) && ($operator = array_pop($operator_stack)) != '(') {
array_push($final_stack, $operator);
}
$i++; continue;
}
$i++; // 忽略其他字符,例如空格
}
while ($oper = array_pop($operator_stack)) {
array_push($final_stack, $oper);
}
return $final_stack;
}
// 使用示例
try {
$expression1 = "27+38+81+48*33*53+91*53+82*14+96";
echo "表达式: " . $expression1 . " = " . calculate($expression1) . "\n"; // 预期输出: 90165
$expression2 = "3 + 2 * (5 - 1)";
echo "表达式: " . $expression2 . " = " . calculate($expression2) . "\n"; // 预期输出: 11
$expression3 = "(10 + 20) / 5 - 3";
echo "表达式: " . $expression3 . " = " . calculate($expression3) . "\n"; // 预期输出: 3
$expression4 = "10 / 3";
echo "表达式: " . $expression4 . " = " . calculate($expression4) . "\n"; // 预期输出: 3.333...
$expression5 = "10 % 3";
echo "表达式: " . $expression5 . " = " . calculate($expression5) . "\n"; // 预期输出: 1
// 尝试除以零
// $expression_error = "5 / 0";
// echo "表达式: " . $expression_error . " = " . calculate($expression_error) . "\n";
} catch (InvalidArgumentException $e) {
echo "计算错误: " . $e->getMessage() . "\n";
}
?>通过调度场算法将中缀表达式转换为逆波兰表示法,并利用栈结构对 RPN 表达式进行求值,我们成功地在 PHP 中实现了一个不依赖 eval() 函数的数学表达式计算器。这种方法不仅避免了 eval() 带来的安全风险,还提供了一个清晰、可控且易于理解的表达式处理机制。虽然当前实现有一些限制,但其模块化的设计为未来功能的扩展和优化奠定了坚实的基础。
以上就是PHP实现数学表达式解析与计算:基于逆波兰表示法(不使用eval())的详细内容,更多请关注php中文网其它相关文章!
# 浮点数
# 房山建设公司网站
# 什么源码利于seo
# 盐都seo价格
# 收废旧物资哪个网站推广
# 杭州网站建设服务平台
# 乐平关键词排名优化
# 山西网站高端建设
# 浏阳营销推广企业
# 使用营销推广的条件
# 立春seo
# 怎么看
# 为空
# php
# 遍历
# 不支持
# 求值
# 转换为
# 弹出
# 波兰
# 运算符
# php 函数
# php开发
# switch
# 栈
# go
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
AngularJS动态内容中DOM元素查找的时序问题及$timeout解决方案
热血江湖归来医师加点攻略
申通快递物流信息查询 申通快递包裹状态追踪
知音漫客官网首页入口_知音漫客热门漫画推荐
顺丰快递收费标准查询_如何查看顺丰最新收费价格
小米手机屏幕失灵乱跳怎么办 屏幕触控问题自检与临时解决方法【应急】
优酷官网登录入口电脑版 优酷官网网址入口
J*aScript类型数组_TypedArray使用
cad视图选项卡不见了怎么办_cad视图标签恢复显示方法
芒果TV官网登录入口 芒果TV官方网站登录入口
MacBook Pro词典使用指南
如何在Podman容器中运行Composer_Docker替代品Podman的PHP与Composer容器化实践
《大学搜题酱》官网地址登录
抖音网页版地址直接进入_抖音网页版在线观看入口
使用VS Code调试Python代码:从入门到精通
CSS过渡如何实现按钮悬停效果_transition属性控制背景颜色变化
被称为海蜈蚣的海洋动物是
《浙里办》电子发票开具方法
抖音视频如何添加标题?添加标题有哪些好处?
抖音号怎么解除企业认证改成个人?改成个人有影响吗?
手机坏了微信聊天记录怎么导出来 新手机恢复聊天记录技巧
优化Flask模板中SQLAlchemy查询迭代标签:处理字符串空格问题
《三角洲行动》战斗步枪与机枪类改装代码分享
192.168.1.1路由器后台入口 192.168.1.1默认登录入口
如何用mysql开发用户注册登录功能_mysql用户注册登录数据库设计
C++怎么解决数值计算中的精度问题_C++浮点数误差与数值稳定性分析
12306夜间购票失败? | 查看官方公布的暂停服务公告与应对方案
SQL聚合查询、联接与筛选:GROUP BY 子句的正确使用与常见陷阱
如何在mysql中使用索引提示_mysql索引提示优化方法
win11关机几秒又自己开机 Win11关机自动重启问题修复
Sublime怎么格式化HTML代码_Sublime前端代码美化插件使用指南
酷狗音乐多音轨设置教程
TikTok笔记文字无法编辑如何解决 TikTok笔记文字编辑优化方法
如何取消数字签名
小红书网页版在线直达 小红书网页版免费登录入口
《金山词霸》语音翻译方法
视频号视频怎么免费保存到相册?保存到相册需要注意什么?
《地下城堡4:骑士与破碎编年史》墓穴挑战125攻略
哈尔滨城市通昵称修改方法
sublime text 4如何安装_最新版sublime下载与汉化教程
狙击外星人小游戏在线链接_狙击外星人小游戏网页链接
《海底捞》点外卖方法
Win11便笺在哪打开 Win11桌面便笺(Sticky Notes)使用方法【详解】
win11如何开启单声道音频 Win11为听障用户合并左右声道【辅助】
SQLAlchemy 2.0 与 Pydantic 模型类型安全集成指南
《雷电模拟器》自动点击设置方法
win11如何诊断DirectX问题 Win11运行dxdiag工具排查显卡故障【排错】
PHP utf8_encode 字符编码转换疑难解析与最佳实践
J*a列表元素格式化输出教程
哔哩哔哩黑名单怎么查看
2025-11-15
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。