PHP 循环和&引用陷阱
前言
很久之前给电商公司写过一个计算销售人员绩效的方法,那复杂度,懂的都懂。实现时就发生了对同一个数组多次操作并改值的需求,也踩到了PHP作为弱类型语言和引用相关的坑,花了好久才找到问题。今日就做个记录。
陷阱的简单描述
- PHP的循环,没有单独的生命周期,循环产生的变量,在循环结束后不会被释放。
- 循环产生的值变量,如果与已存在的引用变量同名,会给对应的引用变量赋值。
现象
我们先看一段代码,可以根据描述思考下,这段代码的输出是什么?
代码
$a = [1, 2]; $b = [3, 4]; $i = &$a[0]; foreach ($a as &$c) {} echo 'i: ', var_dump($i); echo 'c: ', var_dump($c); echo 'a: ', var_dump($a); foreach ($b as $i => $c) { echo $i . ' changed a: ', var_dump($a); } echo 'b end,changed a: ', var_dump($a);
输出
i: int(1) c: int(2) a: array(2) { [0]=> &int(1) [1]=> &int(2) } 0 changed a: array(2) { [0]=> &int(0) [1]=> &int(3) } 1 changed a: array(2) { [0]=> &int(1) [1]=> &int(4) } b end,changed a: array(2) { [0]=> &int(1) [1]=> &int(4) }
处理思路
由于这是源码带来的特性,所以解决思路并不是修复它,而是规避它。
- 一、尽量避免使用同名的变量,即便是在循环中的“临时变量”。
- 二、循环完毕后,使用
unset()
释放循环产生的变量。
这两个特性当然也可以利用起来实现某些特殊的需求,比如:
foreach($arr as $i => $item) {}
循环后,$i
可以当做count($arr)-1
来使用,$item
可以当做end($arr)
来使用。这会节省一些开销。- 当需要保留对
end($arr)
的引用时。 - 当需要覆盖之前的某个变量时。
这其实类似于在switch/case
中,特意不使用break
一样。可以根据特定的场景进行使用,但可读性较差,知其然,谨慎选择。