multidimensional Wie funktioniert PHP 'foreach' eigentlich?




php foreach object (5)

Einige Punkte, die bei der Arbeit mit foreach() zu beachten sind:

a) foreach arbeitet an der prospektierten Kopie des ursprünglichen Arrays. Dies bedeutet, dass foreach () SHARED-Datenspeicher haben wird, bis oder solange keine prospected copy für manual .

b) Was löst eine vermutete Kopie aus ? Die erwartete Kopie wird basierend auf der Kopier copy-on-write Richtlinie erstellt, dh, wenn ein an foreach () übergebenes Array geändert wird, wird ein Klon des ursprünglichen Arrays erstellt.

c) Der ursprüngliche DISTINCT SENTINEL VARIABLES und foreach () - Iterator wird DISTINCT SENTINEL VARIABLES haben, DISTINCT SENTINEL VARIABLES einen für das ursprüngliche Array und einen anderen für foreach; Sehen Sie den Testcode unten. SPL , Iterators und Array-Iterator .

Stapelüberlauffrage Wie kann sichergestellt werden, dass der Wert in einer foreach-Schleife in PHP zurückgesetzt wird? adressiert die Fälle (3,4,5) Ihrer Frage.

Das folgende Beispiel zeigt, dass jedes () und reset () SENTINEL-Variablen (for example, the current index variable) SENTINEL (for example, the current index variable) des foreach () - Iterators NICHT beeinflusst.

$array = array(1, 2, 3, 4, 5);

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

foreach($array as $key => $val){
    echo "foreach: $key => $val<br/>";

    list($key2,$val2) = each($array);
    echo "each() Original(inside): $key2 => $val2<br/>";

    echo "--------Iteration--------<br/>";
    if ($key == 3){
        echo "Resetting original array pointer<br/>";
        reset($array);
    }
}

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

Ausgabe:

each() Original (outside): 0 => 1
foreach: 0 => 1
each() Original(inside): 1 => 2
--------Iteration--------
foreach: 1 => 2
each() Original(inside): 2 => 3
--------Iteration--------
foreach: 2 => 3
each() Original(inside): 3 => 4
--------Iteration--------
foreach: 3 => 4
each() Original(inside): 4 => 5
--------Iteration--------
Resetting original array pointer
foreach: 4 => 5
each() Original(inside): 0=>1
--------Iteration--------
each() Original (outside): 1 => 2

https://code.i-harness.com

Lassen Sie mich dies mit der Aussage foreach , dass ich weiß, was foreach ist, tut und wie man es benutzt. Diese Frage betrifft, wie es unter der Motorhaube funktioniert, und ich will keine Antworten in Richtung "das ist, wie Sie ein Array mit foreach ".

Lange Zeit nahm ich an, dass foreach mit dem Array selbst arbeitete. Dann habe ich viele Hinweise darauf gefunden, dass es mit einer Kopie des Arrays funktioniert, und ich habe seitdem angenommen, dass dies das Ende der Geschichte ist. Aber ich habe kürzlich eine Diskussion darüber geführt und nach ein paar Experimenten festgestellt, dass dies nicht 100% ig wahr ist.

Lass mich zeigen, was ich meine. Für die folgenden Testfälle arbeiten wir mit folgendem Array:

$array = array(1, 2, 3, 4, 5);

Testfall 1 :

foreach ($array as $item) {
  echo "$item\n";
  $array[] = $item;
}
print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 2 3 4 5 1 2 3 4 5 */

Dies zeigt deutlich, dass wir nicht direkt mit dem Quell-Array arbeiten - sonst würde die Schleife für immer fortfahren, da wir während des Loops ständig Elemente auf das Array schieben. Aber um sicher zu sein, ist dies der Fall:

Testfall 2 :

foreach ($array as $key => $item) {
  $array[$key + 1] = $item + 2;
  echo "$item\n";
}

print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 3 4 5 6 7 */

Dies sichert unsere erste Schlussfolgerung, wir arbeiten mit einer Kopie des Quell-Arrays während der Schleife, ansonsten würden wir die modifizierten Werte während der Schleife sehen. Aber...

