PHP - foreach 使用空合并运算符丢失引用

PHP - foreach lose reference with null coalescing operator

Q1: 我认为 ?? 在以下情况下不会执行任何操作:

$a = [1, 2];
foreach ($a ?? [] as &$v) {
    $v++;
}
var_dump($a);

但是为什么呢?

array(2) {
  [0]=>
  int(1)
  [1]=>
  int(2)
}

Q2: 这个比较奇怪:

foreach ($a = [1, 2] as &$v) {
    $v++;
}
var_dump($a);
// output
array(2) {
  [0]=>
  int(1)
  [1]=>
  int(2)
}

我的想法: 我认为这些表达式不可引用,但 foreach 捕获错误或以某种方式复制。 有效的参考资料:

$a = 1;
$c = &$a;

不工作:

$a = 1;
$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);

dos ?? 复制一份?如果 $a 为空并且 foreach 将失败,我只是不想用 if (isset($a)) 包装 foreach

您可以使用扩展数组语法获取索引,然后使用它来取消引用原始数组值:

$a = [1, 2];
foreach ($a ?? [] as $i => $v) {
    ++$a[$i];
}
var_dump($a);

但请注意,无论如何这可能都没用,因为如果未设置 $a(以便 ?? 符合条件),则循环将进行零次迭代并且 $a 仍将为 var_dump() 取消设置。 (除非那是你需要的,我想......)

I just dont want to wrap the foreach with a if (isset($a)) if $a is null and foreach will fail.

如果您没有将变量初始化为正确的类型,这是不可避免的,但是在使用按引用传递、return 按引用和默认值的实用函数中有一些技巧:

function &test(&$array=[]) {
    return $array;
}

$a = [1, 2];

foreach (test($a) as &$v) {
    $v++;
}

如果未设置 $a 并且不循环,则不会生成错误,但是在上面它会产生:

array(2) {
  [0]=>
  int(2)
  [1]=>
  &int(3)
}

在PHP中数组按值赋值(赋值副本),因此如果$a !== null$a ?? []returns $a的值或[1, 2]。因此 $a 不会被使用 &$v.

对这个值的值的引用修改

对象是通过引用分配的,因此在这种情况下引用被 returned 并且原始对象被修改,除非 $a 未设置。那么你显然会得到:

Notice: Undefined variable: a

$a = (object)[1, 2];

foreach ($a ?? [] as &$v) {
    $v++;
}
var_dump($a);

这产生:

object(stdClass)#1 (2) {
  ["0"]=>
  int(2)
  ["1"]=>
  &int(3)
}

来自 Assignment by Reference:

Assignment by reference is also supported, using the "$var = &$othervar;" syntax. Assignment by reference means that both variables end up pointing at the same data, and nothing is copied anywhere.

在这些情况下,它们都是表达式,无法引用:

$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);

如果这只是一个很好的练习,但如果您要尝试解决特定问题,那么您需要概述更广泛的问题。

TL;DR 对于您的情况,您可以考虑以这种方式使用空合并运算符:

$a = $a ?? [];
foreach ($a as &$v) { ... }

或者,根本不使用引用,方法是使用 array_map() 或使用键在基础数组中进行修改。

Q1

$a = [1, 2];
foreach ($a ?? [] as &$v) {
    $v++;
}
var_dump($a);

合并运算符使用原始数组的副本,然后如果 null 应用右侧操作数。因此,迭代发生在原始数组的副本上。

您可以将其与以下内容进行比较:

$a = [1, 2];
$x = $a ?? [];
$x[1] = 4;
var_dump($a); // [1, 2]

代码洞察力

compiled vars:  !0 = $a, !1 = $v
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   8     0  E >   ASSIGN                                                   !0, <array>
   9     1        COALESCE                                         ~3      !0
         2        QM_ASSIGN                                        ~3      <array>
         3      > FE_RESET_RW                                            ~3, ->8
... rest of looping code

FE_RESET_RW的第一个操作数是将被迭代的散列变量,你可以看到它是~3而不是!0(你的$a代码),这正是您所期望的。

Q2

foreach ($a = [1, 2] as &$v) {
    $v++;
}

这里发生的是赋值 $a = [1, 2] 的 return 值被用作要迭代的数组。

您可以将此行为与以下内容进行比较:

$x = $a = [1, 2];
$x[0] = 4; // modify in-place
var_dump($a); // [1, 2]

代码洞察力

compiled vars:  !0 = $a, !1 = $v
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   ASSIGN                                                 !0, <array>
         1      > FE_RESET_RW                                            , ->6
... rest of looping code

同样,</code> 是 <code>FE_RESET_RW 的第一个操作数,它是赋值结果,因此不会对 !0 进行迭代(代码中的 $a ).