33

この質問にはすでに答えがあります。

これは単純なループです

$list = array("A", "B", "C","D");
foreach ($list as $var) {
    print(current($list));
}

出力(デモ

 BBBB   // Output for 5.2.4 - 5.5.0alpha4
 BCD    // Output for 4.4.1
 AAAA   // Output for 4.3.0 - 4.4.0, 4.4.2 - 5.2.3

質問 :

  • 誰かが起こっていることを説明してもらえますか?
  • なぜ私はABCDを受けていないのですか
  • 配列のコピーがによって作成されたとしてもforeach私はなっているはずですAAAAしかし現在ではそれを得ていないPHP安定版

注*私は私が単に使用できることを知っていますprint $varしかしPHP DOCから

current - 配列内の現在の要素を返す   の現在()functionは単に、内部ポインタが現在指している配列要素の値を返すだけです。ポインタを動かすことはありません。内部ポインタが要素リストの末尾を超えているか、配列が空の場合、current()はFALSEを返します。

アップデート1 - 新しい所見

ありがとうダニエル・フィゲロア:ラッピングだけでcurrent関数内では異なる結果が得られます

foreach ( $list as $var ) {
    print(item($list));
}

function item($list) {
    return current($list);
}

出力(デモ

 BCDA   // What the hell 

質問 :

  • なぜ「BBBB」を取得しないのですか?
  • 関数内でのラッピング電流はどのように影響しますかforeach出力?
  • 余分な "A"はどこから来ましたか?

更新2

$list = array("A","B","C","D");
item2($list);
function item2($list) {
    foreach ( $list as $var ) {
        print(current($list));
    }
}

出力(デモを見る

AAAA // No longer BBBB when using a function

質問 :

  • 関数内でループを実行することと関数外で実行することの違いは何ですか。AAAA外とBBBBほとんどのPHPバージョンの関数で


8 답변


18

なぜBで始まるのですか?

5.2以降foreach(確実に)配列ポインタを進めるループ本体が始まります。また見なさいFE_RESETオペコード。

$list = array("A", "B", "C","D");
foreach ($list as $var) {
    break;
}
var_dump(current($list));

出力:

B

これは、ZEND_OP_DATA疑似オペコードは機能します(これは実際には文書化されていません)。

どしてcurrent()同じ値を与え続ける?

ループが始まる前に、foreachループしている配列への内部参照を作成します。ループ内に入ると、配列変数が変更または参照渡しされるたびに、配列参照のコピーを作成することによって内部参照と変数の関連付けが解除されます(要素は不可)。このコピーされた値は配列ポインタ(ループ初期化によって以前に変更されたもの)を保持します。

この振る舞いはより破壊的にも現れます。unset()操作:

$list = array('A', 'B', 'C', 'D');
foreach ($list as $key => $val) {
  echo $val;
  unset($list[1], $list[2], $list[3]);
}
echo "\n", print_r($list, true), "\n";

出力:

ABCD
Array
(
    [0] => A
)

関数にループ変数を渡す

これは別の興味深いシナリオです。

$list = array('A', 'B', 'C', 'D');
function itm($arr) 
{
    return current($arr);
}

foreach ($list as $item) {
    print itm($list);
}
var_dump(current($list));

出力:

BCDA
bool(false)

今回は、配列は値渡しされるので、その配列構造は(要素ではなく)関数の関数にコピーされます。$arrパラメータ前の例とは異なり、ループの内部参照と次のコードの関連付けは解除されていません。$listコピーは関数の有効範囲内で行われるためです。

最後はどうですか"A"

これは、はるかに最も謎めいた振る舞いです。foreachそしてこれらの状況下でのみ目撃することができます。最後のループ反復では、配列ポインタは一見最初の項目に巻き戻します。ループの終わりでそれが明らかに要素の終わりを越えて指しているので(出力の最後の行からわかるように)。

これは何かと関係があるかもしれませんSWITCH_FREEの最後に実行されるオペコードforeach

それでなぜ配置するのですかforeach関数でそれは違うの?

次のコードを見てください。

function item2($arr) 
{
    foreach ($arr as $var) {
        print(current($arr));
    }
    var_dump(current($arr));
}
$list = array("A","B","C","D");
item2($list);

出力:

AAAA
string(1) "A"

この場合、foreach(refcount> 1であるため)配列のコピーで初期化され、その結果配列からの関連付けが解除されます。$arrシンボル。

さらに悪化する可能性がありますか?

もちろん!参照を使い始めたり、複数の入れ子にしたりすると、さらに驚くべき結果が得られます。foreach同じ変数をループします。

それでは、どうすれば一貫した結果が得られるでしょうか。

つかいますIteratorsまたは、実行中に配列変数を参照して一貫した値を取得することに頼らないでください。foreach操作。


  • +いいけど、どうやって説明するの?stackoverflow.com/a/14849560/1226894 - Baba
  • よくわからないのは、なぜrewindから来た ?それは関数が配列を巻き戻すことを意味するのでしょうか???またはreturn文が配列に影響を与える - Baba
  • あなたは関数内でループを実行すると3つの異なる結果が得られることを知っていますか - 3v4l.org/1aUpdしかし、イテレータは同じ結果をもたらします - 3v4l.org/ViCNn - Baba
  • @ババうん、私もそれに気づいた。イテレータはとても安定しています:) - Ja͢ck
  • しかし、それらは参照とともに使用することはできません... :( - Baba

3

からPHP.net

current()関数は単純に配列要素の値を返します   それは現在内部ポインタによって指されています。ありません   何らかの方法でポインタを動かす

それから:use次()

$list = array("A", "B", "C","D");
foreach ($list as $var) {
    print(current($list));
    next($list);
}

注:foreachがポインタを配列の2番目の要素に移動したため、最初の要素は印刷されません:)

この例は完全な振る舞いを説明します:

$list = array("A", "B", "C","D");
foreach ($list as $var) {
   if(!isset($a)) reset($list); $a = 'isset';
   print(current($list));
   next($list);
}

出力はABCDです

次の点にも注意してください。

As foreach relies on the internal array pointer changing it within the loop 
may lead to unexpected behavior.

前々


編集:私も私の新しい紛らわしい発見を共有したいです!

例1

$list = array("A", "B", "C","D");
$list_copy = $list;
foreach ($list as $key => $val) {
  current($list_copy);
  echo current($list);
  //next($list);
}

出力:AAAA

例2

$list = array("A", "B", "C","D");
$list_copy = $list;
foreach ($list as $key => $val) {
  current($list_copy);
  echo current($list);
  next($list);
}

出力:ABCD

他の配列であってもforeach内でcurrent()関数を呼び出すと、foreachの動作に影響します。

例3

$list = array("A", "B", "C","D");

$refcopy = &$list;

foreach ($list as $key => $val) {
  if(!isset($a)) { $a = 'isset'; reset($list); }
  echo current($list);
  next($list);
}

出力:ACD(うわー!B不足している)

例:4

$list = array("A", "B", "C","D");

$refcopy = &$list;

foreach ($list as $key => $val) {
  echo current($list);
  next($list);
}

出力:BCD

foreachループの中で何が起こるのか正確には決定できません!!!


2

これがなぜなのかについて私には本当の手がかりがありませんが、私はそれが割り当てが評価/処理される方法と関係があるかもしれないと思います。楽しみのために私はこれを試してみました、そしてそれはもう一つをもたらしましたincorrect動作:

$arr = array('A', 'B', 'C', 'D');
function itm($val) {
    return current($val);
}

foreach ($arr as $item) {
    print itm($arr);
}

結果:BCDA

それで、ここで起こっていることは、関数呼び出しが電流の評価を正しい方法で行わせることであると私は思います。また、私がABCDの代わりにBCDAを取得した理由は、おそらく最初は内部ポインタがインクリメントされ(Bを指す)、その後enではそれがAを指すようにリセットされるためです。

この行に注目する価値があります。PHPドキュメント

代入は元の変数を新しいものにコピーする(値による代入)ので、一方への変更はもう一方に影響しません。あなたがタイトなループの中に大きな配列のようなものをコピーする必要があるならば、これはまた関連性があるかもしれません。

私はこれが本当に答えとして数えないと思いますが、私はあなたの質問が好きで、少し貢献したかったです。


  • これは複雑になりつつあります。 - Baba
  • 確かに、ソフトコピーの議論は一種の無効なので、今私は思います。 - Daniel Figueroa
  • 質問にあなたの観察を追加します。これが重要です...観察 - Baba
  • @DanielFigueroa私もそう思います。合格しようとしました$arr参考までに出力BBBBcodepad.viper-7.com/6MxdFr - Leri

1

たとえ配列のコピーがforeachによって作られたとしても、私はAAAAを取得すべきですが、現在のPHP安定版では取得してはいけません。

私はここでこの質問に対する答えを見つけられなかったので、私は説明しようとします。

の最初の繰り返しの前にforeach $list実際にはコピーされません。のみ参照カウント$listしたがって、最初の反復では、の最初の値$listにコピーされます$varポインタはの2番目の要素に移動します$listそして実際のコピー$listできるでしょう。だからあなたが電話をするときcurrentポインタは2番目の要素を指していますが、2番目以降の反復では変更されません。実際のコピー$list存在するcurrent常に2番目の要素を出力します。

編集する

私はと遊んだdebug_zval_dumpこの非常に予想外の動作を理解するには

<pre>

<?php


$list = array("A", "B", "C","D");

echo '<h1>Ref count before entering foreach:</h1><br>';
debug_zval_dump($list); echo '<br><br>';

$i = 0;
echo '<h1>Ref count in foreach:</h1><br>';
foreach ($list as $var) {
    $i++;
    echo '<b>Iteration #'.$i.':</b> ';
    debug_zval_dump($list);
    echo '<br>';
}

$list = array("A", "B", "C","D"); //re-assign array to avoid confusion

echo '<h1>Ref count before entering foreach that calls method "item" and passes array by value:</h1><br>';
debug_zval_dump($list);
$i = 0;
echo '<h1>Ref count in foreach that calls method "item" and passes array by value:</h1><br>';
foreach ( $list as $var ) {
    $i++;
    item($list, $i);
}

function item($list, $i) {
    echo '<b>Iteration #'.$i.':</b> ';
    debug_zval_dump($list);
}

$list = array("A", "B", "C","D"); //re-assign array to avoid confusion

echo '<h1>Ref count before entering foreach that calls method "item" and passes array by reference:</h1><br>';
debug_zval_dump($list);
$i = 0;
echo '<h1>Ref count in foreach that calls method "item" and passes array by reference:</h1><br>';
foreach ( $list as $var ) {
    $i++;
    itemWithRef($list, $i);
}

function itemWithRef(&$list, $i) {
    echo '<b>Iteration #'.$i.':</b> ';
    debug_zval_dump($list);
}

そして、次のような出力が得られました。

Ref count before entering foreach:
array(4) refcount(2){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) }

Ref count in foreach:
Iteration #1: array(4) refcount(3){ [0]=> string(1) "A" refcount(2) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) }
Iteration #2: array(4) refcount(3){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(2) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) }
Iteration #3: array(4) refcount(3){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(2) [3]=> string(1) "D" refcount(1) }
Iteration #4: array(4) refcount(3){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(2) }
Ref count before entering foreach that calls method "item" and passes array by value:
array(4) refcount(2){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Ref count in foreach that calls method "item" and passes array by value:
Iteration #1: array(4) refcount(5){ [0]=> string(1) "A" refcount(2) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Iteration #2: array(4) refcount(5){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(2) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Iteration #3: array(4) refcount(5){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(2) [3]=> string(1) "D" refcount(1) } Iteration #4: array(4) refcount(5){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(2) } Ref count before entering foreach that calls method "item" and passes array by reference:
array(4) refcount(2){ [0]=> string(1) "A" refcount(1) [1]=> string(1) "B" refcount(1) [2]=> string(1) "C" refcount(1) [3]=> string(1) "D" refcount(1) } Ref count in foreach that calls method "item" and passes array by reference:
Iteration #1: array(4) refcount(1){ [0]=> string(1) "A" refcount(4) [1]=> string(1) "B" refcount(3) [2]=> string(1) "C" refcount(3) [3]=> string(1) "D" refcount(3) } Iteration #2: array(4) refcount(1){ [0]=> string(1) "A" refcount(3) [1]=> string(1) "B" refcount(4) [2]=> string(1) "C" refcount(3) [3]=> string(1) "D" refcount(3) } Iteration #3: array(4) refcount(1){ [0]=> string(1) "A" refcount(3) [1]=> string(1) "B" refcount(3) [2]=> string(1) "C" refcount(4) [3]=> string(1) "D" refcount(3) } Iteration #4: array(4) refcount(1){ [0]=> string(1) "A" refcount(3) [1]=> string(1) "B" refcount(3) [2]=> string(1) "C" refcount(3) [3]=> string(1) "D" refcount(4) }

出力は少し混乱します。

最初の例ではforeachの内部コピーを作成しました$listそのため、参照カウントは2(結果の4)でした。debug_zval_dump追加するrefCount) 2番目の例(値渡し)refCount3に増加$list機能用にコピーされました。 3番目の例では、countは1のままです。$list値渡しされました。その理由を理解するために少し時間が必要です。あなたがこの結果シェアからポイントを得るならば。

私が言えるのは、配列を値渡ししたときだけですforeach反復していた配列を渡していましたが、参照渡ししたときに元の $list。問題は次のとおりです。foreachその配列を渡す?


  • +いいですが見ている必要がありますstackoverflow.com/a/14849560/1226894 - Baba
  • @ババはい、見たことがありますが、「wtfphp」を考えています。 - Leri
  • 自分では信じられない - Baba
  • あなたは関数内でループを実行すると3つの異なる結果が得られることを知っていますか - 3v4l.org/1aUpdしかし、イテレータは同じ結果をもたらします - 3v4l.org/ViCNn - Baba
  • @ババ知らなかった。この後、私たちは現実世界のアプリケーションでこれらの種類の構造体を使用してはいけないことをはるかに確信しています。それは後で大きな頭痛になります。動作は未定義です。 - Leri

1

嘘ならあなたが使うコード。文字通り同じコードのように見えるかもしれませんが、変数は違います(http://3v4l.org/jainJ

実際の質問に答えるには、一貫した結果を得るために適切なツールを使用してください。

配列値を持つ変数が必要な場合は、それを代入します。

$list = array(....);

現在の値を取得する必要がある場合それ配列、それを使うforeach

$current = current($list);

内側からforeachこれは同じ変数名かもしれませんが、値は異なります(想像してみてください、あなたは繰り返しています!)

あなたが必要な場合現在各反復ごとの値、それを使用してください:

foreach ($list as $current) {
    ...
}

見る$current

おお、ええ、それはそれほど簡単でした。結果が安定しているのを待ちますああ、それは私をだまさないためにそれほど簡単でした。わーい! ;)

ログの場合:関数パラメータとして変数を渡すと、新しい変数になります。参考になる場合でも(説明されています)。

疑問がある場合は、PHPの参照を使用しないでください。あるいは変数でさえない:http://3v4l.org/6p5nZ


  • + LOL ....いい回避策 - Baba

0

いい指摘だ。しかし、異なるバージョンのphpではメモリを指す問題があるようです。 また現在適切な出力が得られないように、どこにもインクリメント(ナビゲーション)されていない現在位置のみを表示します。異なるバージョンのphpの次のバージョンと配列の始点を解釈するための解決策は、リセットループ内に何らかの条件があります。 (ところで、ループしてから現在のを使用すると、次のprevはすでにvar:にオブジェクトを持っているので、あまり良い方法ではありません) これはあなたがそれを機能させることができる一つの方法です:

<?php
$list = array("A", "B", "C","D");
$flag =0;
foreach ($list as $var) {
    if($flag==0)
    {   
        reset($list);
        $flag=1;
    }
    print(current($list));
    next($list);
}

出力はABCDです。 で見るhttp://3v4l.org/5Hm5Y


-1

$list = array("A", "B", "C","D");
foreach ($list as $var) {
    echo $var;
}

やるべきだ。


  • 質問に記載されているとおりに問題を確認してください。問題はの変わったふるまいについてですcurrentforeachの内部では、特定の出力を取得する方法ではありません。 - Charles

-1

これを使うあなたはすでに何が起こるのか知っている!

$list = array('A', 'B', 'C','D');
foreach ($list as $var) {
 var_dump(current($list));
}

それはあなたの助けかもしれません!

リンクされた質問


関連する質問

最近の質問