Wenn wir in das manual schauen, finden wir diese Aussage:

Wenn foreach zum ersten Mal gestartet wird, wird der interne Array-Zeiger automatisch auf das erste Element des Arrays zurückgesetzt.

Richtig ... Dies scheint darauf foreach , dass foreach auf den Array-Zeiger des Quell-Arrays angewiesen ist. Aber wir haben gerade bewiesen, dass wir nicht mit dem Quell-Array arbeiten , oder? Nun, nicht ganz.

Testfall 3 :

// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));

foreach ($array as $item) {
  echo "$item\n";
}

var_dump(each($array));

/* Output
  array(4) {
    [1]=>
    int(1)
    ["value"]=>
    int(1)
    [0]=>
    int(0)
    ["key"]=>
    int(0)
  }
  1
  2
  3
  4
  5
  bool(false)
*/

Trotz der Tatsache, dass wir nicht direkt mit dem Quell-Array arbeiten, arbeiten wir direkt mit dem Quell-Array-Zeiger - die Tatsache, dass der Zeiger am Ende des Arrays am Ende der Schleife ist, zeigt dies. Außer das kann nicht wahr sein - wenn das der Fall wäre, würde Testfall 1 für immer durchlaufen .

Das PHP-Handbuch besagt auch:

Da foreach auf den internen Array-Zeiger angewiesen ist, kann das Ändern innerhalb der Schleife zu unerwartetem Verhalten führen.

Nun, lasst uns herausfinden, was dieses "unerwartete Verhalten" ist (technisch ist jedes Verhalten unerwartet, da ich nicht mehr weiß, was ich zu erwarten habe).

Testfall 4 :

foreach ($array as $key => $item) {
  echo "$item\n";
  each($array);
}

/* Output: 1 2 3 4 5 */

Testfall 5 :

foreach ($array as $key => $item) {
  echo "$item\n";
  reset($array);
}

/* Output: 1 2 3 4 5 */

... nichts Unerwartetes, tatsächlich scheint es die Theorie der "Kopie der Quelle" zu stützen.

Die Frage

Was geht hier vor sich? Mein C-Fu ist nicht gut genug für mich, um eine richtige Schlussfolgerung zu ziehen, indem ich einfach den PHP-Quellcode anschaue. Ich würde es begrüßen, wenn jemand es für mich ins Englische übersetzen könnte.

Mir scheint, dass foreach mit einer Kopie des Arrays arbeitet, aber den Array-Zeiger des Quell-Arrays nach der Schleife auf das Ende des Arrays setzt.

  • Ist das richtig und die ganze Geschichte?
  • Wenn nicht, was macht es wirklich?
  • Gibt es Situationen, in denen die Verwendung von Funktionen, die den Array-Zeiger ( each() , reset() foreach ) während einer foreach anpassen, das Ergebnis der Schleife beeinflussen könnte?

In Beispiel 3 ändern Sie das Array nicht. In allen anderen Beispielen ändern Sie entweder den Inhalt oder den internen Array-Zeiger. Dies ist wichtig bei PHP Arrays wegen der Semantik des Zuweisungsoperators.

Der Zuweisungsoperator für die Arrays in PHP funktioniert eher wie ein fauler Klon. Wenn Sie eine Variable einer anderen zuweisen, die ein Array enthält, wird das Array im Gegensatz zu den meisten anderen Sprachen geklont. Das tatsächliche Klonen wird jedoch nicht durchgeführt, wenn es nicht benötigt wird. Dies bedeutet, dass der Klon nur dann stattfindet, wenn eine der Variablen geändert wird (copy-on-write).

Hier ist ein Beispiel:

$a = array(1,2,3);
$b = $a;  // This is lazy cloning of $a. For the time
          // being $a and $b point to the same internal
          // data structure.

$a[] = 3; // Here $a changes, which triggers the actual
          // cloning. From now on, $a and $b are two
          // different data structures. The same would
          // happen if there were a change in $b.

