Permettez-moi de préfixer cela en disant que je sais ce que foreach
c'est, ce qu'il fait et comment l'utiliser. Cette question concerne la façon dont cela fonctionne sous le capot, et je ne veux pas de réponses du type "c'est comme ça que vous bouclez un tableau avec foreach
".
Pendant longtemps, j'ai supposé que cela foreach
fonctionnait avec le tableau lui-même. Ensuite, j'ai trouvé de nombreuses références au fait qu'il fonctionne avec une copie du tableau, et j'ai depuis supposé que c'était la fin de l'histoire. Mais j'ai récemment entamé une discussion à ce sujet, et après un peu d'expérimentation, j'ai découvert que ce n'était pas vrai à 100%.
Permettez-moi de montrer ce que je veux dire. Pour les cas de test suivants, nous travaillerons avec le tableau suivant:
$array = array(1, 2, 3, 4, 5);
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 travaillons pas directement avec le tableau source - sinon la boucle continuerait pour toujours, car nous poussons constamment des éléments sur le tableau pendant la boucle. Mais juste pour être sûr que c'est le cas:
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 confirme notre conclusion initiale, nous travaillons avec une copie du tableau source pendant la boucle, sinon nous verrions les valeurs modifiées pendant la boucle. Mais...
Si nous regardons dans le manuel , nous trouvons cette déclaration:
Lorsque foreach commence à s'exécuter pour la première fois, le pointeur de tableau interne est automatiquement réinitialisé sur le premier élément du tableau.
Bon ... cela semble suggérer que foreach
repose sur le pointeur de tableau du tableau source. Mais nous venons de prouver que nous ne travaillons pas avec le tableau source , non? Enfin, pas entièrement.
// 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)
*/
Donc, malgré le fait que nous ne travaillons pas directement avec le tableau source, nous travaillons directement avec le pointeur du tableau source - le fait que le pointeur se trouve à la fin du tableau à la fin de la boucle le montre. Sauf que cela ne peut pas être vrai - si c'était le cas, le cas de test 1 serait en boucle pour toujours.
Le manuel PHP indique également:
Comme foreach repose sur le pointeur de tableau interne, le modifier dans la boucle peut entraîner un comportement inattendu.
Eh bien, découvrons ce qu'est ce "comportement inattendu" (techniquement, tout comportement est inattendu puisque je ne sais plus à quoi m'attendre).
foreach ($array as $key => $item) {
echo "$item\n";
each($array);
}
/* Output: 1 2 3 4 5 */
foreach ($array as $key => $item) {
echo "$item\n";
reset($array);
}
/* Output: 1 2 3 4 5 */
... rien d'inattendu là-bas, en fait, il semble soutenir la théorie de la "copie de la source".
La question
Qu'est-ce qui se passe ici? Mon C-fu n'est pas assez bon pour que je puisse extraire une conclusion correcte simplement en regardant le code source PHP, j'apprécierais que quelqu'un puisse le traduire en anglais pour moi.
Il me semble que cela foreach
fonctionne avec une copie du tableau, mais définit le pointeur du tableau du tableau source à la fin du tableau après la boucle.
- Est-ce correct et toute l'histoire?
- Sinon, que fait-il vraiment?
- Existe-t-il une situation où l'utilisation de fonctions qui ajustent le pointeur de tableau (
each()
,reset()
et al.) Pendant aforeach
pourrait affecter le résultat de la boucle?
foreach ($array as &$value)
) - PHP a besoin de connaître la position actuelle dans le tableau d'origine même s'il s'agit en fait d'une itération sur une copie.Réponses:
foreach
prend en charge l'itération sur trois types de valeurs différents:Traversable
objetsDans ce qui suit, je vais essayer d'expliquer précisément comment fonctionne l'itération dans différents cas. Le cas de loin le plus simple est celui des
Traversable
objets, car il neforeach
s'agit pour l'essentiel que de sucre de syntaxe pour le code suivant:Pour les classes internes, les appels de méthode réels sont évités en utilisant une API interne qui reflète essentiellement l'
Iterator
interface au niveau C.L'itération de tableaux et d'objets simples est beaucoup plus compliquée. Tout d'abord, il convient de noter qu'en PHP, les "tableaux" sont vraiment des dictionnaires ordonnés et ils seront parcourus selon cet ordre (qui correspond à l'ordre d'insertion tant que vous n'avez pas utilisé quelque chose comme
sort
). Cela s'oppose à l'itération par l'ordre naturel des clés (comment fonctionnent souvent les listes dans d'autres langues) ou à aucun ordre défini (comment fonctionnent souvent les dictionnaires dans d'autres langues).La même chose s'applique également aux objets, car les propriétés d'objet peuvent être vues comme un autre dictionnaire (ordonné) mappant les noms de propriété à leurs valeurs, plus une certaine gestion de la visibilité. Dans la majorité des cas, les propriétés des objets ne sont pas réellement stockées de cette manière plutôt inefficace. Cependant, si vous commencez à itérer sur un objet, la représentation compressée normalement utilisée sera convertie en un véritable dictionnaire. À ce stade, l'itération d'objets simples devient très similaire à l'itération de tableaux (c'est pourquoi je ne parle pas beaucoup de l'itération d'objets simples ici).
Jusqu'ici tout va bien. Itérer sur un dictionnaire ne peut pas être trop difficile, non? Les problèmes commencent lorsque vous réalisez qu'un tableau / objet peut changer pendant l'itération. Cela peut se produire de plusieurs manières:
foreach ($arr as &$v)
puis$arr
est transformé en référence et vous pouvez le changer pendant l'itération.$ref =& $arr; foreach ($ref as $v)
Le problème avec l'autorisation des modifications pendant l'itération est le cas où l'élément sur lequel vous êtes actuellement est supprimé. Supposons que vous utilisez un pointeur pour savoir à quel élément du tableau vous vous trouvez actuellement. Si cet élément est maintenant libéré, vous vous retrouvez avec un pointeur suspendu (ce qui entraîne généralement une erreur de segmentation).
Il existe différentes façons de résoudre ce problème. PHP 5 et PHP 7 diffèrent considérablement à cet égard et je décrirai les deux comportements ci-dessous. Le résumé est que l'approche de PHP 5 était plutôt stupide et conduisait à toutes sortes de problèmes de bord étranges, tandis que l'approche plus impliquée de PHP 7 se traduisait par un comportement plus prévisible et cohérent.
En dernier lieu, il convient de noter que PHP utilise le comptage des références et la copie sur écriture pour gérer la mémoire. Cela signifie que si vous "copiez" une valeur, vous ne faites que réutiliser l'ancienne valeur et incrémenter son compte de référence (refcount). Une fois que vous avez effectué une sorte de modification, une copie réelle (appelée "duplication") sera effectuée. Voir On vous ment pour une introduction plus complète sur ce sujet.
PHP 5
Pointeur de tableau interne et HashPointer
Les tableaux en PHP 5 ont un "pointeur de tableau interne" (IAP) dédié, qui prend correctement en charge les modifications: chaque fois qu'un élément est supprimé, il y aura une vérification si l'IAP pointe vers cet élément. Si c'est le cas, il est avancé à l'élément suivant à la place.
Bien
foreach
qu'il utilise l'IAP, il existe une complication supplémentaire: il n'y a qu'un seul IAP, mais un tableau peut faire partie de plusieursforeach
boucles:Pour prendre en charge deux boucles simultanées avec un seul pointeur de tableau interne,
foreach
effectuez les manœuvres suivantes: Avant l'exécution du corps de la boucle,foreach
sauvegardera un pointeur sur l'élément actuel et son hachage dans un per-foreachHashPointer
. Après l'exécution du corps de la boucle, l'IAP sera redéfini sur cet élément s'il existe toujours. Si toutefois l'élément a été supprimé, nous n'utiliserons que l'endroit où se trouve actuellement l'IAP. Ce schéma fonctionne principalement en quelque sorte, mais il y a beaucoup de comportements étranges que vous pouvez en tirer, dont certains que je vais démontrer ci-dessous.Duplication de baies
L'IAP est une caractéristique visible d'un tableau (exposée à travers la
current
famille de fonctions), car de telles modifications du compte IAP comptent comme des modifications dans la sémantique de copie sur écriture. Cela signifie malheureusement que,foreach
dans de nombreux cas, il est obligé de dupliquer le tableau sur lequel il est en cours d'itération. Les conditions précises sont:refcount
vaut 1, alors le tableau n'est pas partagé et nous sommes libres de le modifier directement.Si le tableau n'est pas dupliqué (is_ref = 0, refcount = 1), alors seul son
refcount
sera incrémenté (*). De plus, siforeach
par référence est utilisé, le tableau (potentiellement dupliqué) sera transformé en référence.Considérez ce code comme un exemple de duplication:
Ici,
$arr
sera dupliqué pour éviter les$arr
fuites des modifications IAP$outerArr
. En termes des conditions ci-dessus, le tableau n'est pas une référence (is_ref = 0) et est utilisé à deux endroits (refcount = 2). Cette exigence est regrettable et un artefact de l'implémentation sous-optimale (il n'y a pas de souci de modification pendant l'itération ici, donc nous n'avons pas vraiment besoin d'utiliser l'IAP en premier lieu).(*) L'incrémentation
refcount
ici semble inoffensive, mais viole la sémantique de copie sur écriture (COW): cela signifie que nous allons modifier l'IAP d'un tableau refcount = 2, tandis que COW stipule que les modifications ne peuvent être effectuées que sur refcount = 1 valeurs. Cette violation entraîne un changement de comportement visible par l'utilisateur (alors qu'un COW est normalement transparent) car le changement IAP sur le tableau itéré sera observable - mais uniquement jusqu'à la première modification non IAP sur le tableau. Au lieu de cela, les trois options "valides" auraient été a) de toujours dupliquer, b) ne pas incrémenter lerefcount
et ainsi permettre au tableau itéré d'être arbitrairement modifié dans la boucle ou c) ne pas utiliser du tout l'IAP (le PHP 7 solution).Ordre d'avancement de poste
Il y a un dernier détail d'implémentation que vous devez connaître pour bien comprendre les exemples de code ci-dessous. La manière "normale" de parcourir une certaine structure de données ressemblerait à ceci dans le pseudocode:
Cependant
foreach
, étant un flocon de neige plutôt spécial, choisit de faire les choses légèrement différemment:A savoir, le pointeur de tableau est déjà avancé avant l'exécution du corps de boucle. Cela signifie que pendant que le corps de la boucle travaille sur l'élément
$i
, l'IAP est déjà sur l'élément$i+1
. C'est la raison pour laquelle les échantillons de code montrant une modification pendant l'itération seront toujoursunset
l' élément suivant , plutôt que l'élément actuel.Exemples: vos cas de test
Les trois aspects décrits ci-dessus devraient vous donner une impression presque complète des particularités de l'
foreach
implémentation et nous pouvons passer à quelques exemples.Le comportement de vos cas de test est simple à expliquer à ce stade:
Dans les cas de test 1 et 2
$array
commence par refcount = 1, il ne sera donc pas dupliqué parforeach
: Seul lerefcount
est incrémenté. Lorsque le corps de boucle modifie par la suite le tableau (qui a refcount = 2 à ce point), la duplication se produit à ce point. Foreach continuera de travailler sur une copie non modifiée de$array
.Dans le cas de test 3, une fois de plus, le tableau n'est pas dupliqué, ce
foreach
qui modifiera donc l'IAP de la$array
variable. À la fin de l'itération, l'IAP est NULL (ce qui signifie que l'itération a été effectuée), ce quieach
indique en retournantfalse
.Dans les cas de test 4 et 5,
each
ilreset
s'agit de fonctions de référence. Le$array
a unrefcount=2
quand il leur est transmis, il doit donc être dupliqué. En tant que tel,foreach
il travaillera à nouveau sur un tableau séparé.Exemples: effets de
current
in foreachUn bon moyen de montrer les différents comportements de duplication est d'observer le comportement de la
current()
fonction à l'intérieur d'uneforeach
boucle. Considérez cet exemple:Ici, vous devez savoir qu'il
current()
s'agit d'une fonction by-ref (en fait: prefer-ref), même si elle ne modifie pas le tableau. Cela doit être pour jouer bien avec toutes les autres fonctions comme cellesnext
qui sont toutes by-ref. Le passage par référence implique que le tableau doit être séparé et donc$array
et leforeach-array
sera différent. La raison pour laquelle vous obtenez à la2
place de1
est également mentionnée ci-dessus:foreach
avance le pointeur de tableau avant d' exécuter le code utilisateur, pas après. Ainsi, même si le code est au premier élément,foreach
le pointeur a déjà été avancé au second.Essayons maintenant une petite modification:
Ici, nous avons le cas is_ref = 1, donc le tableau n'est pas copié (comme ci-dessus). Mais maintenant qu'il s'agit d'une référence, le tableau n'a plus à être dupliqué lors du passage à la fonction by-ref
current()
. Ainsicurrent()
etforeach
travaillez sur le même tableau. Cependant, vous voyez toujours le comportement off-by-one, en raison de la façon dontforeach
le pointeur avance.Vous obtenez le même comportement lors de l'itération par référence:
Ici, la partie importante est que foreach fera
$array
un is_ref = 1 lorsqu'il est itéré par référence, donc fondamentalement, vous avez la même situation que ci-dessus.Une autre petite variation, cette fois nous allons assigner le tableau à une autre variable:
Ici, le décompte du
$array
est de 2 lorsque la boucle est lancée, donc pour une fois, nous devons faire la duplication à l'avance. Ainsi$array
, le tableau utilisé par foreach sera complètement séparé dès le départ. C'est pourquoi vous obtenez la position de l'IAP où qu'il se trouve avant la boucle (dans ce cas, c'était à la première position).Exemples: modification pendant l'itération
Essayer de prendre en compte les modifications pendant l'itération est à l'origine de tous nos problèmes foreach, donc cela sert à considérer quelques exemples pour ce cas.
Considérez ces boucles imbriquées sur le même tableau (où l'itération by-ref est utilisée pour s'assurer qu'elle est vraiment la même):
La partie attendue ici est celle qui
(1, 2)
manque dans la sortie car l'élément a1
été supprimé. Ce qui est probablement inattendu, c'est que la boucle externe s'arrête après le premier élément. Pourquoi donc?La raison derrière cela est le hack de boucle imbriquée décrit ci-dessus: Avant que le corps de la boucle ne s'exécute, la position et le hachage IAP actuels sont sauvegardés dans a
HashPointer
. Après le corps de la boucle, il sera restauré, mais uniquement si l'élément existe toujours, sinon la position IAP actuelle (quelle qu'elle soit) est utilisée à la place. Dans l'exemple ci-dessus, c'est exactement le cas: L'élément actuel de la boucle externe a été supprimé, il utilisera donc l'IAP, qui a déjà été marqué comme terminé par la boucle interne!Une autre conséquence du
HashPointer
mécanisme de sauvegarde + restauration est que les modifications apportées à l'IAP viareset()
etc. n'ont généralement pas d'impactforeach
. Par exemple, le code suivant s'exécute comme s'ilreset()
n'était pas présent du tout:La raison en est que, bien qu'il
reset()
modifie temporairement l'IAP, il sera restauré dans l'élément foreach actuel après le corps de la boucle. Pour forcerreset()
à faire un effet sur la boucle, vous devez en outre supprimer l'élément actuel, afin que le mécanisme de sauvegarde / restauration échoue:Mais, ces exemples sont toujours sains d'esprit. Le vrai plaisir commence si vous vous souvenez que la
HashPointer
restauration utilise un pointeur sur l'élément et son hachage pour déterminer s'il existe toujours. Mais: les hachages ont des collisions et les pointeurs peuvent être réutilisés! Cela signifie qu'avec un choix judicieux de clés de tableau, nous pouvons faireforeach
croire qu'un élément qui a été supprimé existe toujours, il y sautera donc directement. Un exemple:Ici, nous devrions normalement attendre la sortie
1, 1, 3, 4
selon les règles précédentes. Ce qui se passe, c'est qu'il'FYFY'
a le même hachage que l'élément supprimé'EzFY'
, et l'allocateur arrive à réutiliser le même emplacement de mémoire pour stocker l'élément. Ainsi, foreach finit par sauter directement à l'élément nouvellement inséré, raccourcissant ainsi la boucle.Substitution de l'entité itérée pendant la boucle
Un dernier cas étrange que je voudrais mentionner, c'est que PHP vous permet de remplacer l'entité itérée pendant la boucle. Vous pouvez donc commencer l'itération sur un tableau, puis le remplacer par un autre tableau à mi-chemin. Ou commencez à itérer sur un tableau, puis remplacez-le par un objet:
Comme vous pouvez le voir dans ce cas, PHP commencera juste à itérer l'autre entité depuis le début une fois la substitution effectuée.
PHP 7
Itérateurs de table de hachage
Si vous vous souvenez encore, le principal problème avec l'itération de tableau était de savoir comment gérer la suppression des éléments à mi-itération. PHP 5 a utilisé un seul pointeur de tableau interne (IAP) à cet effet, qui était quelque peu sous-optimal, car un pointeur de tableau devait être étiré pour prendre en charge plusieurs boucles foreach simultanées et l' interaction avec
reset()
etc. en plus de cela.PHP 7 utilise une approche différente, à savoir qu'il prend en charge la création d'une quantité arbitraire d'itérateurs de table de hachage externes et sûrs. Ces itérateurs doivent être enregistrés dans le tableau, à partir de ce moment, ils ont la même sémantique que l'IAP: si un élément du tableau est supprimé, tous les itérateurs de table de hachage pointant vers cet élément seront avancés vers l'élément suivant.
Cela signifie que
foreach
n'utilisera plus du tout l'IAP . Laforeach
boucle n'aura absolument aucun effet sur les résultats decurrent()
etc. et son propre comportement ne sera jamais influencé par des fonctions commereset()
etc.Duplication de baies
Un autre changement important entre PHP 5 et PHP 7 concerne la duplication de tableaux. Maintenant que l'IAP n'est plus utilisé, l'itération de tableau par valeur ne fera qu'un
refcount
incrément (au lieu de dupliquer le tableau) dans tous les cas. Si le tableau est modifié pendant laforeach
boucle, à ce stade, une duplication se produira (selon la copie sur écriture) etforeach
continuera de fonctionner sur l'ancien tableau.Dans la plupart des cas, ce changement est transparent et n'a d'autre effet que de meilleures performances. Cependant, il y a une occasion où il en résulte un comportement différent, à savoir le cas où le tableau était une référence au préalable:
Auparavant, l'itération par valeur des tableaux de référence était des cas spéciaux. Dans ce cas, aucune duplication ne s'est produite, donc toutes les modifications du tableau pendant l'itération seraient reflétées par la boucle. En PHP 7, ce cas particulier a disparu: une itération par valeur d'un tableau continuera toujours à travailler sur les éléments d'origine, sans tenir compte des modifications pendant la boucle.
Ceci, bien sûr, ne s'applique pas à l'itération par référence. Si vous parcourez par référence toutes les modifications seront reflétées par la boucle. Fait intéressant, il en va de même pour l'itération par valeur des objets simples:
Cela reflète la sémantique du by-handle des objets (c'est-à-dire qu'ils se comportent comme des références même dans des contextes de by-value).
Exemples
Prenons quelques exemples, en commençant par vos cas de test:
Les cas de test 1 et 2 conservent la même sortie: l'itération de tableau par valeur continue de fonctionner sur les éléments d'origine. (Dans ce cas, le
refcounting
comportement pair et de duplication est exactement le même entre PHP 5 et PHP 7).Le scénario de test 3 change:
Foreach
n'utilise plus l'IAP, ileach()
n'est donc pas affecté par la boucle. Il aura la même sortie avant et après.Les cas de test 4 et 5 restent les mêmes:
each()
etreset()
dupliqueront la baie avant de modifier l'IAP, toutforeach
en utilisant toujours la baie d'origine. (Ce n'est pas que le changement IAP aurait eu de l'importance, même si le tableau était partagé.)Le deuxième ensemble d'exemples était lié au comportement de
current()
sous différentesreference/refcounting
configurations. Cela n'a plus de sens, car ilcurrent()
n'est pas affecté par la boucle, donc sa valeur de retour reste toujours la même.Cependant, nous obtenons des changements intéressants lors de l'examen des modifications lors de l'itération. J'espère que vous trouverez le nouveau comportement plus sain. Le premier exemple:
Comme vous pouvez le voir, la boucle externe n'interrompt plus après la première itération. La raison en est que les deux boucles ont maintenant des itérateurs de table de hachage entièrement séparés, et il n'y a plus de contamination croisée des deux boucles via un IAP partagé.
Un autre cas de bord étrange qui est résolu maintenant, est l'effet étrange que vous obtenez lorsque vous supprimez et ajoutez des éléments qui se trouvent avoir le même hachage:
Auparavant, le mécanisme de restauration HashPointer sautait directement vers le nouvel élément car il "ressemblait" à la même chose que l'élément supprimé (en raison de la collision du hachage et du pointeur). Comme nous ne comptons plus sur l'élément de hachage pour rien, ce n'est plus un problème.
la source
$foo = $array
avant la boucle;)Bucket
s font partie d'une liste doublement liée pour les collisions de hachage et font également partie d'une liste doublement liée pour la commande;)iterate($outerArr);
et pasiterate($arr);
quelque part.Dans l'exemple 3, vous ne modifiez pas le tableau. Dans tous les autres exemples, vous modifiez le contenu ou le pointeur de tableau interne. Ceci est important en ce qui concerne les tableaux PHP 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 clone paresseux. L'affectation d'une variable à une autre contenant un tableau clone le tableau, contrairement à la plupart des langues. Cependant, le clonage proprement dit ne sera effectué que s'il est nécessaire. Cela signifie que le clone n'aura lieu que si l'une des variables est modifiée (copie sur écriture).
Voici un exemple:
Pour en revenir à vos cas de test, vous pouvez facilement imaginer que cela
foreach
crée une sorte d'itérateur avec une référence au tableau. Cette référence fonctionne exactement comme la variable$b
dans mon exemple. Cependant, l'itérateur et la référence ne vivent que pendant la boucle, puis ils sont tous les deux rejetés. Vous pouvez maintenant voir que, dans tous les cas sauf 3, le tableau est modifié pendant la boucle, tandis que cette référence supplémentaire est active. Cela déclenche un clone, et cela explique ce qui se passe ici!Voici un excellent article pour un autre effet secondaire de ce comportement de copie sur écriture: L'opérateur ternaire PHP: rapide ou non?
la source
each()
à la fin du premier cas de test, où nous voyons que le pointeur de tableau du tableau d'origine pointe vers le deuxième élément, puisque le tableau a été modifié pendant la première itération. Cela semble également démontrer queforeach
déplace le pointeur de tableau avant d'exécuter le bloc de code de la boucle, ce à quoi je ne m'attendais pas - j'aurais pensé qu'il ferait cela à la fin. Merci beaucoup, cela m'éclaircit bien.Quelques points à noter lorsque vous travaillez avec
foreach()
:a)
foreach
fonctionne sur la copie prospectée de la matrice d'origine. Cela signifie qu'il yforeach()
aura un stockage de données PARTAGÉ jusqu'à ce que ou à moins qu'aucunprospected copy
ne soit créé pour chaque note / commentaire utilisateur .b) Qu'est-ce qui déclenche une copie prospectée ? Une copie prospectée est créée sur la base de la politique de
copy-on-write
, c'est-à-dire que chaque fois qu'un tableau transmis àforeach()
est modifié, un clone du tableau d'origine est créé.c) Le tableau et l'
foreach()
itérateur d'origine aurontDISTINCT SENTINEL VARIABLES
, c'est-à-dire un pour le tableau d'origine et un autre pourforeach
; voir le code de test ci-dessous. SPL , Iterators et Array Iterator .Question de dépassement de pile Comment s'assurer que la valeur est réinitialisée dans une boucle «foreach» en PHP? répond aux cas (3,4,5) de votre question.
L'exemple suivant montre que each () et reset () N'affectent PAS les
SENTINEL
variables(for example, the current index variable)
de l'foreach()
itérateur.Production:
la source
foreach
fonctionne sur une copie potentielle du tableau, mais il ne fait pas la copie réelle, sauf si elle est nécessaire.foreach
copie le tableau 100% du temps. J'ai hâte de savoir. Merci pour vos commentairesfor
ouforeach
. Vous ne verrez aucune différence significative entre les deux, car une copie réelle n'a pas lieu.SHARED data storage
réservé jusqu'à ou à moinscopy-on-write
, mais (d'après mon extrait de code), il est évident qu'il y aura toujours DEUX ensembles de l'SENTINEL variables
un pour l'original array
autre et pourforeach
. Merci qui a du sensNOTE POUR PHP 7
Pour mettre à jour cette réponse car elle a gagné en popularité: Cette réponse ne s'applique plus à partir de PHP 7. Comme expliqué dans le " modifications incompatibles en amont ", dans PHP 7 foreach fonctionne sur la copie du tableau, donc tout changement sur le tableau lui-même ne sont pas reflétés sur la boucle foreach. Plus de détails sur le lien.
Explication (citation de php.net ):
Ainsi, dans votre premier exemple, vous n'avez qu'un seul élément dans le tableau, et lorsque le pointeur est déplacé, l'élément suivant n'existe pas, donc après avoir ajouté un nouvel élément pour chaque fin, car il a déjà "décidé" de le placer comme dernier élément.
Dans votre deuxième exemple, vous commencez avec deux éléments, et la boucle foreach n'est pas au dernier élément, donc elle évalue le tableau à la prochaine itération et réalise ainsi qu'il y a un nouvel élément dans le tableau.
Je crois que tout cela est la conséquence de chaque partie de l' itération de l'explication dans la documentation, ce qui signifie probablement que cela
foreach
fait toute la logique avant d'appeler le code dans{}
.Cas de test
Si vous exécutez ceci:
Vous obtiendrez cette sortie:
Ce qui signifie qu'il a accepté la modification et l'a effectuée car elle a été modifiée "dans le temps". Mais si vous faites cela:
Tu auras:
Ce qui signifie que le tableau a été modifié, mais puisque nous l'avons modifié lorsque le
foreach
qu'il se trouvait déjà au dernier élément du tableau, il a "décidé" de ne plus boucler, et même si nous avons ajouté un nouvel élément, nous l'avons ajouté "trop tard" et n'a pas été bouclé.Une explication détaillée peut être lue sur Comment fonctionne PHP «foreach»? ce qui explique les internes derrière ce comportement.
la source
Selon la documentation fournie par le manuel PHP.
Donc, selon votre premier exemple:
$array
avoir qu'un seul élément, donc selon l'exécution foreach, 1 assigner à$v
et il n'a pas d'autre élément pour déplacer le pointeurMais dans votre deuxième exemple:
$array
ont deux éléments, alors maintenant $ array évalue les indices zéro et déplace le pointeur de un. Pour la première itération de la boucle, ajoutée$array['baz']=3;
comme passe par référence.la source
Grande question, car de nombreux développeurs, même expérimentés, sont confus par la façon dont PHP gère les tableaux dans les boucles foreach. Dans la boucle foreach standard, PHP crée une copie du tableau utilisé dans la boucle. La copie est supprimée immédiatement après la fin de la boucle. Ceci est transparent dans le fonctionnement d'une simple boucle foreach. Par exemple:
Cela produit:
La copie est donc créée mais le développeur ne le remarque pas, car le tableau d'origine n'est pas référencé dans la boucle ou une fois la boucle terminée. Cependant, lorsque vous essayez de modifier les éléments dans une boucle, vous constatez qu'ils ne sont pas modifiés lorsque vous avez terminé:
Cela produit:
Les modifications par rapport à l'original ne peuvent pas être des avis, en fait il n'y a pas de changement par rapport à l'original, même si vous avez clairement attribué une valeur à $ item. En effet, vous travaillez sur $ item tel qu'il apparaît dans la copie de $ set en cours de traitement. Vous pouvez remplacer cela en saisissant $ item par référence, comme ceci:
Cela produit:
Il est donc évident et observable que lorsque $ item est opéré par référence, les modifications apportées à $ item sont apportées aux membres de l'ensemble $ original. L'utilisation de $ item par référence empêche également PHP de créer la copie du tableau. Pour tester cela, nous allons d'abord montrer un script rapide démontrant la copie:
Cela produit:
Comme le montre l'exemple, PHP a copié $ set et l'a utilisé pour boucler, mais quand $ set a été utilisé à l'intérieur de la boucle, PHP a ajouté les variables au tableau d'origine, pas le tableau copié. Fondamentalement, PHP utilise uniquement le tableau copié pour l'exécution de la boucle et l'affectation de $ item. Pour cette raison, la boucle ci-dessus ne s'exécute que 3 fois, et chaque fois elle ajoute une autre valeur à la fin de l'ensemble $ d'origine, en laissant l'ensemble $ d'origine avec 6 éléments, mais sans entrer dans une boucle infinie.
Cependant, que se passerait-il si nous avions utilisé $ item par référence, comme je l'ai mentionné précédemment? Un seul caractère ajouté au test ci-dessus:
Résultats dans une boucle infinie. Notez qu'il s'agit en fait d'une boucle infinie, vous devrez soit tuer le script vous-même, soit attendre que votre système d'exploitation manque de mémoire. J'ai ajouté la ligne suivante à mon script pour que PHP manque de mémoire très rapidement, je vous suggère de faire de même si vous allez exécuter ces tests de boucle infinie:
Donc, dans cet exemple précédent avec la boucle infinie, nous voyons la raison pour laquelle PHP a été écrit pour créer une copie du tableau à boucler. Lorsqu'une copie est créée et utilisée uniquement par la structure de la construction de boucle elle-même, le tableau reste statique pendant toute l'exécution de la boucle, vous ne rencontrerez donc jamais de problèmes.
la source
La boucle foreach PHP peut être utilisée avec
Indexed arrays
,Associative arrays
etObject public variables
.Dans la boucle foreach, la première chose que fait php est qu'il crée une copie du tableau qui doit être itéré. PHP parcourt ensuite cette nouvelle
copy
du tableau plutôt que l'original. Ceci est démontré dans l'exemple ci-dessous:En plus de cela, php permet également d'utiliser
iterated values as a reference to the original array value
. Ceci est démontré ci-dessous:Remarque: Il ne permet pas
original array indexes
d'être utilisé en tant quereferences
.Source: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples
la source
Object public variables
est faux ou au mieux trompeur. Vous ne pouvez pas utiliser un objet dans un tableau sans l'interface correcte (par exemple, Traversible) et lorsque vous le faites,foreach((array)$obj ...
vous travaillez en fait avec un tableau simple, et non plus un objet.