修改正在迭代的数组时的奇怪行为

时间:2010-07-27 15:04:57

标签: php reference foreach behavior

这是一个愚蠢的例子,但显示了我的问题究竟是什么。在某些情况下,阵列是成功修改的,而其他不是,为什么?值是按值给出的吗?并且输出也被拧紧,有些线似乎有'\ 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);

的text.txt:

  

0
  1
  2
  3
  4
  5
  6个

输出:

  

[0] =&gt; 2 [1] =&gt; 2
  [2] =&gt; 3
  [3] =&gt; 5 [4] =&gt; 5
  [5] =&gt; 6 [6] =&gt; 1Array
  (
  [0] =&gt; 0
  
  [3] =&gt; 3
  
  [6] =&gt; 6
  [7] =&gt; 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);

的text.txt:

  

TEST0
  test1word
  TEST2

数组中的预期值:

  

[0] =&gt; TEST0
  [1] =&gt; test1的
  [2] =&gt;单词test2

6 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:3)

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

  

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

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

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

答案 2 :(得分:0)

那么你的例子取消了每个不能被3整除的元素。现在在$arr[$x+1] += 1;中你将值增加一,在下一次迭代中它将被取消,因为它不能被3整除。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

你想用这个来实现什么?

if( $x % 3){ 

当$ x为0时,$ x%3为0,因此为false;当$ 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) { 

答案 5 :(得分:0)

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

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

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

修改

引用PHP manual

  

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

修改

  

我不建议使用   在foreach()中的引用,它是   真的很慢,在我的情况下它是16倍   慢点。添加此解决方案   line:$ 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