Wenn Sie zu Ihren Testfällen zurückkehren, können Sie sich leicht vorstellen, dass foreach eine Art Iterator mit einem Verweis auf das Array erstellt. Dieser Verweis funktioniert genau wie die Variable $b in meinem Beispiel. Der Iterator mit der Referenz lebt jedoch nur während der Schleife, und dann werden beide verworfen. Jetzt können Sie sehen, dass in allen Fällen außer 3 das Array während der Schleife geändert wird, während diese zusätzliche Referenz aktiv ist. Dies löst einen Klon aus, und das erklärt, was hier vor sich geht!

Hier ist ein ausgezeichneter Artikel für einen weiteren Nebeneffekt dieses Copy-on-Write-Verhaltens: Der PHP-Ternär-Operator: Schnell oder nicht?


Erklärung (Zitat von php.net ):

Die erste Form läuft über das durch array_expression angegebene Array. Bei jeder Iteration wird der Wert des aktuellen Elements $ value zugewiesen und der interne Array-Zeiger wird um eins erhöht (bei der nächsten Iteration sehen Sie also das nächste Element).

Also, in Ihrem ersten Beispiel haben Sie nur ein Element im Array, und wenn der Zeiger verschoben wird, existiert das nächste Element nicht, also nachdem Sie ein neues Element foreach hinzugefügt haben, da es bereits entschieden hat, dass es das letzte Element ist.

In Ihrem zweiten Beispiel beginnen Sie mit zwei Elementen, und foreach loop ist nicht das letzte Element, daher wertet es das Array bei der nächsten Iteration aus und stellt somit fest, dass ein neues Element im Array vorhanden ist.

Ich glaube, dass dies alles Folge von jedem Iterationsteil der Erklärung in der Dokumentation ist, was wahrscheinlich bedeutet, dass foreach alle Logik vor dem Aufruf des Codes in {} .

Testfall

Wenn Sie das ausführen:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

Sie erhalten diese Ausgabe:

1 2 3 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Das bedeutet, dass es die Änderung akzeptierte und durchging, weil es "in time" geändert wurde. Aber wenn du das tust:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

You will get:

1 2 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Which means that array was modified, but since we modified it when the foreach already was at the last element of the array, it "decided" not to loop anymore, and even though we added new element, we added it "too late" and it was not looped through.

Detailed explanation can be read at How does PHP 'foreach' actually work? which explains the internals behind this behaviour.


As per the documentation provided by PHP manual.

On each iteration, the value of the current element is assigned to $v and the internal
array pointer is advanced by one (so on the next iteration, you'll be looking at the next element).

So as per your first example:

$array = ['foo'=>1];
foreach($array as $k=>&$v)
{
   $array['bar']=2;
   echo($v);
}

$array have only single element, so as per the foreach execution, 1 assign to $v and it don't have any other element to move pointer

But in your second example:

$array = ['foo'=>1, 'bar'=>2];
foreach($array as $k=>&$v)
{
   $array['baz']=3;
   echo($v);
}

$array have two element, so now $array evaluate the zero indices and move the pointer by one. For first iteration of loop, added $array['baz']=3; as pass by reference.


PHP foreach loop can be used with Indexed arrays , Associative arrays and Object public variables .

In foreach loop, the first thing php does is that it creates a copy of the array which is to be iterated over. PHP then iterates over this new copy of the array rather than the original one. This is demonstrated in the below example:

<?php
$numbers = [1,2,3,4,5,6,7,8,9]; # initial values for our array
echo '<pre>', print_r($numbers, true), '</pre>', '<hr />';
foreach($numbers as $index => $number){
    $numbers[$index] = $number + 1; # this is making changes to the origial array
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # showing data from the copied array
}
echo '<hr />', '<pre>', print_r($numbers, true), '</pre>'; # shows the original values (also includes the newly added values).

Besides this, php does allow to use iterated values as a reference to the original array value as well. This is demonstrated below:

<?php
$numbers = [1,2,3,4,5,6,7,8,9];
echo '<pre>', print_r($numbers, true), '</pre>';
foreach($numbers as $index => &$number){
    ++$number; # we are incrementing the original value
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # this is showing the original value
}
echo '<hr />';
echo '<pre>', print_r($numbers, true), '</pre>'; # we are again showing the original value

Note: It does not allow original array indexes to be used as references .

Source: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples







php-internals