2212 votes

Comment ' foreach ' fonctionne réellement

Permettez-moi de préfixe en disant que ce que je sais de quoi foreach est, et comment l'utiliser. Cette question concerne la façon dont il fonctionne sous le capot, et je ne veux pas de réponses allant dans le sens de "c'est comment vous boucle un tableau avec foreach".


Pendant longtemps, j'ai supposé qu' foreach a travaillé avec le tableau lui-même. Ensuite, j'ai trouvé beaucoup de références au fait qu'il travaille avec une copie de la matrice, et depuis, j'ai supposé que cela soit la fin de l'histoire. Mais j'ai récemment eu une discussion sur le sujet, et après un peu d'expérimentation trouvé que ce n'était pas vrai à 100%.

Laissez-moi vous montrer ce que je veux dire. Pour la suite de cas de test, nous allons travailler avec le tableau suivant:

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

Cas de Test 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 */

Cela montre clairement que nous ne sommes pas travailler directement avec la source de tableau - sinon la boucle continuera à jamais, car nous sommes toujours en poussant des éléments sur le tableau lors de la boucle. Mais juste pour être sûr que c'est le cas:

Cas de Test 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 */

Cela nous conforte dans notre première conclusion, nous travaillons avec une copie de la source matrice au cours de la boucle, sinon, nous aurions voir les valeurs modifiées au cours de la boucle. Mais...

Si l'on regarde dans le manuel, on trouve cette déclaration:

Lorsque foreach première commence à s'exécuter, l'interne pointeur sur le tableau est automatiquement remise à zéro pour le premier élément du tableau.

Bon... ceci semble suggérer que l' foreach s'appuie sur le tableau de pointeur du tableau source. Mais nous venons de prouver que nous sommes de ne pas travailler avec la source de tableau, à droite? Eh bien, pas tout à fait.

Cas de Test 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)
*/

Ainsi, malgré le fait que nous ne sommes pas travailler directement avec la source de tableau, nous travaillons directement avec la source pointeur sur le tableau - le fait que le pointeur est à la fin du tableau à la fin de la boucle indique ce. Sauf que cela ne peut pas être vrai, si c'était le cas, cas de test 1 serait en boucle pour toujours.

Le manuel PHP indique également:

Comme foreach s'appuie sur l'interne pointeur sur le tableau de changer à l'intérieur de la boucle peut entraîner un comportement inattendu.

Bien, laissez-nous savoir ce que ce "comportement inattendu" est (techniquement, tout comportement est inattendu, car je ne sais plus à quoi m'attendre).

Cas de Test 4:

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

/* Output: 1 2 3 4 5 */

Cas de Test 5:

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

/* Output: 1 2 3 4 5 */

...rien que des il y a, en fait, il semble à l'appui de la "copie de la source" de la théorie.


La Question

Ce qui se passe ici? Mon C-fu n'est pas assez bon pour moi de pouvoir extraire une conclusion correcte simplement en regardant le code source en PHP, je vous serais reconnaissant si quelqu'un pouvait traduire en anglais pour moi.

Il me semble qu' foreach travaille avec une copie de la matrice, mais définit le pointeur sur le tableau de la source de tableau à la fin du tableau après la boucle.

  • Est-ce correct et l'ensemble de l'histoire?
  • Si non, quel est-il vraiment en train de faire?
  • Est-il une situation où l'utilisation de fonctions qui permettent d'ajuster le pointeur sur le tableau (each(), reset() et coll.) au cours d'une foreach pourrait affecter le résultat de la boucle?

124voto

linepogl Points 5110

Dans l'exemple 3 ne pas modifier le tableau. Dans tous les autres exemples de modifier le contenu ou l'interne pointeur sur le tableau. Ceci est important quand il s'agit de PHP tableaux en raison de la sémantique de l'opérateur d'affectation.

L'opérateur d'affectation pour les tableaux en PHP fonctionne plus comme un paresseux clone. Affectation d'une variable à une autre qui contient un tableau permet de cloner le tableau, à la différence de la plupart des langues. Cependant, le clonage ne sera effectué sauf si c'est nécessaire. Cela signifie que le clone aura lieu uniquement lorsque l'une des variables est modifié (copy-on-write).

Voici un exemple:

$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.

Pour revenir à votre cas de test, vous pouvez facilement imaginer qu' foreach crée une sorte de itérateur avec une référence à la matrice. Cette référence fonctionne exactement comme la variable $b dans mon exemple. Cependant, l'itérateur avec la référence de vivre uniquement au cours de la boucle, et puis, ils sont tous les deux éliminés. Maintenant, vous pouvez voir que, dans tous les cas, mais 3, le tableau est modifié au cours de la boucle, alors que cette référence supplémentaire est vivant. Cela déclenche un clone, et qui explique ce qui se passe ici!

Voici un excellent article pour un autre effet secondaire de cette copie sur écriture comportement: Le PHP Opérateur Ternaire: Rapide ou pas?

57voto

sakhunzai Points 2399

Quelques points sont à noter lorsque vous travaillez avec des foreach():

a) foreach fonctionne sur le prospectés copie du tableau original. Cela signifie foreach() auront PARTAGÉ le stockage de données jusqu'à ce que ou à moins d'un prospected copyest pas créé foreach Notes/commentaires de l'Utilisateur.

