PHP的&

发布于:2025-06-29 ⋅ 阅读:(14) ⋅ 点赞:(0)

环境

  • PHP 8.4.5
  • Windows 11 专业版

问题

看下面的PHP代码:

$arr1 = [1, 2, 3];

foreach($arr1 as &$v) { // 这里加上&,是为了修改原数组的值
    $v++;
}

// 其它代码......

foreach($arr1 as $v) {
    echo $v;
}

代码分析:

  1. 初始化数组 arr1
  2. 遍历数组,给每个元素加1。这里 &$v 中的 & 表示引用,也就是说, $v 指向的是实际的数组元素,这是因为需要修改数组元素的值,如果不加 & ,则是复制了一份(对 $v 的修改不影响数组)
  3. 遍历数组,打印每个元素的值

看起来简单直观,期望的输出结果是 234

然而,实际输出结果却是 233

分析

和很多其它语言一样,PHP在循环结束后,并不会自动清除循环变量。

比如:在Java语言里:

        for (; i <= 10; i++) {
            ......
        }
        
        System.out.println("i = " + i);

在循环结束后,允许访问变量 i ,此时其值为 11

Delphi (Object Pascal)语言也类似:

    for i := 1 to 10 do
    begin
        ......
    end;
    writeln('i = ', i);

在循环结束后,允许访问变量 i ,此时其值为 11 ,不过编译器会给出一个警告。

回到PHP,在上面的代码里,第一个循环里用的是 &$v ,在循环结束后,数组变成了 2, 3, 4 。注意,此时 $v 指向数组最后一个元素(其值为 4 )。

在第二个循环里,每次迭代时会给 $v 赋值,但是别忘了, $v 指向的是数组最后一个元素,所以,给 $v 赋值,就会覆盖数组的最后一个元素。

  • 在第一次迭代时,数组变成了 2, 3, 2 (把第一个元素值赋给 $v ,覆盖最后一个元素)
  • 在第二次迭代时,数组变成了 2, 3, 3 (把第二个元素值赋给 $v ,覆盖最后一个元素)
  • 在第三次迭代时,数组仍然是 2, 3, 3 (把最后一个元素值赋给 $v ,覆盖最后一个元素)

解决方法

有两种解决方法:

  • 在第一次循环结束后,使用 unset($v) 来清理变量,也就是说,第二个循环里的 $v 是一个全新的变量
  • 在第二次循环里换个变量名

最好是两种方法都用。

当然,如果第一个循环里没用 & ,那么第二个循环里继续使用 $v 也没问题。

考一考

下面的代码,输出结果是什么?

$arr1 = [1, 2, 3];

foreach($arr1 as &$v) { // 这里加上&,是为了修改原数组的值
    $v++;
}

foreach($arr1 as &$v) {
    echo $v;
}

答案:输出结果是:

234

这里的输出结果,反而是期望的正确结果。

这是因为,在第二个循环里也用了引用 & ,所以每次迭代时, $v 会正确的指向每个元素。

总结

加不加 & ,本质的区别在于:

  • 不加 & 时:复制。在第一个例子中,会把迭代的元素值“复制”到 $v (注意 $v 指向了数组最后一个元素)
  • 加上 & 时:不复制,而是把 $v 改为指向迭代的元素。

换句话说:

  • 不加 & 时, $v 的内存地址不会变化,并把迭代的元素值复制过来:
    • 如果 $v 变量已经存在,则继续用它(第一个例子就是这种情况)
    • 如果 $v 变量不存在,则创建一个全新变量,后续迭代时,其内存地址不再变化
  • 加上 & 时, $v 不管是不是全新的变量,都会随着每次迭代,将其内存地址动态指向迭代的元素 ( $v 没有开辟另外的内存地址)。

何时使用&

原则:能不用 & 就不用,因为它增加了复杂度,可能会引起潜在的不易发现的问题。

比如一个函数的定义为 function func1($arr1) ,那我就可以放心的传一个数组进去,而不用担心该函数会修改我的数组。

& 常见的使用场景如下:

循环里修改元素值

foreach($arr1 as &$v) {
    $v++;
}

unset($v);

函数内修改参数值

function swap(&$a, &$b) {
    $tmp = $a;
    $a = $b;
    $b = $tmp;
}

注:如果没记错,Pascal里,带不带 & ,分别叫做“值参”和“变参”。

function func1(&$arr1) { // 如果不带 & ,整个数组会被复制一份
    ......
    $arr1[0]++;
}

注:PHP 5+ 对写时复制(Copy-On-Write)有优化,也就是说,即使是传值,也只有在确实有修改时,才会复制。因此,多数情况下,无需因为考虑性能而使用传引用的方式。

多个变量指向同一个内存地址

$a = 1;
$b = &$a; // $b 和 $a 指向同一个值
$b = 2;
echo $a; // 输出 2($a 也被修改)

函数返回引用

function &getSingleton() {
    static $instance = null;
    if ($instance === null) {
        $instance = new HeavyObject();
    }
    return $instance; // 返回引用,避免复制
}
$obj = &getSingleton(); // 必须用 & 接收

在链式API中,也经常需要返回原来的对象,而不能复制一个新对象。