Biarkan saya awali ini dengan mengatakan bahwa saya tahu apa foreach
itu, apakah dan bagaimana menggunakannya. Pertanyaan ini menyangkut bagaimana cara kerjanya di bawah kap, dan saya tidak ingin ada jawaban di sepanjang baris "ini adalah bagaimana Anda mengulang array dengan foreach
".
Untuk waktu yang lama saya menganggap itu foreach
bekerja dengan array itu sendiri. Kemudian saya menemukan banyak referensi tentang fakta bahwa ia bekerja dengan salinan array, dan sejak itu saya menganggap ini sebagai akhir dari cerita. Tetapi saya baru saja berdiskusi tentang masalah ini, dan setelah sedikit percobaan menemukan bahwa ini sebenarnya tidak 100% benar.
Biarkan saya menunjukkan apa yang saya maksud. Untuk kasus uji berikut, kami akan bekerja dengan array berikut:
$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 */
Ini jelas menunjukkan bahwa kami tidak bekerja secara langsung dengan array sumber - jika tidak, loop akan terus selamanya, karena kami terus-menerus mendorong item ke array selama loop. Tetapi untuk memastikan hal ini terjadi:
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 */
Ini mendukung kesimpulan awal kami, kami bekerja dengan salinan array sumber selama loop, jika tidak kita akan melihat nilai yang dimodifikasi selama loop. Tapi...
Jika kita melihat di manual , kita menemukan pernyataan ini:
Ketika foreach pertama kali mulai dieksekusi, pointer array internal secara otomatis direset ke elemen pertama array.
Benar ... ini sepertinya menyarankan bahwa foreach
bergantung pada pointer array dari array sumber. Tapi kami baru saja membuktikan bahwa kami tidak bekerja dengan array sumber , kan? Ya, tidak sepenuhnya.
// 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)
*/
Jadi, terlepas dari kenyataan bahwa kami tidak bekerja secara langsung dengan array sumber, kami bekerja secara langsung dengan pointer array sumber - fakta bahwa pointer berada di ujung array di akhir loop menunjukkan ini. Kecuali ini tidak mungkin benar - jika benar, maka test case 1 akan berulang selamanya.
Manual PHP juga menyatakan:
Karena foreach bergantung pada pointer array internal mengubahnya dalam loop dapat menyebabkan perilaku yang tidak terduga.
Baiklah, mari kita cari tahu apa itu "perilaku tak terduga" (secara teknis, perilaku apa pun tidak terduga karena saya tidak lagi tahu apa yang diharapkan).
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 */
... tidak ada yang tak terduga di sana, bahkan tampaknya mendukung teori "salinan sumber".
Pertanyaan
Apa yang terjadi disini? C-fu saya tidak cukup baik bagi saya untuk dapat mengambil kesimpulan yang tepat hanya dengan melihat kode sumber PHP, saya akan sangat menghargai jika seseorang dapat menerjemahkannya ke dalam Bahasa Inggris untuk saya.
Sepertinya saya yang foreach
bekerja dengan salinan array, tetapi menetapkan pointer array dari array sumber ke akhir array setelah loop.
- Apakah ini benar dan keseluruhan cerita?
- Jika tidak, apa yang sebenarnya dilakukannya?
- Apakah ada situasi di mana menggunakan fungsi yang mengatur pointer array (
each()
,reset()
et al.) Selama aforeach
dapat mempengaruhi hasil loop?
foreach ($array as &$value)
) - PHP perlu mengetahui posisi saat ini di array asli meskipun sebenarnya iterating di atas salinan.Jawaban:
foreach
mendukung iterasi dalam tiga jenis nilai:Traversable
bendaBerikut ini, saya akan mencoba menjelaskan dengan tepat bagaimana iterasi bekerja dalam berbagai kasus. Sejauh ini kasus yang paling sederhana adalah
Traversable
objek, karena iniforeach
pada dasarnya hanya gula sintaks untuk kode di sepanjang baris ini:Untuk kelas internal, panggilan metode aktual dihindari dengan menggunakan API internal yang pada dasarnya hanya mencerminkan
Iterator
antarmuka pada level C.Iterasi array dan objek polos secara signifikan lebih rumit. Pertama-tama, harus dicatat bahwa dalam PHP "array" adalah kamus yang benar-benar teratur dan mereka akan dilintasi menurut urutan ini (yang cocok dengan urutan penyisipan selama Anda tidak menggunakan sesuatu seperti
sort
). Ini bertentangan dengan pengulangan dengan urutan alami kunci (bagaimana daftar dalam bahasa lain sering bekerja) atau tidak memiliki urutan yang jelas sama sekali (bagaimana kamus dalam bahasa lain sering bekerja).Hal yang sama juga berlaku untuk objek, karena properti objek dapat dilihat sebagai kamus lain (dipesan) nama properti pemetaan dengan nilai-nilai mereka, ditambah beberapa penanganan visibilitas. Dalam sebagian besar kasus, properti objek sebenarnya tidak disimpan dengan cara yang agak tidak efisien ini. Namun, jika Anda mulai mengulangi objek, representasi paket yang biasanya digunakan akan dikonversi ke kamus nyata. Pada titik itu, iterasi objek polos menjadi sangat mirip dengan iterasi array (itulah sebabnya saya tidak banyak membahas iterasi objek polos di sini).
Sejauh ini bagus. Mengurai kamus tidak terlalu sulit, bukan? Masalah dimulai ketika Anda menyadari bahwa array / objek dapat berubah selama iterasi. Ada beberapa cara ini bisa terjadi:
foreach ($arr as &$v)
maka$arr
diubah menjadi referensi dan Anda dapat mengubahnya selama iterasi.$ref =& $arr; foreach ($ref as $v)
Masalah dengan mengizinkan modifikasi selama iterasi adalah kasus di mana elemen Anda saat ini dihapus. Katakanlah Anda menggunakan pointer untuk melacak elemen larik Anda saat ini. Jika elemen ini sekarang dibebaskan, Anda dibiarkan dengan pointer menggantung (biasanya menghasilkan segfault).
Ada berbagai cara untuk menyelesaikan masalah ini. PHP 5 dan PHP 7 berbeda secara signifikan dalam hal ini dan saya akan menjelaskan kedua perilaku berikut ini. Kesimpulannya adalah bahwa pendekatan PHP 5 agak bodoh dan mengarah ke semua jenis masalah tepi-aneh, sementara pendekatan PHP 7 yang lebih terlibat menghasilkan perilaku yang lebih dapat diprediksi dan konsisten.
Sebagai pendahuluan terakhir, harus dicatat bahwa PHP menggunakan penghitungan referensi dan copy-on-write untuk mengelola memori. Ini berarti bahwa jika Anda "menyalin" suatu nilai, Anda sebenarnya hanya menggunakan kembali nilai yang lama dan menambah jumlah referensi (refcount). Hanya sekali Anda melakukan beberapa jenis modifikasi, salinan asli (disebut "duplikasi") akan dilakukan. Lihat Anda dibohongi untuk pengantar yang lebih luas tentang topik ini.
PHP 5
Pointer array internal dan HashPointer
Array di PHP 5 memiliki satu "internal array pointer" (IAP), yang mendukung modifikasi: Setiap kali elemen dihapus, akan ada pemeriksaan apakah IAP menunjuk ke elemen ini. Jika ya, alih-alih maju ke elemen berikutnya.
Meskipun
foreach
memanfaatkan IAP, ada komplikasi tambahan: Hanya ada satu IAP, tetapi satu array dapat menjadi bagian dari banyakforeach
loop:Untuk mendukung dua loop simultan dengan hanya satu pointer array internal,
foreach
lakukan shenanigans berikut: Sebelum loop body dieksekusi,foreach
akan membuat cadangan pointer ke elemen saat ini dan hash-nya menjadi per-foreachHashPointer
. Setelah loop body berjalan, IAP akan diatur kembali ke elemen ini jika masih ada. Namun jika elemen telah dihapus, kami hanya akan menggunakan di mana pun IAP saat ini berada. Skema ini sebagian besar-agak-semacam bekerja, tetapi ada banyak perilaku aneh yang bisa Anda dapatkan darinya, beberapa di antaranya akan saya tunjukkan di bawah ini.Duplikasi array
IAP adalah fitur yang terlihat dari sebuah array (diekspos melalui
current
keluarga fungsi), karena itu perubahan pada jumlah IAP sebagai modifikasi di bawah semantik copy-on-write. Sayangnya, ini berarti bahwaforeach
dalam banyak kasus dipaksa untuk menduplikasi array yang sudah di-iterating. Kondisi yang tepat adalah:refcount
1, maka array tidak dibagi dan kita bebas untuk memodifikasinya secara langsung.Jika array tidak diduplikasi (is_ref = 0, refcount = 1), maka hanya arraynya yang
refcount
akan bertambah (*). Selain itu, jikaforeach
dengan referensi digunakan, maka array (berpotensi diduplikasi) akan diubah menjadi referensi.Pertimbangkan kode ini sebagai contoh di mana duplikasi terjadi:
Di sini,
$arr
akan digandakan untuk mencegah perubahan IAP$arr
dari bocor ke$outerArr
. Dalam hal kondisi di atas, array bukan referensi (is_ref = 0) dan digunakan di dua tempat (refcount = 2). Persyaratan ini sangat disayangkan dan merupakan artefak dari implementasi suboptimal (tidak ada masalah modifikasi selama iterasi di sini, jadi kita tidak benar-benar perlu menggunakan IAP sejak awal).(*) Menambah
refcount
sini terdengar tidak berbahaya, tetapi melanggar semantik copy-on-write (COW): Ini berarti bahwa kita akan memodifikasi IAP dari array refcount = 2, sementara COW menentukan bahwa modifikasi hanya dapat dilakukan pada refcount = 1 nilai. Pelanggaran ini menghasilkan perubahan perilaku yang terlihat oleh pengguna (sementara SAP biasanya transparan) karena perubahan IAP pada array yang diulang akan dapat diamati - tetapi hanya sampai modifikasi non-IAP pertama pada array. Alih-alih, tiga opsi "valid" akan menjadi a) untuk selalu menduplikasi, b) tidak menambahrefcount
dan dengan demikian memungkinkan array iterated untuk diubah secara sewenang-wenang dalam loop atau c) tidak menggunakan IAP sama sekali (PHP sama sekali 7 solusi).Urutan kenaikan posisi
Ada satu detail implementasi terakhir yang harus Anda perhatikan untuk memahami contoh kode dengan benar di bawah ini. Cara "normal" untuk perulangan melalui beberapa struktur data akan terlihat seperti ini di pseudocode:
Namun
foreach
, karena kepingan salju yang agak istimewa, memilih untuk melakukan hal-hal yang sedikit berbeda:Yaitu, pointer array sudah bergerak maju sebelum loop body berjalan. Ini berarti bahwa sementara badan loop bekerja pada elemen
$i
, IAP sudah ada di elemen$i+1
. Ini adalah alasan mengapa sampel kode yang menunjukkan modifikasi selama iterasi akan selaluunset
menjadi elemen berikutnya , bukan yang sekarang.Contoh: Kasing uji Anda
Tiga aspek yang dijelaskan di atas akan memberi Anda kesan yang lengkap tentang keanehan
foreach
implementasi dan kami dapat melanjutkan untuk membahas beberapa contoh.Perilaku kasus pengujian Anda mudah dijelaskan pada titik ini:
Dalam kasus uji 1 dan 2
$array
dimulai dengan refcount = 1, jadi itu tidak akan diduplikasi olehforeach
: Hanya yangrefcount
bertambah. Ketika badan loop selanjutnya memodifikasi array (yang memiliki refcount = 2 pada saat itu), duplikasi akan terjadi pada titik itu. Foreach akan terus mengerjakan salinan$array
.Dalam test case 3, sekali lagi array tidak diduplikasi, sehingga
foreach
akan memodifikasi IAP dari$array
variabel. Pada akhir iterasi, IAP adalah NULL (artinya iterasi telah dilakukan), yangeach
menunjukkan dengan mengembalikanfalse
.Dalam kasus uji 4 dan 5 keduanya
each
danreset
merupakan fungsi referensi. The$array
memilikirefcount=2
ketika diberikan kepada mereka, sehingga harus digandakan. Dengan demikianforeach
akan bekerja pada array yang terpisah lagi.Contoh: Efek
current
ineachCara yang baik untuk menunjukkan berbagai perilaku duplikasi adalah dengan mengamati perilaku
current()
fungsi di dalamforeach
loop. Pertimbangkan contoh ini:Di sini Anda harus tahu bahwa itu
current()
adalah fungsi by-ref (sebenarnya: prefer-ref), meskipun itu tidak mengubah array. Itu harus untuk bermain bagus dengan semua fungsi lain sepertinext
yang semuanya oleh-ref. Pass-reference passing menyiratkan bahwa array harus dipisahkan dan dengan demikian$array
danforeach-array
akan berbeda. Alasan Anda mendapatkan2
alih-alih1
juga disebutkan di atas:foreach
memajukan pointer array sebelum menjalankan kode pengguna, bukan setelahnya. Jadi meskipun kode berada di elemen pertama,foreach
sudah maju pointer ke yang kedua.Sekarang mari kita coba modifikasi kecil:
Di sini kita memiliki case is_ref = 1, sehingga array tidak disalin (seperti di atas). Tapi sekarang itu adalah referensi, array tidak lagi harus diduplikasi ketika melewati fungsi by-ref
current()
. Jadicurrent()
danforeach
bekerja pada array yang sama. Anda masih melihat perilaku off-by-one, karena caraforeach
memajukan pointer.Anda mendapatkan perilaku yang sama saat melakukan by-ref iteration:
Di sini bagian yang penting adalah bahwa foreach akan membuat
$array
is_ref = 1 ketika iterated oleh referensi, jadi pada dasarnya Anda memiliki situasi yang sama seperti di atas.Variasi kecil lainnya, kali ini kami akan menetapkan array ke variabel lain:
Di sini refcount dari
$array
adalah 2 ketika loop dimulai, jadi untuk sekali ini kita benar-benar harus melakukan duplikasi dimuka. Dengan demikian$array
dan array yang digunakan oleh foreach akan sepenuhnya terpisah dari permulaan. Itu sebabnya Anda mendapatkan posisi IAP di mana pun sebelum loop (dalam hal ini di posisi pertama).Contoh: Modifikasi selama iterasi
Mencoba untuk memperhitungkan modifikasi selama iterasi adalah tempat semua masalah kami berasal, sehingga berfungsi untuk mempertimbangkan beberapa contoh untuk kasus ini.
Pertimbangkan loop bersarang ini di atas array yang sama (di mana by-ref iteration digunakan untuk memastikan itu benar-benar sama):
Bagian yang diharapkan di sini adalah yang
(1, 2)
hilang dari output karena elemen1
telah dihapus. Apa yang mungkin tidak terduga adalah bahwa loop luar berhenti setelah elemen pertama. Mengapa demikian?Alasan di balik ini adalah hack nested-loop yang dijelaskan di atas: Sebelum loop body berjalan, posisi IAP dan hash saat ini dicadangkan menjadi a
HashPointer
. Setelah loop body akan dikembalikan, tetapi hanya jika elemen masih ada, jika tidak posisi IAP saat ini (apa pun itu) digunakan sebagai gantinya. Dalam contoh di atas, inilah yang sebenarnya terjadi: Elemen saat ini dari loop luar telah dihapus, sehingga akan menggunakan IAP, yang telah ditandai sebagai selesai oleh loop dalam!Konsekuensi lain dari
HashPointer
mekanisme backup + restore adalah bahwa perubahan pada IAP melaluireset()
dll. Biasanya tidak berdampakforeach
. Misalnya, kode berikut dijalankan seolah-olahreset()
tidak ada sama sekali:Alasannya adalah bahwa,
reset()
sementara memodifikasi sementara IAP, itu akan dikembalikan ke elemen foreach saat ini setelah tubuh loop. Untuk memaksareset()
membuat efek pada loop, Anda harus menghapus elemen saat ini, sehingga mekanisme backup / restore gagal:Tapi, contoh-contoh itu masih waras. Kegembiraan yang sebenarnya dimulai jika Anda ingat bahwa
HashPointer
pengembalian menggunakan pointer ke elemen dan hash untuk menentukan apakah itu masih ada. Tetapi: Hash memiliki benturan, dan pointer dapat digunakan kembali! Ini berarti bahwa, dengan pilihan kunci array yang hati-hati, kita dapatforeach
meyakini bahwa elemen yang telah dihapus masih ada, sehingga akan langsung melompat ke sana. Sebuah contoh:Di sini kita biasanya mengharapkan output
1, 1, 3, 4
sesuai dengan aturan sebelumnya. Bagaimana yang terjadi adalah yang'FYFY'
memiliki hash yang sama dengan elemen yang dihapus'EzFY'
, dan pengalokasi terjadi untuk menggunakan kembali lokasi memori yang sama untuk menyimpan elemen. Jadi foreach akhirnya langsung melompat ke elemen yang baru dimasukkan, sehingga memotong pendek loop.Mengganti entitas iterasi selama loop
Satu kasus aneh terakhir yang ingin saya sebutkan, adalah bahwa PHP memungkinkan Anda untuk mengganti entitas iterated selama loop. Jadi Anda bisa mulai iterasi pada satu array dan kemudian menggantinya dengan array lain di tengah jalan. Atau mulai iterasi pada array lalu ganti dengan objek:
Seperti yang Anda lihat dalam hal ini PHP hanya akan mulai mengulangi entitas lain dari awal setelah substitusi terjadi.
PHP 7
Iterator yang mudah pecah
Jika Anda masih ingat, masalah utama dengan iterasi array adalah bagaimana menangani penghapusan elemen iterasi-tengah. PHP 5 menggunakan pointer array internal tunggal (IAP) untuk tujuan ini, yang agak suboptimal, karena satu pointer array harus diregangkan untuk mendukung beberapa loop foreach simultan dan interaksi dengan
reset()
dll di atas itu.PHP 7 menggunakan pendekatan yang berbeda, yaitu, mendukung pembuatan sejumlah iterator eksternal, hashtable yang sewenang-wenang. Iterator ini harus didaftarkan dalam array, dari titik mana mereka memiliki semantik yang sama dengan IAP: Jika elemen array dihapus, semua iterator hashtable yang menunjuk ke elemen itu akan maju ke elemen berikutnya.
Ini berarti bahwa
foreach
tidak akan lagi menggunakan IAP sama sekali . Theforeach
Loop akan benar-benar tidak berpengaruh pada hasilcurrent()
dll dan perilaku sendiri tidak akan pernah dipengaruhi oleh fungsi sepertireset()
dllDuplikasi array
Perubahan penting lainnya antara PHP 5 dan PHP 7 terkait dengan duplikasi array. Sekarang IAP tidak lagi digunakan, iterasi array nilai-hanya akan melakukan
refcount
peningkatan (bukan duplikasi array) dalam semua kasus. Jika array diubah selamaforeach
loop, pada saat itu duplikasi akan terjadi (sesuai dengan copy-on-write) danforeach
akan tetap bekerja pada array yang lama.Dalam kebanyakan kasus, perubahan ini transparan dan tidak memiliki efek selain kinerja yang lebih baik. Namun, ada satu kesempatan di mana ia menghasilkan perilaku yang berbeda, yaitu kasus di mana array adalah referensi sebelumnya:
Sebelumnya oleh-nilai iterasi array referensi adalah kasus khusus. Dalam hal ini, tidak ada duplikasi yang terjadi, jadi semua modifikasi array selama iterasi akan direfleksikan oleh loop. Dalam PHP 7 kasus khusus ini hilang: iterasi nilai-nilai dari array akan selalu bekerja pada elemen asli, mengabaikan modifikasi apa pun selama loop.
Ini, tentu saja, tidak berlaku untuk iterasi referensi. Jika Anda mengulangi dengan referensi semua modifikasi akan tercermin oleh loop. Menariknya, hal yang sama berlaku untuk iterasi nilai-rata objek polos:
Hal ini mencerminkan semantik objek yang ditangani sendiri (yaitu mereka berperilaku seperti referensi bahkan dalam konteks oleh-nilai).
Contohnya
Mari kita perhatikan beberapa contoh, dimulai dengan test case Anda:
Kasing uji 1 dan 2 mempertahankan output yang sama: Iterasi array nilai-selalu bekerja pada elemen asli. (Dalam hal ini, genap
refcounting
dan perilaku duplikasi persis sama antara PHP 5 dan PHP 7).Perubahan test case 3:
Foreach
tidak lagi menggunakan IAP, jadieach()
tidak terpengaruh oleh loop. Ini akan memiliki output yang sama sebelum dan sesudah.Kasing uji 4 dan 5 tetap sama:
each()
danreset()
akan menduplikasi array sebelum mengubah IAP, sementaraforeach
masih menggunakan array asli. (Bukan berarti perubahan IAP akan menjadi masalah, bahkan jika array dibagikan.)Rangkaian contoh kedua terkait dengan perilaku di
current()
bawahreference/refcounting
konfigurasi yang berbeda . Ini tidak lagi masuk akal, karenacurrent()
sama sekali tidak terpengaruh oleh loop, sehingga nilai pengembaliannya selalu tetap sama.Namun, kami mendapatkan beberapa perubahan menarik ketika mempertimbangkan modifikasi selama iterasi. Saya harap Anda akan menemukan perilaku baru yang lebih waras. Contoh pertama:
Seperti yang Anda lihat, loop luar tidak lagi dibatalkan setelah iterasi pertama. Alasannya adalah bahwa kedua loop sekarang memiliki iterator hashtable yang sepenuhnya terpisah, dan tidak ada lagi kontaminasi silang dari kedua loop melalui IAP bersama.
Kasing tepi aneh lain yang diperbaiki sekarang, adalah efek aneh yang Anda dapatkan ketika Anda menghapus dan menambahkan elemen yang memiliki hash yang sama:
Sebelumnya mekanisme pemulihan HashPointer melompat tepat ke elemen baru karena "tampak" seperti itu sama dengan elemen yang dihapus (karena bertabrakan hash dan pointer). Karena kita tidak lagi mengandalkan hash elemen untuk apa pun, ini tidak lagi menjadi masalah.
sumber
$foo = $array
sebelum loop;)Bucket
s adalah bagian dari daftar ganda terkait untuk tabrakan hash dan juga bagian dari daftar ganda terkait untuk pesanan;)iterate($outerArr);
dan tidak diiterate($arr);
suatu tempat.Dalam contoh 3 Anda tidak mengubah array. Dalam semua contoh lain Anda memodifikasi konten atau pointer array internal. Ini penting ketika menyangkut array PHP karena semantik dari operator penugasan.
Operator penugasan untuk array dalam PHP bekerja lebih seperti klon malas. Menetapkan satu variabel ke yang lain yang berisi array akan mengkloning array, tidak seperti kebanyakan bahasa. Namun, kloning yang sebenarnya tidak akan dilakukan kecuali jika diperlukan. Ini berarti bahwa klon akan terjadi hanya ketika salah satu variabel dimodifikasi (copy-on-write).
Berikut ini sebuah contoh:
Kembali ke kasus pengujian Anda, Anda dapat dengan mudah membayangkan bahwa
foreach
menciptakan semacam iterator dengan referensi ke array. Referensi ini berfungsi persis seperti variabel$b
dalam contoh saya. Namun, iterator bersama dengan referensi hanya hidup selama loop dan kemudian, keduanya dibuang. Sekarang Anda dapat melihat bahwa, dalam semua kasus kecuali 3, array dimodifikasi selama loop, sementara referensi tambahan ini masih hidup. Ini memicu klon, dan itu menjelaskan apa yang terjadi di sini!Berikut ini adalah artikel yang bagus untuk efek samping lain dari perilaku copy-on-write ini: Operator Ternary PHP: Cepat atau tidak?
sumber
each()
pada akhir test case pertama, di mana kita melihat bahwa pointer array dari array asli menunjuk ke elemen kedua, karena array tersebut dimodifikasi selama iterasi pertama. Ini juga tampaknya menunjukkan bahwaforeach
memindahkan pointer array pada sebelum mengeksekusi blok kode loop, yang saya tidak harapkan - saya akan berpikir itu akan melakukan ini di akhir. Banyak terima kasih, ini jelas bagi saya.Beberapa hal yang perlu diperhatikan ketika bekerja dengan
foreach()
:a)
foreach
bekerja pada salinan prospektif dari array asli. Ini berartiforeach()
akan memiliki penyimpanan data SHARED sampai atau kecuali jikaprospected copy
tidak dibuat untuk setiap Catatan / komentar Pengguna .b) Apa yang memicu salinan prospektif ? Salinan prospektif dibuat berdasarkan kebijakan
copy-on-write
, yaitu, setiap kali array yang diteruskanforeach()
diubah, klon dari array asli dibuat.c) Array asli dan
foreach()
iterator akan memilikiDISTINCT SENTINEL VARIABLES
, yaitu, satu untuk array asli dan lainnya untukforeach
; lihat kode tes di bawah ini. SPL , Iterators , dan Array Iterator .Pertanyaan Stack Overflow Bagaimana memastikan nilai diatur ulang dalam loop 'foreach' di PHP? membahas kasus-kasus (3,4,5) dari pertanyaan Anda.
Contoh berikut menunjukkan bahwa setiap () dan reset () TIDAK mempengaruhi
SENTINEL
variabel(for example, the current index variable)
dariforeach()
iterator.Keluaran:
sumber
foreach
beroperasi pada salinan potensial array, tetapi tidak membuat salinan yang sebenarnya kecuali diperlukan.foreach
menyalin array 100% dari waktu. Saya ingin sekali tahu. Terima kasih atas komentar Andafor
atauforeach
. Anda tidak akan melihat perbedaan yang signifikan di antara keduanya, karena salinan yang sebenarnya tidak terjadi.SHARED data storage
dicadangkan sampai atau kecualicopy-on-write
, tetapi (dari potongan kode saya) jelas bahwa akan selalu ada DUA setSENTINEL variables
satu untukoriginal array
dan untuk lainnyaforeach
. Terima kasih itu masuk akalCATATAN UNTUK PHP 7
Untuk memperbarui jawaban ini karena telah mendapatkan popularitas: Jawaban ini tidak lagi berlaku pada PHP 7. Seperti yang dijelaskan dalam " Perubahan ke belakang yang tidak kompatibel ", dalam PHP 7, masing-masing bekerja pada salinan array, sehingga setiap perubahan pada array itu sendiri tidak tercermin pada foreach loop. Lebih detail di tautan.
Penjelasan (kutipan dari php.net ):
Jadi, dalam contoh pertama Anda, Anda hanya memiliki satu elemen dalam array, dan ketika pointer dipindahkan, elemen berikutnya tidak ada, jadi setelah Anda menambahkan elemen baru untuk setiap ujung karena sudah "memutuskan" bahwa itu sebagai elemen terakhir.
Dalam contoh kedua Anda, Anda mulai dengan dua elemen, dan foreach loop bukan pada elemen terakhir sehingga mengevaluasi array pada iterasi berikutnya dan dengan demikian menyadari bahwa ada elemen baru dalam array.
Saya percaya bahwa ini semua konsekuensi dari Pada setiap bagian iterasi dari penjelasan dalam dokumentasi, yang mungkin berarti bahwa
foreach
melakukan semua logika sebelum memanggil kode{}
.Kasus cobaan
Jika Anda menjalankan ini:
Anda akan mendapatkan hasil ini:
Yang berarti menerima modifikasi dan pergi melalui itu karena itu diubah "dalam waktu". Tetapi jika Anda melakukan ini:
Kamu akan mendapatkan:
Yang berarti array telah dimodifikasi, tetapi karena kami memodifikasinya ketika elemen yang
foreach
sudah ada di array terakhir, itu "memutuskan" untuk tidak mengulang lagi, dan meskipun kami menambahkan elemen baru, kami menambahkannya "terlambat" dan itu tidak diulang.Penjelasan terperinci dapat dibaca di Bagaimana cara PHP 'foreach' bekerja? yang menjelaskan internal di balik perilaku ini.
sumber
Sesuai dokumentasi yang disediakan oleh manual PHP.
Jadi sesuai contoh pertama Anda:
$array
hanya memiliki satu elemen, sehingga sesuai dengan eksekusi sebelumnya, 1 ditugaskan$v
dan tidak memiliki elemen lain untuk memindahkan pointerTetapi dalam contoh kedua Anda:
$array
memiliki dua elemen, jadi sekarang $ array mengevaluasi indeks nol dan memindahkan pointer dengan satu. Untuk iterasi loop pertama, ditambahkan$array['baz']=3;
sebagai referensi by pass.sumber
Pertanyaan besar, karena banyak pengembang, bahkan yang berpengalaman, bingung dengan cara PHP menangani array di foreach loop. Dalam loop foreach standar, PHP membuat salinan array yang digunakan dalam loop. Salinan dibuang segera setelah loop selesai. Ini transparan dalam pengoperasian loop foreach sederhana. Sebagai contoh:
Output ini:
Jadi salinan dibuat tetapi pengembang tidak memperhatikan, karena array asli tidak direferensikan dalam loop atau setelah loop selesai. Namun, ketika Anda mencoba untuk memodifikasi item dalam satu lingkaran, Anda menemukan bahwa mereka tidak dimodifikasi ketika Anda selesai:
Output ini:
Setiap perubahan dari yang asli tidak boleh menjadi pemberitahuan, sebenarnya tidak ada perubahan dari yang asli, meskipun Anda dengan jelas menetapkan nilai ke $ item. Ini karena Anda beroperasi pada $ item karena muncul dalam salinan $ set yang sedang dikerjakan. Anda dapat mengesampingkan ini dengan meraih $ item dengan referensi, seperti:
Output ini:
Jadi jelas dan dapat diamati, ketika $ item dioperasikan berdasarkan referensi, perubahan yang dilakukan ke $ item dilakukan kepada anggota dari set $ asli. Menggunakan $ item dengan referensi juga mencegah PHP membuat salinan array. Untuk menguji ini, pertama-tama kami akan menampilkan skrip cepat yang menunjukkan salinan:
Output ini:
Seperti yang ditunjukkan pada contoh, PHP menyalin $ set dan menggunakannya untuk mengulang, tetapi ketika $ set digunakan di dalam loop, PHP menambahkan variabel ke array asli, bukan array yang disalin. Pada dasarnya, PHP hanya menggunakan array yang disalin untuk mengeksekusi loop dan penugasan $ item. Karena itu, loop di atas hanya mengeksekusi 3 kali, dan setiap kali menambahkan nilai lain ke akhir dari set $ asli, meninggalkan $ set asli dengan 6 elemen, tetapi tidak pernah memasukkan loop infinite.
Namun, bagaimana jika kami menggunakan $ item dengan referensi, seperti yang saya sebutkan sebelumnya? Satu karakter ditambahkan ke tes di atas:
Hasil dalam loop tak terbatas. Perhatikan ini sebenarnya adalah infinite loop, Anda harus mematikan skrip sendiri atau menunggu OS Anda kehabisan memori. Saya menambahkan baris berikut ke skrip saya sehingga PHP akan kehabisan memori dengan sangat cepat, saya sarankan Anda melakukan hal yang sama jika Anda akan menjalankan tes loop tak terbatas ini:
Jadi dalam contoh sebelumnya dengan infinite loop, kita melihat alasan mengapa PHP ditulis untuk membuat salinan array untuk di-loop. Ketika salinan dibuat dan digunakan hanya oleh struktur loop itu sendiri, array tetap statis sepanjang eksekusi loop, sehingga Anda tidak akan pernah mengalami masalah.
sumber
PHP foreach loop dapat digunakan dengan
Indexed arrays
,Associative arrays
danObject public variables
.Dalam foreach loop, hal pertama yang dilakukan php adalah membuat salinan array yang akan diulangi. PHP kemudian beralih ke
copy
array baru ini daripada yang asli. Ini ditunjukkan dalam contoh di bawah ini:Selain itu, php juga memungkinkan untuk digunakan
iterated values as a reference to the original array value
. Ini ditunjukkan di bawah ini:Catatan: Ini tidak memungkinkan
original array indexes
untuk digunakan sebagaireferences
.Sumber: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples
sumber
Object public variables
salah atau paling tidak menyesatkan. Anda tidak dapat menggunakan objek dalam array tanpa antarmuka yang benar (misalnya, Traversible) dan ketika Anda melakukannya,foreach((array)$obj ...
Anda sebenarnya bekerja dengan array sederhana, bukan objek lagi.