b) Quels sont les éléments déclencheurs prospectés copie? Prospectés copie est créé sur la base de la politique de l' copy-on-write, qui est, à chaque fois que un tableau passé à foreach() est modifié, un clone de l'original tableau est créé.

c) Le tableau d'origine et foreach() itérateur devront DISTINCT SENTINEL VARIABLES, soit un pour le tableau d'origine et l'autre pour la foreach; voir le code de test ci-dessous. SPL , Itérateurs, et le Tableau d'Itérateur.

Un Débordement de pile question Comment assurez-vous que la valeur est remise à un "foreach" en boucle en PHP? les adresses le cas (3,4,5) de votre question.

L'exemple suivant montre que chacun() et reset() N'affecte PAS SENTINELvariables (for example, the current index variable) de la foreach() itérateur.

$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/>";

Sortie:

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

41voto

DKasipovic Points 3555

Explication (citation de php.net):

La première forme de boucle sur la table donnée par array_expression. Sur chaque itération, la valeur de l'élément courant est assignée à $valeur et le tableau interne pointeur est avancé par un (donc sur la prochaine itération, vous serez à la recherche à l'élément suivant).

Donc, dans votre premier exemple, vous n'avez qu'un élément dans le tableau, et lorsque le pointeur est déplacé à l'élément suivant n'existe pas, donc une fois que vous ajoutez un nouvel élément foreach se termine, car il est déjà "décidé" que comme le dernier élément.

Dans ton deuxième exemple, vous commencez avec deux éléments, et la boucle foreach n'est pas le dernier élément de sorte qu'il évalue le tableau sur la prochaine itération et donc se rend compte qu'il est un nouvel élément dans le tableau.

Je crois que tout cela est la conséquence de À chaque itération, une partie de l'explication dans la documentation, ce qui signifie probablement que l' foreach n'toute logique, avant d'appeler le code en {}.

Cas de Test

Si vous exécutez ce:

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

Vous obtiendrez ce résultat:

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

Ce qui signifie qu'il a accepté la modification et suis allé à travers elle, parce qu'il a été modifié "dans le temps". Mais si vous faites cela:

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

Vous obtiendrez:

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

Ce qui signifie que le tableau a été modifié, mais depuis que nous avons modifié lorsque l' foreach déjà dans le dernier élément du tableau, il "a décidé" de ne pas en boucle, et de plus, même si nous avons ajouté un nouvel élément, nous avons ajouté ça "trop tard" et il n'était pas bouclé.

Explication détaillée peut être lu de Façon foreach fonctionne réellement ce qui explique le fonctionnement interne derrière ce comportement.

18voto

user3535130 Points 11

Selon les documents fournis par le manuel PHP.

À chaque itération, la valeur de l'élément courant est assignée à $v et de l'intérieur
pointeur sur le tableau est avancé par un (donc sur la prochaine itération, vous serez à la recherche à l'élément suivant).

Comme dans votre premier exemple:

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

$array ont un seul élément, comme dans le foreach exécution, 1 affecter à l' $v et il n'y a aucun autre élément à déplacer le pointeur

Mais dans ton deuxième exemple:

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

$array ont deux élément, alors maintenant, $array évaluer le zéro indices et déplacez le pointeur de la par un. Pour la première itération de la boucle, a ajouté $array['baz']=3; que le passage par référence.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X