Referans tarafından PHP Foreach Pass: Son Eleman Çoğaltma? (Bug?)


159

Sadece yazdığım basit bir php betiği ile bazı çok garip davranışlar vardı. Hatayı yeniden oluşturmak için gereken minimum düzeye indirdim:

<?php

$arr = array("foo",
             "bar",
             "baz");

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

?>

Bu çıktılar:

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

Bu bir hata mı yoksa gerçekten garip bir davranış mı?


Tekrar değere göre yapın, 3. kez değişip değişmediğine bakın ...?
Shackrock

1
@Shackrock, döngülerin değere göre tekrarlanmasıyla artık değişmiyor gibi görünüyor.
regality

1
İlginçtir, ikinci öğeyi $ öğesi dışında bir şey kullanacak şekilde değiştirirseniz, beklendiği gibi çalışır.
Steve Claridge

9
her zaman döngü gövdesinin sonunda öğeyi unset: foreach($x AS &$y){ ... unset($y); }- aslında php.net (nerede bilmiyorum) çünkü çok yapılmış bir hata.
Rudie

Yanıtlar:


170

İlk foreach döngüsünden sonra $item, yine de tarafından kullanılan bazı değerlere bir referanstır $arr[2]. Bu nedenle, ikinci döngüdeki her bir foreach çağrısı, referans olarak çağrılamaz, bu değeri ve dolayısıyla $arr[2]yeni değeri değiştirir.

Yani döngü 1, değer ve $arr[2]haline $arr[0], ki bu 'foo'.
Döngü 2, değer ve $arr[2]haline $arr[1], 'bar'.
Döngü 3, 'bar' olan değer ve $arr[2]haline $arr[2](döngü 2 nedeniyle).

'Baz' değeri aslında ikinci foreach döngüsünün ilk çağrısında kaybolur.

Çıktıda Hata Ayıklama

Döngünün her yinelemesi için $item, diziyi yinelemeli olarak yazdırmanın yanı sıra değerini de yansıtacağız $arr.

İlk döngü tamamlandığında, bu çıktıyı görüyoruz:

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

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

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

Döngünün sonunda $item, hala aynı yere işaret ediyor $arr[2].

İkinci döngü tamamlandığında, bu çıktıyı görüyoruz:

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

Her dizinin nasıl yeni bir değer eklediğini fark edeceksiniz , ikisi de aynı yere işaret ettiğinden aynı değerle $itemgüncellendi $arr[3]. Döngü, dizinin üçüncü değerine ulaştığında, değeri baro döngünün önceki yinelemesi tarafından ayarlandığı için içerecektir .

Bu bir hata mı?

Hayır. Bu, başvurulan bir öğenin davranışıdır, bir hata değildir. Şunun gibi bir şey çalıştırmaya benzer:

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

Bir foreach döngüsü, başvurulan öğeleri yok sayabileceği özel bir özellik değildir. Basitçe, bu değişkeni her döngü dışında yaptığınız gibi yeni bir değere ayarlamaktır.


4
Hafif bir bilgiçlik düzeltmem var. $itembir referans değil$arr[2] , içerdiği değer tarafından $arr[2]atıfta bulunulan değere referanstır $item. Farkı göstermek için, ayarını kaldırabilir $arr[2]ve $itembundan etkilenmezsiniz ve yazmak, $itemonu etkilemez.
Paul Biggar

2
Bu davranışın anlaşılması karmaşıktır ve sorunlara yol açabilir. Bunu, öğrencilerime neden "referans olarak" şeylerden kaçınmaları gerektiğini göstermek için favorilerimden biri olarak saklıyorum.
Olivier Pons

1
$itemForeach döngüsünden çıkıldığında neden kapsam dışı kalmıyor? Bu bir kapatma sorunu gibi mi görünüyor?
jocull

6
@jocull: PHP'de, foreach, for, while, vb. kendi kapsamlarını oluşturmaz.
animuson

1
@jocull, PHP'nin yerel değişkenleri (blok) yoktur. Beni sinirlendirmesinin nedenlerinden biri.
Qtax

29

$item$arr[2]animuson'un işaret ettiği gibi ikinci foreach döngüsüne bir referanstır ve üzerine yazılmaktadır.

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

unset($item); // This will fix the issue.

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

3

Bu resmi olarak bir hata olmasa da, bence öyle. Sanırım buradaki sorun şu:$item , döngüden çıkıldığında diğer birçok programlama dilinde olduğu gibi kapsam dışına çıkma . Ancak durum böyle görünmüyor ...

Bu kod ...

$arr = array('one', 'two', 'three');
foreach($arr as $item){
    echo "$item\n";
}    
echo $item;

Çıktı verir ...

one
two
three
three

Diğer insanların söylediği gibi, referanslanan değişkenin üzerine $arr[2]ikinci döngünüzün üzerine yazıyorsunuz , ancak bu sadece $itemkapsamın dışına çıkmadığı için oluyor . Siz ne düşünüyorsunuz ... böcek?


4
1) Hata değil. Zaten kılavuzda çağrıldı ve amaçlandığı gibi bir dizi hata raporunda reddedildi. 2) Soruya gerçekten cevap vermiyor ...
BoltClock

Kapsam sorunu nedeniyle beni yakalamadı, $ item'in ilk foreach'dan sonra kalmasını bekledim, ancak foreach değişkenini DEĞİŞTİRMEK yerine GÜNCELLEMEDİĞİNİ fark etmedim. örneğin, ikinci döngüden önce unset ($ item) çalıştırmakla aynı. Unset değeri (ve böylece dizideki son öğeyi) temizlemez, sadece değişkeni kaldırır.
Programcı

Ne yazık ki, PHP {}genel olarak döngüler veya bloklar için yeni bir kapsam oluşturmaz . Dil şu şekilde çalışır
Fabian Schmengler


0

PHP doğru davranış benim fikrim bir BİLDİRİM hatası olabilir. Bir foreach döngüsünde oluşturulan başvurulan bir değişken, döngü dışında kullanılırsa bir bildirime neden olmalıdır. Bu davranışa düşmek çok kolay, gerçekleştiğinde fark etmek çok zor. Ve hiçbir geliştirici foreach dokümantasyon sayfasını okumaz, bu bir yardım değildir.

unset()Bu tür bir sorunu önlemek için döngünüzden sonra referans almalısınız . bir referanstaki unset (), orijinal verilere zarar vermeden referansı kaldırır.


0

çünkü ref direktifiyle (&) kullanıyorsunuz. son değer, ikinci döngü ile değiştirilir ve dizinizi bozar. en basit çözüm ikinci döngü için farklı bir isim kullanmaktır:

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.