Skip to content

表达式基础

表达式是 PHP 最重要的构建部分。在 PHP 中,几乎所写的任何东西都是表达式。理解表达式的概念、类型和求值规则,是掌握 PHP 编程语言的关键。

前置知识

  • 已掌握 PHP 的基本语法、变量和常量
  • 了解基本的运算符概念
  • 对编程语言的表达式有初步认识

基础概念

表达式最简单而精确的定义就是:"任何有值的东西"。在 PHP 中,常量、变量、函数调用、运算组合,甚至赋值语句本身,都是表达式。

表达式与语句的区别

  • 表达式(Expression):有值的代码单元,如 $a + $b$x = 5
  • 语句(Statement):以分号 ; 结尾的完整执行单元,如 $a = 5;

表达式可以成为语句的一部分,但不是所有语句都是表达式:

php
<?php
declare(strict_types=1);

// 表达式
5;          // 整数字面量,值为 5
$name;      // 变量,值为变量的内容
3 + 4;      // 算术表达式,值为 7
true;       // 布尔常量,值为 true

// 语句(表达式加分号)
$a = 5;      // 赋值语句
echo $a;    // 输出语句

常量表达式

最简单的表达式是常量和变量。它们有确定的值:

php
<?php
declare(strict_types=1);

// 字面量都是表达式
$integer = 42;          // 值为 42
$float = 3.14;          // 值为 3.14
$string = "Hello";       // 值为 "Hello"
$boolean = true;         // 值为 true
$null = null;           // 值为 null
$array = [1, 2, 3];    // 值为数组

// 命名常量也是表达式
echo PHP_INT_MAX;       // 值为最大整数值
echo PHP_VERSION;        // 值为版本字符串

变量表达式

变量本身就是表达式,其值为变量的当前内容:

php
<?php
declare(strict_types=1);

$a = 5;
$b = $a;      // $a 是值为 5 的表达式,赋值给 $b

echo $b;      // 5

// 变量可以作为更复杂表达式的一部分
$c = $a + $b; // 两个变量表达式通过运算符组合
echo $c;      // 10

赋值表达式

PHP 是面向表达式的语言

PHP 中赋值操作本身也是表达式。赋值表达式的值就是被赋的值。这意味着 $a = 5 既是赋值语句,也是值为 5 的表达式。

php
<?php
declare(strict_types=1);

// 赋值表达式有值
$a = 5;
$b = ($a = 10);   // $a 被赋值为 10,然后 $b 也被赋值为 10
echo $a . "\n";   // 10
echo $b . "\n";   // 10

// 链式赋值(从右到左)
$c = $d = $e = 100;
echo $c . "\n";   // 100
echo $d . "\n";   // 100
echo $e . "\n";   // 100

函数调用表达式

函数调用也是表达式,其值为函数的返回值:

php
<?php
declare(strict_types=1);

function double(int $value): int
{
    return $value * 2;
}

// 函数调用是值为返回值的表达式
$result = double(5);
echo $result . "\n"; // 10

// 函数调用可以直接用在更复杂的表达式中
$combined = double(3) + double(7);
echo $combined . "\n"; // 20(6 + 14)

详细说明

比较表达式

比较运算符产生的表达式求值为布尔值 truefalse

php
<?php
declare(strict_types=1);

$comparison = (5 > 3);     // true
echo $comparison ? "yes" : "no"; // yes

// 比较运算符
echo (5 === 5) ? "equal" : "not equal";       // equal
echo (5 !== "5") ? "not identical" : "identical"; // not identical
echo (10 >= 10) ? "gte" : "lt";                // gte
echo ("hello" === "world") ? "same" : "diff";   // diff

递增/递减表达式

递增和递减是两种特殊的表达式,它们有副作用(修改变量值),并且前递增和后递增的表达式值不同:

php
<?php
declare(strict_types=1);

// 前递增:先递增再返回新值
$a = 5;
$b = ++$a;    // $a 先变为 6,然后 $b = 6
echo $a . "\n"; // 6
echo $b . "\n"; // 6

// 后递增:先返回原值再递增
$c = 5;
$d = $c++;    // $d = 5(原值),然后 $c 变为 6
echo $c . "\n"; // 6
echo $d . "\n"; // 5

// 递减同理
$e = 10;
$f = --$e;    // $e = 9, $f = 9
echo $e . "\n"; // 9
echo $f . "\n"; // 9

$g = 10;
$h = $g--;    // $h = 10, $g = 9
echo $g . "\n"; // 9
echo $h . "\n"; // 10

三元条件表达式

三元运算符 ? : 是一种简洁的条件表达式:

php
<?php
declare(strict_types=1);

// 基本三元表达式
$age = 20;
$status = ($age >= 18) ? "adult" : "minor";
echo $status . "\n"; // adult

// PHP 7.0+ 空合并运算符 ??
$username = $_GET["username"] ?? "Guest";
echo $username . "\n"; // Guest(如果未提供 username)

// PHP 7.4+ 可以省略中间部分(Elvis 运算符风格)
$value = null ?: "default";
echo $value . "\n"; // default

PHP 8.0 三元运算符变更

PHP 8.0 起,嵌套的三元运算符变为非关联的。未加括号的嵌套三元表达式会抛出致命错误:

php
<?php
declare(strict_types=1);

// PHP 8.0+ 必须使用括号
$a = true ? 0 : (true ? 1 : 2);  // 正确

// 以下在 PHP 8.0+ 中报错
// $a = true ? 0 : true ? 1 : 2; // Fatal error

组合赋值运算符

组合赋值运算符是赋值与运算的结合,它们也是表达式:

