schleife - php foreach object




Unerwartetes Verhalten von current() in einer foreach-Schleife (6)

Diese Frage hat hier bereits eine Antwort:

Hier ist eine einfache Schleife

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

Ausgabe ( demo )

 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

Frage:

  • Kann jemand bitte erklären was los ist?
  • Warum bekomme ich nicht ABCD
  • Selbst wenn eine Kopie des Arrays von foreach sollte ich AAAA bekommen, aber nicht in der aktuellen stabilen Version von PHP

Hinweis * Ich weiß, ich kann einfach print $var aber die von PHP DOC verwenden

current - Gibt das aktuelle Element in einem Array zurück Die Funktion current() einfach den Wert des Array-Elements zurück, auf das der interne Zeiger gerade zeigt. Es bewegt den Zeiger in keiner Weise. Wenn der interne Zeiger über das Ende der Elementliste hinausweist oder das Array leer ist, gibt current () FALSE zurück.

Update 1 - Neue Beobachtung

Danke an Daniel Figueroa : Nur wenn man den current in eine Funktion bringt, erhält man ein anderes Ergebnis

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

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

Ausgabe ( Demo )

 BCDA   // What the hell 

Frage:

  • Warum nicht "BBBB" bekommen?
  • Wie beeinflusst der Umhüllungsstrom in einer Funktion den Ausgang?
  • Woher kam das extra "A"?

Update 2

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

Ausgabe ( Siehe Demo )

AAAA // No longer BBBB when using a function

Frage:

  • Was ist der Unterschied, eine Schleife in einer Funktion BBBB und sie außerhalb einer Funktion AAAA weil AAAA außerhalb und BBBB in einer Funktion in den meisten PHP-Versionen enthalten sind

Der Code, den Sie verwenden, wenn Sie lügen. Sogar wörtlich könnte es wie der gleiche Code aussehen, aber die Variablen sind nicht ( http://3v4l.org/jainJ ).

Um Ihre tatsächliche Frage zu beantworten, verwenden Sie für konsistente Ergebnisse die richtigen Tools.

Wenn Sie eine Variable mit einem Array-Wert benötigen, weisen Sie sie zu:

$list = array(....);

Wenn Sie den aktuellen Wert dieses Arrays abrufen müssen, verwenden Sie ihn vor der foreach :

$current = current($list);

Denn innerhalb der foreach dies der gleiche Variablenname sein, aber der Wert wird anders sein (stell dir vor, du iterierst!).

Wenn Sie den aktuellen Wert für jede Iteration benötigen, verwenden Sie ihn:

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

Siehe $current ?

Oh Gott, ja, so einfach war es. Warte, ich habe bereits konsistente Ergebnisse. Oh, und es war so einfach, mich nicht zu täuschen. Yay! ;)

Für das Protokoll: Übergeben einer Variablen als Funktionsparameter macht sie zu einer neuen Variablen. Auch wenn eine Referenz (das erklärt wird).

Verwenden Sie im Zweifelsfall keine PHP-Referenzen. Oder nicht einmal Variablen: http://3v4l.org/6p5nZ


Großartig darauf hinweisen. Aber es scheint Speicherproblem mit verschiedenen Versionen von PHP. Auch current gibt nur die aktuelle Position an, die Sie nirgends inkrementiert (navigiert) haben, um keine korrekte Ausgabe zu erhalten. Als eine andere Version von PHP, die als nächstes und als Startpunkt von Array auf unterschiedliche Weise interpretiert, könnte eine Lösung für dieses Problem ein Reset innerhalb der Schleife mit einer gewissen Bedingung sein. (übrigens Schleifen und dann mit aktuellen, nächste prev ist nicht eine gute Möglichkeit, wie bereits in var :) was auch immer es ist Ihre Wahl) Dies ist eine Möglichkeit, wie Sie es funktionieren können:

<?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);
}

Ausgabe ist ABCD. Siehe unter http://3v4l.org/5Hm5Y


Selbst wenn eine Kopie des Arrays von foreach erstellt wurde, sollte ich AAAA bekommen, aber nicht in der aktuellen stabilen Version von PHP

Da ich hier keine Antwort auf diese Frage gefunden habe, werde ich es (versuchen) erklären.

Vor der ersten Iteration von foreach $list wird nicht wirklich kopiert. Nur die Referenzzählung von $list wird auf 2 erhöht. Bei der ersten Iteration wird der erste Wert von $list in $var kopiert, der Zeiger wird zum zweiten Element von $list verschoben und die eigentliche Kopie von $list wird erstellt. Wenn Sie also current Zeigerpunkte auf das zweite Element, aber auf die zweite und weitere Iterationen aufrufen, wird es nie geändert, da die eigentliche Kopie von $list existiert, so dass immer das zweite Element ausgegeben wird.

