PHP的
文章目录
- 环境
- 问题
- 分析
- 解决方法
- 考一考
- 总结
- 何时使用&
- 循环里修改元素值
- 函数内修改参数值
- 多个变量指向同一个内存地址
- 函数返回引用
环境
- PHP 8.4.5
- Windows 11 专业版
问题
看下面的PHP代码:
$arr1 = [1, 2, 3];foreach($arr1 as &$v) { // 这里加上&,是为了修改原数组的值$v++;
}// 其它代码......foreach($arr1 as $v) {echo $v;
}
代码分析:
- 初始化数组
arr1
- 遍历数组,给每个元素加1。这里
&$v
中的&
表示引用,也就是说,$v
指向的是实际的数组元素,这是因为需要修改数组元素的值,如果不加&
,则是复制了一份(对$v
的修改不影响数组) - 遍历数组,打印每个元素的值
看起来简单直观,期望的输出结果是 234
。
然而,实际输出结果却是 233
。
分析
和很多其它语言一样,PHP在循环结束后,并不会自动清除循环变量。
比如:在Java语言里:
for (; i <= 10; i++) {......}System.out.println("i = " + i);
在循环结束后,允许访问变量 i
,此时其值为 11
。
Delphi (Object Pascal)语言也类似:
for i := 1 to 10 dobegin......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中,也经常需要返回原来的对象,而不能复制一个新对象。