php
<?php
declare(strict_types=1);

$a = 10;
$b = ($a += 5);     // $a = $a + 5 = 15,表达式值为 15
echo $a . "\n";     // 15
echo $b . "\n";     // 15

$c = 20;
$d = ($c *= 2);     // $c = 40,表达式值为 40
echo $c . "\n";     // 40
echo $d . "\n";     // 40

// 字符串拼接
$s = "Hello";
$t = ($s .= " World"); // $s = "Hello World",表达式值为 "Hello World"
echo $s . "\n";     // Hello World
echo $t . "\n";     // Hello World

短路求值

逻辑运算符 &&(和)和 ||(或)支持短路求值——如果第一个操作数已经可以确定整个表达式的结果,则不会计算第二个操作数:

php
<?php
declare(strict_types=1);

// || 短路:如果左侧为 true,右侧不执行
$cache = null;
$result = ($cache !== null) || loadCache();
// loadCache() 不会被调用

// && 短路:如果左侧为 false,右侧不执行
function validate(): bool
{
    echo "Validating...\n";
    return true;
}

$isValid = false && validate();
// validate() 不会被调用,没有输出

// 短路求值的实用模式
$user = null;
$name = $user ?? "Guest";     // $user 为 null,返回 "Guest"
echo $name . "\n";             // Guest

$items = [];
$count = $items[0] ?? 0;      // 索引不存在,返回 0
echo $count . "\n";            // 0

实战示例

复杂表达式的求值顺序

php
<?php
declare(strict_types=1);

function double(int $i): int
{
    return $i * 2;
}

// 多个表达式的组合
$b = $a = 5;            // $a = 5, $b = 5
$c = $a++;              // $c = 5(后递增),$a 变为 6
$e = $d = ++$b;         // $b = 6(前递增),$d = 6, $e = 6
$f = double($d++);      // $f = 12(2*6),$d 变为 7
$g = double(++$e);      // $e = 7(前递增),$g = 14(2*7)
$h = $g += 10;          // $g = 24(14+10),$h = 24

echo "a={$a}, b={$b}, c={$c}, d={$d}, e={$e}, f={$f}, g={$g}, h={$h}\n";

表达式在条件判断中的应用

php
<?php
declare(strict_types=1);

// 表达式在 if 中的使用
function checkUser(array $user): string
{
    // 多个条件用 && 和 || 组合
    if (
        isset($user["name"]) && !empty($user["name"])
        && isset($user["email"]) && filter_var($user["email"], FILTER_VALIDATE_EMAIL)
    ) {
        $role = $user["role"] ?? "viewer";
        return match ($role) {
            "admin" => "Welcome, Administrator",
            "editor" => "Welcome, Editor",
            default => "Welcome, {$user["name"]}",
        };
    }

    return "Invalid user data";
}

$user = ["name" => "Alice", "email" => "alice@example.com", "role" => "editor"];
echo checkUser($user) . "\n"; // Welcome, Editor

or 和 die() 模式

php
<?php
declare(strict_types=1);

// 利用 || 的短路特性实现简洁的错误处理
$file = fopen("/tmp/data.txt", "r")
    or die("Cannot open file: " . error_get_last()["message"]);

// 等价于
if (!$file = fopen("/tmp/data.txt", "r")) {
    die("Cannot open file: " . error_get_last()["message"]);
}

// 更现代的写法:使用异常处理
try {
    $content = file_get_contents("/tmp/data.txt");
    if ($content === false) {
        throw new RuntimeException("Failed to read file");
    }
    echo "File content length: " . strlen($content) . "\n";
} catch (RuntimeException $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

注意事项

1. 表达式求值顺序不保证

PHP 不保证子表达式的求值顺序。不要编写依赖特定求值顺序的代码:

php
<?php
declare(strict_types=1);

// 危险:求值顺序未定义
$a = 1;
echo $a + $a++;  // 可能输出 2 或 3

// 安全:分步执行
$a = 1;
$temp = $a;
$a++;
echo $temp + $a; // 明确输出 3

2. 避免在表达式中产生副作用

在复杂表达式中使用递增/递减运算符容易产生难以追踪的 bug:

php
<?php
declare(strict_types=1);

// 危险:难以理解的副作用
$n = 3;
$m = $n * --$n;  // 结果是 4(2 * 2),而不是 6(3 * 2)
echo $m . "\n";

// 推荐:分步执行,清晰明确
$n = 3;
$n--;            // $n = 2
$m = $n * $n;    // $m = 4
echo $m . "\n";

3. echo 不是表达式

echo 是语言结构(不是函数),没有返回值,不能用在需要表达式的地方:

php
<?php
declare(strict_types=1);

// 错误:echo 不能用在表达式中
// $result = ($condition) ? echo("yes") : echo("no");

// 正确:echo 不是表达式,但 print 是
$result = ($condition) ? print("yes") : print("no");

最佳实践

  1. 表达式保持简洁:复杂表达式拆分为多行,提高可读性
  2. 避免副作用表达式:在复杂表达式中不使用递增/递减
  3. 使用括号明确优先级:即使不需要,也能提高可读性
  4. 利用短路求值:用 ?? 提供默认值,用 && 做前置检查
  5. 三元表达式适度使用:简单条件用三元,复杂条件用 if/else
  6. 变量先声明再使用:避免未定义变量表达式
  7. 类型声明增强安全:配合 declare(strict_types=1) 使用

下一节

理解了表达式的基本概念之后,接下来将深入学习表达式的求值规则,包括类型自动转换、运算符优先级、短路求值以及 truthy/falsy 值的判定。请阅读 表达式求值

参考链接