【问题标题】:Strange behavior while modifying array that is being iterated修改正在迭代的数组时出现奇怪的行为
【发布时间】:2010-07-27 15:04:57
【问题描述】:

这是一个愚蠢的例子,但确切地说明了我的问题。在某些情况下,数组已成功修改,而在另一些情况下则没有,为什么?值是按值赋予foreach的吗?并且输出也搞砸了,有些行似乎有 '\r\n' 其他行没有。

$arr = file('text.txt');
echo '<pre>';

foreach( $arr as $x => $line){
    if( $x % 3){ unset( $arr[$x]); } // this works
    else{ $arr[$x+1] += 1;} // this don't
    echo "[$x] => ${arr[$x+1]}";
}


print_r( $arr);

文本.txt:

0
1
2
3
4
5
6

输出:

[0] => 2[1] => 2
[2] => 3
[3] => 5[4] => 5
[5] => 6[6] => 1 个数组
(
[0] => 0

[3] => 3

[6] => 6
[7] => 1
)

编辑:

这个例子并没有真正完成任何事情,只是为了表明发生了意外的事情是没有用的,所以,这里有一些更接近我需要做的事情:

<?php

$arr = file('text.txt');
echo '<pre>';

foreach( $arr as $x => $line){
    if( preg_match("/word$/", $line)){
        $line = preg_replace( "/word$/", '', $line);
        $arr[$x+1] = 'word ' . $arr[$x+1];
    }
}


print_r( $arr);

文本.txt:

测试0
test1word
测试2

数组中的预期值:

[0] => 测试0
[1] => 测试1
[2] => 单词 test2

【问题讨论】:

  • 修改是个糟糕的词。
  • 您不需要在问题标题中包含 [括号中的关键字]。这就是标签的用途。我已经更新了标题并修复了“修改”。 :)

标签: php reference foreach behavior


【解决方案1】:

在迭代数组时修改数组通常是不安全的,并且可能导致意外行为。

【讨论】:

  • 在 foreach() 循环中,您不是在遍历 $arr,而是遍历 $arr 的副本。只要你知道自己在做什么,修改 $arr 是很安全的。
  • 所以,有一个副本,foreach 按值使用,除非我们指定它应该与引用一起使用,但为什么它允许我编辑原始数组? unset() 适用于原始数组,+1 适用于副本,我怎样才能安全地使用它?
  • 另一个注意事项,有一个具有意外行为的迭代器有什么意义?
  • 重点是在迭代时知道什么可以做,什么不应该做。修改值很好。修改结构是有风险的(尽管按照 Mark Ba​​ker 的说法,在这种情况下应该没问题)。如果您在迭代时尝试从迭代器中添加/删除项目,某些语言(例如 Java)将显式抛出异常。为了安全起见,您始终可以将所需的值复制到新数组中。
【解决方案2】:

除非您通过引用传递,否则您不能更改 foreach 迭代器中的值。作为PHP manual page states

注意:除非数组被引用, foreach 在副本上运行 指定数组而不是数组 本身。 foreach 有一些副作用 在数组指针上。不要依赖 期间或之后的数组指针 foreach 而不重置它。

因此,您需要将 foreach 行更新为:

foreach( $arr as $x => &$line){
...

【讨论】:

  • 使用数组的副本并不是真正的问题。问题是副本有时被修改,有时原始数组被修改
【解决方案3】:

您的第二个示例仍然是 expected 行为...您正在处理数组及其值的 副本,而不是实际的数组值,除非您使用“通过引用”

foreach( $arr as $x => &$line){
    if( preg_match("/word$/", $line)){
        $line = preg_replace( "/word$/", '', $line);
        $arr[$x+1] = 'word ' . $arr[$x+1];
    }
}
unset($line);

注意使用 &$line 而不是 $line,在循环结束后取消设置总是最安全的

编辑

引用PHP manual:

注意:除非数组被引用, foreach 在副本上运行 指定数组而不是数组 本身。 foreach 有一些副作用 在数组指针上。不要依赖 期间或之后的数组指针 foreach 而不重置它。

编辑

我不建议使用 foreach() 中的引用,它是 真的很慢,在我的情况下是 16 倍 慢点。添加这个的解决方案 行:$line = $arr[$x];在里面 循环的开始,它似乎做 一些魔术技巧和一切 按我的预期工作

并不是真正的魔术。它只是用通过键 ($x) 直接从数组中提取的 $line 覆盖通过 foreach 循环提取的 $line 的值。

YMMV,但对我来说似乎并没有慢多少。

以下测试脚本:

$arr = range(1,9999);


$callStartTime = microtime(true);

foreach($arr as &$line) {
    $line += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access by reference was '.sprintf('%.4f',$callTime)." seconds<br />\n";


foreach($arr as $x => &$line) {
    $line += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access by reference (retrieving key as well) was '.sprintf('%.4f',$callTime)." seconds<br />\n";


$callStartTime = microtime(true);

foreach($arr as $x => $line) {
    $arr[$x] += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time and then access array element directly was '.sprintf('%.4f',$callTime)." seconds<br />\n";

$callStartTime = microtime(true);

foreach(array_keys($arr) as $x) {
    $arr[$x] += 1;
}

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access array_keys was '.sprintf('%.4f',$callTime)." seconds<br />\n";

返回以下时间:

Call time to access by reference was 0.0018 seconds
Call time to access by reference (retrieving key as well) was 0.0039 seconds
Call time to access key and then access array element directly was 0.0077 seconds
Call time to access array_keys was 0.0071 seconds

【讨论】:

  • 我不建议在 foreach() 中使用引用,它真的很慢,在我的情况下它慢了 16 倍。添加此行的解决方案: $line = $arr[$x];在循环的开始,它似乎做了一些魔术,一切都按我的预期进行
【解决方案4】:

好吧,您的示例取消设置不能被 3 整除的每个元素。现在在此 $arr[$x+1] += 1; 中,您将值增加 1,并且在下一次迭代中它将被取消设置,因为它不能被 3 整除。

【讨论】:

    【解决方案5】:

    这是预期的行为,它会取消设置不是 3 的倍数的所有内容,然后尝试将未设置的变量增加 1。

    【讨论】:

      【解决方案6】:

      你想用这个来达到什么目的?

      if( $x % 3){ 
      

      当$x为0时,$x % 3为0,故为假;当 $x 为 1 时,$x % 3 为 1,因此为真;当 $x 为 2 时,$x % 3 为 2,因此为真……所以当 $x 为 0 时,它将增加 $x+1 (1),然后立即取消设置条目 1 和 2;当 $x 为 3 时增加 $x+1(条目 4);然后取消设置条目 4 和 5,等等。

      你是说

      if(( $x % 3) == 0) { 
      

      【讨论】:

        猜你喜欢
        • 2017-02-24
        • 1970-01-01
        • 1970-01-01
        • 2023-01-14
        • 2013-03-11
        • 1970-01-01
        • 2016-04-22
        • 1970-01-01
        • 2018-02-12
        相关资源
        最近更新 更多