PHP 基本语法 02|运算符与表达式:优先级里埋着历史
算术运算符
<?php
$a = 7; $b = 3;
var_dump($a + $b); // int(10)
var_dump($a - $b); // int(4)
var_dump($a * $b); // int(21)
var_dump($a / $b); // float(2.333...) —— 除不尽就变 float
var_dump($a % $b); // int(1) —— 取模,操作数会先转成 int
var_dump($a ** $b); // int(343) —— 幂运算(PHP 5.6+)
var_dump(intdiv(7, 3)); // int(2) —— 整除(PHP 7+)
两条容易踩的规则:
/永远返回 float,除非两个操作数都是整数且能整除——不,更精确地说:PHP 的/在无法整除时返回 float,能整除时返回 int。6/2是int(3),7/3是float。想要整数结果用intdiv()。%在 PHP 8 之前,除数为 0 会报警告并返回 false,PHP 8.0 起除零会抛DivisionByZeroError。取模的符号跟随被除数:-7 % 3是-1。
浮点运算的老问题仍然在:
<?php
var_dump(0.1 + 0.2); // float(0.30000000000000004)
var_dump(0.1 + 0.2 == 0.3); // bool(false)
var_dump(abs((0.1 + 0.2) - 0.3) < PHP_FLOAT_EPSILON); // bool(true) 推荐写法
金额计算不要用 float。用整数存"分",或者用 bcmath / gmp 扩展:
<?php
bcscale(2);
echo bcadd("19.90", "29.90"); // 49.80
字符串运算符:. 和 .=
<?php
$s = "Hello" . " " . "World"; // 拼接
$s .= "!"; // 追加
echo $s; // Hello World!
这里有一条 PHP 8.0 的重要变更:. 的优先级原来比 +/- 高,导致 "总和:" . 1 + 2 会算成 ("总和:" . 1) + 2。PHP 8.0 把 . 的优先级降到了比 +/- 低,所以现在 "x" . 1 + 2 会算成 "x" . (1 + 2)。如果你从 PHP 7 迁移代码,这里必须检查——以前能跑错的表达式现在会跑得不一样。
保险做法:拼接复杂的表达式时一律加括号。
赋值运算符
<?php
$a = 5;
$a += 3; // = 8
$a -= 3; // = 5
$a *= 2; // = 10
$a /= 2; // = 5
$a %= 3; // = 2
$a **= 3; // = 8
$a .= "x"; // = "8x"
$a ??= 10; // 只有当 $a 为 null 未赋值时才赋(PHP 7.4+)
??= 很实用,用来做"默认值只设一次":
<?php
$config['cache'] ??= 'redis';
比较运算符:这是 PHP 最难的一块
| 运算符 | 含义 |
|---|---|
== |
松散比较,会做类型转换 |
=== |
严格比较,类型 + 值 |
!= / <> |
松散不等 |
!== |
严格不等 |
<=> |
太空船,返回 -1 / 0 / 1(PHP 7+) |
?: |
三元缩写,$a ?: $b 等价于 $a ? $a : $b |
<=> 在排序回调里非常好用:
<?php
$users = [
['name' => 'bob', 'score' => 80],
['name' => 'alice', 'score' => 95],
['name' => 'carl', 'score' => 80],
];
usort($users, fn($x, $y) => $y['score'] <=> $x['score']); // 分数降序
一条口决记住:只要涉及用户数据、涉及业务判断、涉及 in_array/array_search,全部用 ===。松散比较只在"数值比较且确定类型"时用。
逻辑运算符:&& 与 and 的优先级差异
<?php
$result = true && false; // false
$result = true and false; // true ← 因为 = 的优先级高于 and!
= 的优先级比 and / or / xor 高,所以 $a = true and false 会被解析成 ($a = true) and false,$a 是 true。
这组运算符是给"控制流"用的,不是给赋值用的:
<?php
$fp = fopen($file, 'r') or die("打不开文件"); // 常见写法
我的建议:永远用 && / || / !,and/or 只有在上面这种刻意利用优先级的场景才用,且要加注释。
PHP 8 的 && / || 还带短路:false && foo() 不会执行 foo()。这可以用来做条件调用:
<?php
$user && sendMail($user); // 有 user 才发信
但注意 $user 必须是布尔语义,用 if 更清晰。
位运算
<?php
$a = 0b1100; // 12
$b = 0b1010; // 10
var_dump($a & $b); // 8 按位与
var_dump($a | $b); // 14 按位或
var_dump($a ^ $b); // 6 按位异或
var_dump(~$a); // -13 按位取反
var_dump($a << 1); // 24 左移
var_dump($a >> 1); // 6 右移
位运算最实际的用途是权限标志位:
<?php
const PERM_READ = 1 << 0; // 1
const PERM_WRITE = 1 << 1; // 2
const PERM_DELETE = 1 << 2; // 4
$perm = PERM_READ | PERM_WRITE; // 3
var_dump($perm & PERM_DELETE); // 0,没有删除权限
var_dump(($perm & PERM_READ) !== 0); // true
$perm &= ~PERM_WRITE; // 取消写权限
比存字符串数组省事得多,一个整数就装下所有标志。
错误控制符 @
<?php
$content = @file_get_contents($path); // 抑制警告
@ 会临时把错误报告级别降到 0。它能用,但不要滥用:它会连致命错误之外的所有提示一起吞掉,调试时非常难受。而且 PHP 8 之后 @ 只作用于"同一个表达式内",行为比旧版更窄。
更好的替代:
<?php
if (is_readable($path)) {
$content = file_get_contents($path);
}
执行运算符
反引号执行 shell 命令:
<?php
$out = `ls -l`;
echo $out;
等价于 shell_exec()。除非你非常清楚输入不可信会怎样,否则不要用——任何拼接进命令的变量都是命令注入。
递增递减
<?php
$a = 5;
echo $a++; // 5,先返回再加
echo $a; // 6
echo ++$a; // 7,先加再返回
$s = "a";
$s++; // "b" —— 字符串也能 ++(Perl 遗留行为)
$s = "z";
$s++; // "aa"
字符串自增是 PHP 的历史遗留特性,看着神奇,实际项目里基本是 bug 源。数字溢出的行为也不一致:PHP_INT_MAX++ 会变成 float。
null 安全运算符 ?->
PHP 8.0 引入,避免"对象可能为 null"的层层判断:
<?php
// 旧写法
$city = $user && $user->address ? $user->address->city : null;
// PHP 8
$city = $user?->address?->city; // 中间任一环为 null 就整体返回 null
比 isset() 链干净太多,这是 PHP 8 里最实用的一处语法加法。
注意它只能用在对象访问上,数组还得用 ??:
<?php
$city = $obj?->address?->city; // 对象链
$city = $arr['address']['city'] ?? null; // 数组链
match:比 switch 干净的表达式(PHP 8.0)
<?php
$code = 404;
$text = match (true) {
$code >= 500 => "服务端错误",
$code >= 400 => "客户端错误",
$code >= 300 => "重定向",
default => "其他",
};
match 与 switch 的三点本质差别:
match是表达式,直接有返回值,不用写break。match用严格比较(===),不会再出现"1"匹配到1的怪事。- 没有
default且没匹配上时,会抛UnhandledMatchError——这是它最大的优点:漏掉分支会立刻炸,而不是静默返回 null。
优先级速查(常犯错的三组)
<?php
// 1) 字符串拼接 vs 加减 —— PHP 8 已改,但仍建议加括号
echo "结果:" . (1 + 2); // 明确
// 2) 三元嵌套 —— 从 PHP 8 起必须加括号,否则语法错误
$v = $a ? $b : ($c ? $d : $e); // 合法
// $v = $a ? $b : $c ? $d : $e; // PHP 8 报 ParseError
// 3) 赋值 vs and/or
$ok = foo() or bar(); // 先赋值,再判断 → 等价 ($ok = foo()) or bar()
最稳的做法不是背优先级表,是加括号。括号不花钱,读代码的人省十分钟。
坑的清单
0.1 + 0.2 != 0.3,金额别用 float。$a = true and false,$a是 true。- PHP 8 里
.与+的优先级反转,迁移必查。 "1" == "01"是 true,字符串数字比较会转数值。- 除零在 PHP 8 抛异常,不再返回 false。
@抑制不等于错误被处理,只是被吞了。