运算符在任何编程语言里都扮演着核心角色,PHP自然也不例外。简单说,运算符就是用来对变量或值进行某种操作的符号。作的对象叫操作数,操作数和运算符组合起来就构成了表达式。
比如这行代码:
$total = 10 + 20;
这里面+是运算符,10和20是操作数,$total是接收结果的变量。因为它作用于两个操作数,所以+在这里是一个二元运算符。
PHP的运算符家族相当庞大,按功能可以细分为十一大类。如果按操作数的数量来分,又可以归纳为三种形态:
-
一元运算符:只操作一个操作数,比如
++、--、! -
二元运算符:操作两个操作数,比如
+、-、*、/ -
三元运算符:操作三个操作数,PHP里特指
?:
下面我们按功能分类逐一拆解,每个类别都配上能直接运行的短示例,方便你在本地环境试验。
一、算术运算符
算术运算符处理的就是我们日常的数字运算。PHP支持标准四则运算,加上取模和幂运算。其中幂运算符**是从PHP 5.6.0才加入的,用老版本的话要注意兼容性。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| + | 加法 | a+a+b | 两数之和 |
| - | 减法 | a−a−b | 两数之差 |
| * | 乘法 | a∗a∗b | 两数之积 |
| / | 除法 | a/a/b | 两数之商 |
| % | 取模 | aab | 两数相除的余数 |
| ** | 幂运算 | a∗∗a∗∗b | a的a的b次方 |
示例:
<?php
$coursePrice = 299; // 课程单价
$courseCount = 3; // 购买数量
$discount = 80; // 优惠金额
$subTotal = $coursePrice * $courseCount; // 乘法:897
$finalPrice = $subTotal - $discount; // 减法:817
$averagePrice = $finalPrice / $courseCount; // 除法:约272.33
$remainder = $finalPrice % $courseCount; // 取模:817除以3余1
echo "小计:{$subTotal}元,实付:{$finalPrice}元,余数演示:{$remainder}\n";
// 幂运算:2的10次方
echo "2的10次方是:" . (2 ** 10); // 输出:1024
?>
取模运算在判断奇偶数、分页计算等场景里非常实用。幂运算则省去了以前用pow()函数的麻烦,代码更直观。
二、赋值运算符
最基本的赋值运算符就是等号=,它把右边表达式的值赋给左边变量。PHP还提供了一组复合赋值运算符,把运算和赋值合并成一步,写起来更精炼。
| 运算符 | 名称 | 示例 | 等价于 |
|---|---|---|---|
| = | 赋值 | a=a=b | a=a=b |
| += | 加后赋值 | a+=a+=b | a=a=a + $b |
| -= | 减后赋值 | a−=a−=b | a=a=a - $b |
| *= | 乘后赋值 | a∗=a∗=b | a=a=a * $b |
| /= | 除后赋值 | a/=a/=b | a=a=a / $b |
| %= | 取模后赋值 | aab | a=a=a % $b |
示例:
<?php
$score = 0;
echo "初始分数:{$score}\n";
$score += 10; // 答对一题加10分
echo "第一题后:{$score}\n";
$score *= 2; // 触发双倍积分卡
echo "双倍后:{$score}\n";
$score -= 5; // 违规扣5分
echo "扣分后:{$score}\n";
$score /= 3; // 均分给3个队友
echo "均分后:{$score}\n";
?>
个人见解:复合赋值运算符不仅仅是少写几个字符。在循环累加或者计数器场景里,$counter += 1比$counter = $counter + 1更贴近思维惯性,读代码时一眼就知道是在“追加”而不是“重新计算”。我自己的编码习惯是,只要变量自身参与运算后还要存回自身,一律用复合赋值。
三、位运算符
位运算符直接操作整数的二进制位,属于偏底层但效率很高的操作。在权限系统、状态标记、加密算法等场景中经常能见到它们的身影。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| & | 按位与 | a & b | 两数对应位都是1,结果位才是1 |
| | | 按位或 | a∥a∥b | 两数对应位有一个是1,结果位就是1 |
| ^ | 按位异或 | a ^ b | 两数对应位不同,结果位才是1 |
| ~ | 按位取反 | ~$a | 把每一位的0变1,1变0 |
| << | 左移 | a<<a<<b | 把a的位向左移a的位向左移b位 |
| >> | 右移 | a>>a>>b | 把a的位向右移a的位向右移b位 |
按位与(&)示例:
<?php
$a = 6; // 二进制:110
$b = 3; // 二进制:011
echo $a & $b; // 二进制010 = 十进制2
?>
只有两边都是1的位才保留1,其余归零,所以110 & 011得到010,也就是2。
按位或(|)示例:
<?php
$a = 6; // 110
$b = 3; // 011
echo $a | $b; // 111 = 7
?>
只要有一边是1就置1,所以结果是111,即7。
按位异或(^)示例:
<?php
$a = 6; // 110
$b = 3; // 011
echo $a ^ $b; // 101 = 5
?>
两边不同得1,相同得0,所以110 ^ 011 = 101,即5。
本节课程知识要点:位运算里有个很实用的技巧——用异或来交换两个整数,不需要借助临时变量。当然现在PHP中可读性比炫技更重要,但理解这个原理对底层思维有帮助。另外左移一位等价于乘以2,右移一位等价于除以2并向下取整,处理大循环的性能优化时可以留意这一点。
按位取反(~)示例:
<?php
$num = 5;
echo ~$num; // 输出:-6
// 公式:~n = -(n + 1)
?>
左移(<<)和右移(>>)示例:
<?php
$a = 6;
echo $a << 3; // 6 * 2 * 2 * 2 = 48
$b = 24;
echo $b >> 3; // 24 / 2 / 2 / 2 = 3
?>
四、比较运算符
比较运算符用来判断两个值之间的关系,结果是布尔值(true或false)。它们是条件语句和循环的根基。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| == | 等于 | a==a==b | 值相等即true(类型可不同) |
| === | 全等 | a===a===b | 值和类型都相等才true |
| != 或 <> | 不等于 | a!=a!=b | 值不相等即true |
| !== | 不全等 | a!==a!==b | 值或类型不相等即true |
| < | 小于 | a<a<b | |
| > | 大于 | a>a>b | |
| <= | 小于等于 | a<=a<=b | |
| >= | 大于等于 | a>=a>=b | |
| <=> | 太空船 | a<=>a<=>b | a<a<b返回-1,相等返回0,a>a>b返回1 |
示例:
<?php
$userAge = 20;
$Threshold = 18;
if ($userAge >= $Threshold) {
echo "已成年,可进入\n";
}
// 太空船运算符(PHP 7+)
echo 5 <=> 10; // 输出:-1
echo 10 <=> 10; // 输出:0
echo 15 <=> 10; // 输出:1
?>
为什么用===而不用==:==会做类型转换再比较,比如0 == '0'结果是true,0 == ''也是true,这在很多业务场景里会埋下隐患。===要求两边类型也一致,杜绝了隐式转换带来的意外。我个人的原则是:除非你明确需要利用类型转换的宽松比较特性,否则一律用===和!==。太空船运算符在写排序回调函数时特别简洁,尤其是usort()配合<=>能让代码少好几行。
五、递增/递减运算符
这两个一元运算符专门用来给变量加1或减1,分前缀和后缀两种形态。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| ++$a | 前缀递增 | ++$a | 先加1,再返回$a |
| $a++ | 后缀递增 | $a++ | 先返回$a,再加1 |
| --$a | 前缀递减 | --$a | 先减1,再返回$a |
| $a-- | 后缀递减 | $a-- | 先返回$a,再减1 |
示例:
<?php
$counter = 10;
$result = ++$counter; // 先加到11,再赋给$result
echo "前缀递增:counter={$counter}, result={$result}\n";
// 输出:counter=11, result=11
$counter = 10;
$result = $counter++; // 先把10赋给$result,再加到11
echo "后缀递增:counter={$counter}, result={$result}\n";
// 输出:counter=11, result=10
?>
前缀和后缀的差别只有在“赋值和递增同时发生”的表达式里才体现。单独一行写$i++;时,两者效果没区别。在循环中,for($i=0; $i<10; $i++)和for($i=0; $i<10; ++$i)在PHP里性能差异微乎其微,选哪个主要看团队习惯,我个人倾向$i++,因为它更贴近“用过之后再递增”的直觉。
六、逻辑运算符
逻辑运算符用于组合多个条件表达式,构建更复杂的判断逻辑。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| && | 逻辑与 | a && b | 两边都为true才返回true |
| || | 逻辑或 | a∥∥a∥∥b | 任一边为true就返回true |
| ! | 逻辑非 | !$a | 取反 |
| and | 逻辑与 | aandaandb | 同&&,但优先级不同 |
| or | 逻辑或 | aoraorb | 同||,但优先级不同 |
| xor | 逻辑异或 | axoraxorb | 一真一假才返回true |
示例:
<?php
$hasTicket = true;
$hasID = false;
if ($hasTicket && $hasID) {
echo "可以入场\n";
} else {
echo "禁止入场\n"; // 会输出这行
}
// 优先级差异演示
$result = true || false && false;
// 大多数人以为(false && false)先算,|| 优先级更高,实际||先算
var_dump($result); // true
?>
本节课程知识要点:&&和and、||和or在逻辑功能上一样,但优先级不同。&&的优先级高于and,||高于or。这意味着$a = true or false这行代码,=的优先级比or高,所以$a会先被赋值为true,然后or false部分根本不执行。如果想用or做条件赋值,得加括号。这个坑我早年在写数据库连接$conn = mysql_connect(...) or die(...)时踩过,后来就统一只用&&和||了,避免优先级带来的困惑。
七、字符串运算符
PHP里字符串拼接用的不是加号,而是点号.。这一点和JavaScript、Java等语言不同,初学PHP时要特别适应一下。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| . | 连接 | a.a.b | 把a和a和b拼接成一个字符串 |
| .= | 连接后赋值 | a.=a.=b | 等价于a=a=a . $b |
示例:
<?php
$firstName = "代码号";
$lastName = "学习编程";
$fullName = $firstName . " " . $lastName;
echo $fullName; // 输出:代码号 学习编程
// 追加内容
$breadcrumb = "首页";
$breadcrumb .= " > 课程中心";
$breadcrumb .= " > PHP基础";
echo $breadcrumb; // 输出:首页 > 课程中心 > PHP基础
?>
拼接大量字符串时,用.直接连比用双引号内嵌变量效率高一些,因为PHP不需要解析整个字符串去寻找变量。不过在模板输出场景,双引号内嵌变量可读性好很多,这个取舍要根据实际情况来定。
八、数组运算符
数组运算符主要用来合并和比较数组。
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
| + | 联合 | a+a+b | 把b中不重复的键追加到b中不重复的键追加到a后面 |
| == | 相等 | a==a==b | 键值对相同即true |
| === | 全等 | a===a===b | 键值对相同且顺序和类型也相同 |
| != 或 <> | 不等 | a!=a!=b | |
| !== | 不全等 | a!==a!==b |
示例:
<?php
$frontEnd = ["html" => "结构", "css" => "样式"];
$backEnd = ["php" => "逻辑", "css" => "也能写样式"];
// 联合:重复键保留左边数组的值
$fullStack = $frontEnd + $backEnd;
print_r($fullStack);
/* 输出:
Array (
[html] => 结构
[css] => 样式 // 保留了$frontEnd的
[php] => 逻辑
)
*/
?>
个人建议:数组联合+和array_merge()的行为不一样,前者保留左边数组的重复键值,后者用右边数组的覆盖左边。不少人在需要合并数组时顺手用+,结果发现数据没变,就是因为这个差异。要先搞清楚自己是要“保留原始值”还是“后者覆盖前者”,再选对应的操作方式。
九、类型运算符
PHP里只有一个类型运算符:instanceof。它用来检测一个对象是否属于某个类,或者该类的子类,或者实现了某个接口。
示例:
<?php
class Student {}
class VipStudent extends Student {}
$xiaoming = new VipStudent();
var_dump($xiaoming instanceof VipStudent); // true
var_dump($xiaoming instanceof Student); // true,子类对象也是父类的实例
var_dump($xiaoming instanceof Teacher); // false,没有继承关系
?>
instanceof在大型项目里经常配合依赖注入使用。比如接收一个参数时,先检查它是不是指定接口的实例,不是就直接抛异常,比运行时才发现方法不存在要安全得多。
十、执行运算符
PHP用反引号`(键盘上~那个键,不是单引号)来执行服务器上的shell命令,效果等同于shell_exec()函数。
示例:
<?php
// 在Windows上列出当前目录的php文件
$output = `dir *.php`;
echo $output;
?>
这个运算符在开发环境快速调用系统命令很方便,但在生产环境要极其谨慎。任何用户输入拼接到反引号里都可能造成命令注入漏洞,所以涉及表单输入的场景,不要用执行运算符。
十一、错误控制运算符
@符号放在表达式前,可以抑制该表达式产生的错误信息。
示例:
<?php
// 不加@,打开不存在文件会报Warning
$handle = @fopen("不存在的文件.txt", "r");
// 加了@,错误信息被吞掉,$handle为false
if (!$handle) {
echo "文件打开失败,已静默处理\n";
}
?>
个人见解:@是一把双刃剑。表面上看代码没报错了,但问题并没解决,只是被藏起来了。而且@会拖慢执行速度,因为PHP需要临时关闭错误报告再恢复。与其用@,不如花一点时间写if (file_exists(...))做前置检查,或者用try...catch做异常处理。我自己的代码里几乎不用@,只在某些遗留系统维护时为了避免改不动老逻辑才偶尔用它兜底。
PHP运算符优先级
当一个表达式里有多种运算符时,PHP按照固定的优先级顺序来决定先算谁。这里列出主要几个优先级层级,从高到低:
| 优先级 | 运算符 | 结合方向 |
|---|---|---|
| 最高 | clone, new | 无 |
| ** | 右 | |
| ++ -- ~ (类型转换) @ | 右 | |
| instanceof | 无 | |
| ! | 右 | |
| * / % | 左 | |
| + - . | 左 | |
| << >> | 左 | |
| < <= > >= | 无 | |
| == != === !== <> <=> | 无 | |
| & | 左 | |
| ^ | 左 | |
| | | 左 | |
| && | 左 | |
| || | 左 | |
| ?: | 左 | |
| = += -= 等赋值运算符 | 右 | |
| and | 左 | |
| xor | 左 | |
| 最低 | or | 左 |
示例:
<?php
// 下面表达式按什么顺序算?
$result = 20 + 8 * 16 / 4 - 9 % 4;
/*
* 步骤拆解:
* 8 * 16 = 128 (*和/同级,从左到右)
* 128 / 4 = 32 (继续左到右)
* 9 % 4 = 1 (%和*/同级)
* 20 + 32 = 52 (+和-同级)
* 52 - 1 = 51 (最终结果)
*/
echo $result; // 51
?>
本节课程知识要点:不要靠记忆来排查优先级问题,合理的括号才是正道。哪怕你知道*比+先算,写(8 * 16) / 4也比裸写8 * 16 / 4让后来看代码的人更舒服。尤其在&&、||、and、or混用或者和赋值运算符一起出现时,多加括号能省去大量调试时间。这也是为什么很多团队的PHP编码规范里会直接要求“涉及两个以上不同运算符时,必须用括号明确优先级”。