Skip to content

运算符优先级

运算符优先级决定了表达式中不同运算符的计算顺序。优先级高的运算符先执行,优先级相同时由结合方向决定。理解优先级规则对于编写正确且可读的表达式至关重要。

前置知识

  • 已掌握 表达式基础 的概念
  • 了解 PHP 的基本运算符(算术、比较、逻辑等)
  • 熟悉括号在表达式中的作用

基础概念

当表达式中包含多个运算符时,PHP 需要确定计算顺序。运算符优先级就是决定"先算什么、后算什么"的规则。例如 1 + 5 * 3 的结果是 16 而不是 18,因为乘法 * 的优先级比加法 + 高。

优先级与结合方向

  • 优先级:高优先级的运算符先执行
  • 结合方向:相同优先级时,决定从左到右还是从右到左计算
  • 括号:始终可以用括号强制改变计算顺序

完整优先级表

下表按优先级从高到低排列,同一行中的运算符具有相同优先级:

优先级结合方向运算符类别
1(最高)不适用clone new克隆和实例化
2**算术(求幂)
3不适用++ -- ~ (int) (float) (string) (array) (object) (bool) @ + -递增/递减、类型转换、一元运算
4instanceof类型检查
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输出
24and逻辑与(低优先级)
25xor逻辑异或(低优先级)
26or逻辑或(低优先级)
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 对运算符优先级进行了两项重要变更:

  1. 字符串连接符 . 优先级降低,低于算术加减和位移
  2. 三元运算符 ? : 变为非关联

字符串连接符优先级变更

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 = 2

and/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 => true

3. 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

最佳实践

  1. 使用括号消除歧义:即使不需要,括号也能提高可读性
  2. 避免依赖 and/or 的优先级:使用 &&/|| 替代
  3. 位运算使用括号:位运算结果用于比较时始终加括号
  4. 嵌套三元加括号:PHP 8.0+ 强制要求
  5. 字符串连接使用括号:涉及算术运算时明确顺序
  6. 优先使用 ??:比冗长的 isset() ? ... : ... 更清晰
  7. 遵循 PSR-12 格式:复杂表达式拆分为多行

下一节

理解了运算符优先级之后,接下来将学习最基础的算术运算符——加、减、乘、除、取模和求幂。请阅读 算术运算符

参考链接