Bearbeiten:

Ich habe mit debug_zval_dump , um dieses wirklich sehr unerwartete Verhalten zu verstehen mit:

<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);
}

Und bekam folgende Ausgabe:

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) }

Die Ausgabe ist ein wenig verwirrend.

Im ersten Beispiel hat foreach eine interne Kopie von $list so dass der Referenzzähler 2 war (4 im Ergebnis, da debug_zval_dump einen refCount hinzufügt). Im zweiten Beispiel (pass by value) wurde refCount auf 3 erhöht, weil $list für die Funktion kopiert wurde. Im dritten Beispiel wurde count auf 1 gehalten, weil $list als Wert übergeben wurde. Ich brauche etwas Zeit, um zu verstehen, warum. Wenn Sie den Punkt aus dieser Ergebnisfreigabe erhalten.

Alles, was ich sagen kann ist, dass wenn wir Array nach Wert übergeben haben foreach das Array übergeben hat, das iteriert hat, aber wenn es als Referenz übergeben wurde, hat es die ursprüngliche $list . Die Frage ist: Warum wurde diese Anordnung weitergegeben?


Verwenden Sie das Sie bereits wissen, was passiert ist!

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

Vielleicht hilft es dir!


Warum beginnt es mit B?

Seit 5.2 foreach (zuverlässig) den Array-Zeiger vor, bevor der Schleifenkörper startet. Siehe auch den FE_RESET Opcode.

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

Ausgabe:

B

Dies hat möglicherweise etwas damit zu ZEND_OP_DATA wie der ZEND_OP_DATA Pseudo-Opcode funktioniert (was nicht wirklich dokumentiert ist).

Warum gibt current() immer den gleichen Wert?

Bevor die Schleife beginnt, erstellt foreach einen internen Verweis auf das Array, das Sie durchlaufen. Wenn die Arrayvariable innerhalb der Schleife geändert oder als Referenz übergeben wird, wird die interne Referenz von der Variablen getrennt, indem eine Kopie der Arraystruktur (aber nicht der Elemente) erstellt wird. Dieser kopierte Wert behält den Array-Zeiger bei (der zuvor durch die Schleifeninitialisierung geändert wurde).

Dieses Verhalten wird auch bei einer destruktiven 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";

Ausgabe:

ABCD
Array
(
    [0] => A
)

Schleifenvariable an eine Funktion übergeben

Dies ist ein weiteres interessantes Szenario:

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

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

Ausgabe:

BCDA
bool(false)

Diesmal wird das Array als Wert übergeben und seine Array-Struktur wird (nicht die Elemente) in den $arr Parameter der Funktion kopiert. Im Gegensatz zum vorherigen Beispiel gibt es keine Dissoziation zwischen der internen Referenz der Schleife und dem Symbol $list da die Kopie im Funktionsumfang stattfindet.

Was ist mit dem letzten "A" ?

Dies ist bei weitem das mysteriöseste Verhalten von foreach und kann nur unter diesen Umständen beobachtet werden. In der letzten Schleifeniteration wird der Array-Zeiger scheinbar auf das erste Element zurückgespult; scheinbar, weil es am Ende der Schleife offensichtlich über das Ende der Elemente hinaus zeigt (wie Sie aus der letzten Zeile der Ausgabe sehen können).

Dies kann etwas mit dem SWITCH_FREE Opcode zu tun haben, der am Ende einer foreach .

Warum also macht es die foreach in einer Funktion anders?

Beachten Sie den folgenden Code:

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

Ausgabe:

AAAA
string(1) "A"

In diesem Fall wird die interne Referenz des foreach mit einer Kopie des Arrays initialisiert (weil es einen Refcount> 1 hat) und erzeugt somit eine unmittelbare Dissoziation vom $arr Symbol.

Kann es noch schlimmer werden?

Na sicher! Wenn Sie Referenzen verwenden oder mehrere foreach Schleifen für dieselbe Variable verschachteln, können Sie noch verblüffendere Ergebnisse erzielen.

Wie kann ich konsistente Ergebnisse erhalten?

Verwenden Sie Iterators oder verlassen Sie sich nicht auf einen konsistenten Wert, wenn Sie während einer foreach Operation auf die Array-Variable verweisen.


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

Sollte es tun.





foreach