运算符优先级
运算符优先级决定了表达式中不同运算符的计算顺序。优先级高的运算符先执行,优先级相同时由结合方向决定。理解优先级规则对于编写正确且可读的表达式至关重要。
前置知识
- 已掌握 表达式基础 的概念
- 了解 PHP 的基本运算符(算术、比较、逻辑等)
- 熟悉括号在表达式中的作用
基础概念
当表达式中包含多个运算符时,PHP 需要确定计算顺序。运算符优先级就是决定"先算什么、后算什么"的规则。例如 1 + 5 * 3 的结果是 16 而不是 18,因为乘法 * 的优先级比加法 + 高。
优先级与结合方向
- 优先级:高优先级的运算符先执行
- 结合方向:相同优先级时,决定从左到右还是从右到左计算
- 括号:始终可以用括号强制改变计算顺序
完整优先级表
下表按优先级从高到低排列,同一行中的运算符具有相同优先级:
| 优先级 | 结合方向 | 运算符 | 类别 |
|---|---|---|---|
| 1(最高) | 不适用 | clone new | 克隆和实例化 |
| 2 | 右 | ** | 算术(求幂) |
| 3 | 不适用 | ++ -- ~ (int) (float) (string) (array) (object) (bool) @ + - | 递增/递减、类型转换、一元运算 |
| 4 | 左 | instanceof | 类型检查 |
| 5 | 不适用 | ! | 逻辑非 |
| 6 | 左 | * / % | 算术 |
| 7 | 左 | + - . | 算术和字符串(PHP 8.0+ . 优先级降低) |
| 8 | 左 | << >> | 位运算 |
| 9 | 左 | . | 字符串连接(PHP 8.0+ 新行) |
| 10 | 不适用(管道) | |> | 管道(PHP 8.4+) |
| 11 | 无关联 | < <= > >= | 比较 |
| 12 | 无关联 | == != === !== <> <=> | 比较 |
| 13 | 左 | & | 位运算和引用 |
| 14 | 左 | ^ | 位运算 |
| 15 | 左 | | | 位运算 |
| 16 | 左 | && | 逻辑与 |
| 17 | 左 | || | 逻辑或 |
| 18 | 右 | ?? | 空合并 |
| 19 | 无关联(PHP 8.0+) | ? : | 三元 |
| 20 | 右 | = += -= *= **= /= .= %= &= |= ^= <<= >>= ??= | 赋值 |
| 21 | 不适用 | yield from | 生成器 |
| 22 | 不适用 | yield | 生成器 |
| 23 | 不适用 | print | 输出 |
| 24 | 左 | and | 逻辑与(低优先级) |
| 25 | 左 | xor | 逻辑异或(低优先级) |
| 26 | 左 | or | 逻辑或(低优先级) |
| 27(最低) | 不适用 | throw | 异常(PHP 8.0+ 表达式) |
基本优先级示例
算术运算符优先级
php
<?php
declare(strict_types=1);
// 乘法优先级高于加法
echo 1 + 5 * 3 . "\n"; // 16(先算 5*3=15,再 1+15=16)
// 指数优先级高于乘法
echo 2 * 3 ** 2 . "\n"; // 18(先算 3**2=9,再 2*9=18)
// 取模与乘除同级(左结合)
echo 3 * 3 % 5 . "\n"; // 4(先算 3*3=9,再 9%5=4)
// 使用括号改变顺序
echo (1 + 5) * 3 . "\n"; // 18
echo 2 * (3 ** 2) . "\n"; // 18(虽与不加括号结果相同,但更清晰)比较与逻辑运算符优先级
php
<?php
declare(strict_types=1);
// 比较优先级高于赋值
$a = 3;
$b = ($a > 2); // true
var_dump($b); // bool(true)
// 逻辑运算符的优先级
// ! > && > || > and > or
$result = true || true && false;
var_dump($result); // bool(true)
// 等价于: true || (true && false)
// 因为 && 优先级高于 ||
// 括号改变逻辑
$result = (true || true) && false;
var_dump($result); // bool(false)详细说明
PHP 8.0 的优先级变更
重要变更
PHP 8.0 对运算符优先级进行了两项重要变更:
- 字符串连接符
.优先级降低,低于算术加减和位移 - 三元运算符
? :变为非关联
字符串连接符优先级变更
php
<?php
declare(strict_types=1);
$x = 4;
// PHP 8.0+:. 优先级低于 + -
// 等价于:"x minus one equals " . ($x - 1)
echo "x minus one equals " . $x - 1 . "\n";
// 输出: x minus one equals 3
// PHP 7.x 中:. 和 + 同优先级(左结合)
// 等价于:(("x minus one equals " . $x) - 1)
// 输出: -1, or so I hope
// 推荐做法:始终使用括号
echo "x minus one equals " . ($x - 1) . "\n";三元运算符变为非关联
php
<?php
declare(strict_types=1);
// PHP 8.0+ 必须用括号消除歧义
$a = true ? 0 : (true ? 1 : 2);
var_dump($a); // int(0)
// PHP 8.0+ 嵌套三元必须加括号
$b = true ? 0 : (false ? 1 : 2);
var_dump($b); // int(0)
$c = (true ? 0 : true) ? 1 : 2;
var_dump($c); // int(1)
// PHP 7.x:左关联(不推荐依赖此行为)
// $d = true ? 0 : true ? 1 : 2;
// 等价于:(true ? 0 : true) ? 1 : 2 = 2and/or vs &&/|| 的优先级差异
这是一个最容易导致 bug 的优先级问题:
php
<?php
declare(strict_types=1);
// && 优先级高于 =
$a = true && false;
var_dump($a); // bool(false)
// 解析为:$a = (true && false)
// and 优先级低于 =
$b = true and false;
var_dump($b); // bool(true)
// 解析为:($b = true) and false
// $b 被赋值为 true,然后 false 被 and 忽略
// || 优先级高于 =
$c = false || true;
var_dump($c); // bool(true)
// 解析为:$c = (false || true)
// or 优先级低于 =
$d = false or true;
var_dump($d); // bool(false)
// 解析为:($d = false) or true
// $d 被赋值为 false,然后 true 被 or 忽略?? 的优先级
??(空合并运算符)的优先级低于大多数运算符,但高于三元和赋值:
php
<?php
declare(strict_types=1);
// ?? 优先级低于比较运算符
$a = [];
$b = $a['aa'] ?? 'not set';
echo $b . "\n"; // 'not set'
// 但在字符串连接中,?? 的优先级需要注意
// 错误:. 的优先级高于 ??
// "text" . $a['aa'] ?? 'not set'
// 被解析为:("text" . $a['aa']) ?? 'not set'
// 可能导致 undefined index 警告
// 正确:使用括号
$c = "text" . ($a['aa'] ?? 'not set');
echo $c . "\n"; // textnot set位运算与比较运算符的优先级陷阱
常见陷阱
位运算符 &、^、| 的优先级低于比较运算符 ==、!= 等。这常导致意外的结果:
php
<?php
declare(strict_types=1);
$flags = 0b1101; // 二进制:1101(13)
$mask = 0b1001; // 二进制:1001(9)
// 错误:& 优先级低于 ==
// $flags & $mask == 1 被解析为 $flags & ($mask == 1)
// $mask == 1 的结果为 false(整型0),所以 $flags & 0 = 0
var_dump($flags & $mask == 1); // int(0)
// 正确:使用括号
var_dump(($flags & $mask) == 1); // int(1)
// 1101 & 1001 = 1001 = 9,9 != 1,所以 false
var_dump(($flags & $mask) == 9); // bool(true)实战示例
条件表达式的正确写法
php
<?php
declare(strict_types=1);
// 类型转换与三元运算符的优先级
$numStr = "5";
// 错误:(int) 的优先级高于 ? :
$i1 = (int) isset($numStr) ? $numStr : 0;
var_dump($i1); // string(1) "5"(不是 int!)
// 解析为:((int) isset($numStr)) ? $numStr : 0
// 正确:使用括号
$i2 = (int) (isset($numStr) ? $numStr : 0);
var_dump($i2); // int(5)实际项目中的优先级处理
php
<?php
declare(strict_types=1);
class PermissionChecker
{
public function __construct(
private readonly array $permissions = []
) {
}
/**
* 检查权限
*
* 注意:使用括号确保位运算在比较之前执行
*/
public function hasPermission(int $required, int $userPermissions): bool
{
// 正确:(userPermissions & required) === required
return ($userPermissions & $required) === $required;
}
public function hasAnyPermission(int $required, int $userPermissions): bool
{
return ($userPermissions & $required) !== 0;
}
}
// 定义权限常量
const PERM_READ = 1; // 0001
const PERM_WRITE = 2; // 0010
const PERM_DELETE = 4; // 0100
const PERM_ADMIN = 8; // 1000
$checker = new PermissionChecker();
$userPerms = PERM_READ | PERM_WRITE; // 0011(读+写)
echo "Can read: " . ($checker->hasPermission(PERM_READ, $userPerms) ? "Yes" : "No") . "\n";
echo "Can write: " . ($checker->hasPermission(PERM_WRITE, $userPerms) ? "Yes" : "No") . "\n";
echo "Can delete: " . ($checker->hasPermission(PERM_DELETE, $userPerms) ? "Yes" : "No") . "\n";
echo "Can admin: " . ($checker->hasPermission(PERM_ADMIN, $userPerms) ? "Yes" : "No") . "\n";数组访问与运算符优先级
php
<?php
declare(strict_types=1);
$config = [
"database" => [
"host" => "localhost",
"port" => 3306,
],
];
// [] 的优先级非常高,不需要额外括号
$host = $config["database"]["host"] ?? "127.0.0.1";
echo $host . "\n"; // localhost
// 函数调用与 ?? 的组合
function getDbConfig(): ?array
{
return ["host" => "db.example.com"];
}
$dbHost = getDbConfig()["host"] ?? "localhost";
echo $dbHost . "\n"; // db.example.com注意事项
1. 未定义的执行顺序
PHP 不保证子表达式的求值顺序。不要编写依赖特定执行顺序的代码:
php
<?php
declare(strict_types=1);
// 危险:$a 和 $a++ 的求值顺序未定义
$a = 1;
echo $a + $a++; // 可能输出 2 或 3
$i = 1;
$array[$i] = $i++; // 可能设置索引 1 或 2
// 安全:分步执行
$a = 1;
$prev = $a;
$a++;
echo $prev + $a; // 3(明确)2. 无关联运算符不能连用
相同优先级且无关联的运算符不能连在一起使用:
php
<?php
declare(strict_types=1);
// 错误:无关联运算符不能连用
// 1 < 2 > 1 // Parse error
// 正确:使用 <= 和 == 组合
var_dump(1 <= 1 == 1); // bool(true)
// 因为 == 的优先级低于 <=
// 解析为:(1 <= 1) == 1 => true == 1 => true3. print 的优先级
print 的优先级比赋值 = 低,但比 and/or 高:
php
<?php
declare(strict_types=1);
// print 返回 1,可以用于表达式
$flag = print("Hello\n"); // Hello, $flag = 1
echo "Flag: {$flag}\n";
// 优先级:print > and
// print("test") and false
// 解析为:(print("test")) and false最佳实践
- 使用括号消除歧义:即使不需要,括号也能提高可读性
- 避免依赖
and/or的优先级:使用&&/||替代 - 位运算使用括号:位运算结果用于比较时始终加括号
- 嵌套三元加括号:PHP 8.0+ 强制要求
- 字符串连接使用括号:涉及算术运算时明确顺序
- 优先使用
??:比冗长的isset() ? ... : ...更清晰 - 遵循 PSR-12 格式:复杂表达式拆分为多行
下一节
理解了运算符优先级之后,接下来将学习最基础的算术运算符——加、减、乘、除、取模和求幂。请阅读 算术运算符。