โครงสร้าง foreach เป็นรูปแบบของ for ที่รวมอยู่ในภาษาเพื่อให้การวนซ้ำองค์ประกอบของอาร์เรย์ง่ายขึ้น คำสั่ง foreach มีสองรูปแบบ ซึ่งออกแบบมาสำหรับอาร์เรย์ประเภทต่างๆ:
foreach (อาร์เรย์เป็น $element) (
foreach (อาร์เรย์เป็น $key => $element) (
ตัวอย่างเช่น เมื่อดำเนินการตัวอย่างต่อไปนี้:
$menu = аrrау("พาสต้า", "สเต็ก", "มันฝรั่ง", "ปลา", "มันฝรั่งทอด");
foreach ($เมนูเป็น $item) (
พิมพ์ "$item
";
ผลลัพธ์ต่อไปนี้จะถูกส่งออก:
มีสองสิ่งที่ควรทราบในตัวอย่างนี้ ขั้นแรก โครงสร้าง foreach จะกลับไปยังจุดเริ่มต้นของอาร์เรย์โดยอัตโนมัติ (ซึ่งจะไม่เกิดขึ้นในโครงสร้างการวนซ้ำอื่นๆ) ประการที่สอง ไม่จำเป็นต้องเพิ่มตัวนับอย่างชัดเจนหรือย้ายไปยังองค์ประกอบถัดไปของอาร์เรย์ สิ่งนี้จะเกิดขึ้นโดยอัตโนมัติเมื่อมีการวนซ้ำแต่ละครั้งของ foreach
ตัวเลือกที่สองจะใช้เมื่อทำงานกับอาร์เรย์ที่เชื่อมโยง:
$wine_inventory = อาร์เรย์ (
"เมอร์ล็อต" => 15,
"ซินฟานเดล" => 17,
"โซวิญง" => 32
foreach ($wine_inventory เป็น $i => $item_count) (
พิมพ์ "$item_count ขวดของ $i ที่เหลืออยู่
";
ในกรณีนี้ผลลัพธ์จะเป็นดังนี้:
เมอร์ลอตเหลือ 15 ขวด
ซินฟานเดลเหลืออีก 17 ขวด
โซวิญงเหลือ 32 ขวด
ดังที่คุณเห็นจากตัวอย่างด้านบน โครงสร้าง foreach ช่วยให้การทำงานกับอาร์เรย์ง่ายขึ้นอย่างมาก
หลักการทำงานของโครงสร้างสวิตช์ค่อนข้างชวนให้นึกถึง if - ผลลัพธ์ที่ได้จากการประเมินนิพจน์จะถูกตรวจสอบกับรายการการจับคู่ที่เป็นไปได้
สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อตรวจสอบค่าหลายค่า เนื่องจากการใช้สวิตช์จะทำให้โปรแกรมมองเห็นได้ชัดเจนและกะทัดรัดยิ่งขึ้น รูปแบบทั่วไปของคำสั่ง switch คือ:
สวิตช์ (การแสดงออก) (
กรณี (เงื่อนไข):
กรณี (เงื่อนไข):
เงื่อนไขที่กำลังทดสอบจะระบุอยู่ในวงเล็บหลังคีย์เวิร์ด switch ผลลัพธ์ของการคำนวณจะถูกเปรียบเทียบตามลำดับกับเงื่อนไขในส่วนกรณี หากพบรายการที่ตรงกัน บล็อกของส่วนที่เกี่ยวข้องจะถูกดำเนินการ หากไม่พบรายการที่ตรงกัน ระบบจะดำเนินการบล็อกส่วนเริ่มต้นที่เป็นตัวเลือก
ดังที่คุณจะเห็นในบทต่อๆ ไป หนึ่งในจุดแข็งที่ยิ่งใหญ่ที่สุดของ PHP ก็คือการจัดการอินพุตของผู้ใช้ สมมติว่าโปรแกรมแสดงรายการแบบหล่นลงพร้อมตัวเลือกหลายตัว และแต่ละบรรทัดในรายการสอดคล้องกับคำสั่งที่ถูกดำเนินการในโครงสร้างเคสที่แยกจากกัน สะดวกมากในการสร้างการใช้งานโดยใช้คำสั่ง switch:
$user_input = "สูตรอาหาร"; // คำสั่งที่ผู้ใช้เลือก
สวิตช์ ($user_input) :
case("ค้นหา") :
พิมพ์ "มาทำการค้นหากันเถอะ!";
case("พจนานุกรม") :
พิมพ์ "คุณต้องการค้นหาคำอะไร";
case("สูตรอาหาร") :
print "นี่คือรายการสูตรอาหาร...";
พิมพ์ "นี่คือเมนู...";
ดังที่คุณเห็นจากส่วนด้านบน คำสั่ง switch ช่วยให้จัดระเบียบโค้ดได้ชัดเจนและมองเห็นได้ ตัวแปรที่ระบุในเงื่อนไขสวิตช์ (ในตัวอย่างนี้ $user_input) จะถูกเปรียบเทียบกับเงื่อนไขของส่วนกรณีที่ตามมาทั้งหมด หากค่าที่ระบุในส่วนกรณีตรงกับค่าของตัวแปรที่จะเปรียบเทียบ บล็อกของส่วนนี้จะถูกดำเนินการ คำสั่งแบ่งป้องกันไม่ให้มีการตรวจสอบส่วนกรณีและปัญหาเพิ่มเติม และสิ้นสุดการดำเนินการของโครงสร้างสวิตช์ หากไม่มีตรงตามเงื่อนไขที่เลือก ส่วนเริ่มต้นที่เป็นตัวเลือกจะถูกเปิดใช้งาน หากไม่มีส่วนเริ่มต้นและไม่มีเงื่อนไขใดเป็นจริง คำสั่ง switch จะสิ้นสุดลงและการทำงานของโปรแกรมจะดำเนินต่อไปด้วยคำสั่งถัดไป
คุณต้องจำไว้ว่าหากไม่มีคำสั่ง break ในส่วนของ case (ดูหัวข้อถัดไป) การดำเนินการของสวิตช์จะดำเนินต่อไปด้วยคำสั่งถัดไป จนกว่าจะพบคำสั่ง break หรือถึงจุดสิ้นสุดของคำสั่ง switch ตัวอย่างต่อไปนี้แสดงให้เห็นถึงผลที่ตามมาของการไม่มีคำสั่ง break: $value = 0.4;
สวิตช์($มูลค่า) :
พิมพ์ "ค่าคือ 0.4
";
พิมพ์ "ค่าคือ 0.6
";
พิมพ์ "ค่าเป็น 0.3
";
พิมพ์ "คุณไม่ได้เลือกค่า!";
ผลลัพธ์มีลักษณะดังนี้:
การไม่มีคำสั่งแบ่งส่งผลให้ดำเนินการไม่เพียงแต่คำสั่งพิมพ์ในส่วนที่พบรายการที่ตรงกัน แต่ยังรวมถึงคำสั่งพิมพ์ในส่วนถัดไปด้วย คำสั่งในคำสั่ง switch ถูกขัดจังหวะโดยคำสั่ง switch ตามคำสั่งพิมพ์ที่สอง
การเลือกระหว่างคำสั่งสวิตช์และคำสั่ง if แทบไม่มีผลกระทบต่อประสิทธิภาพของโปรแกรม การตัดสินใจใช้การออกแบบอย่างใดอย่างหนึ่งค่อนข้างเป็นเรื่องส่วนตัวสำหรับโปรแกรมเมอร์
คำสั่ง break จะตัดคำสั่ง while, for หรือ switch ที่พบทันที คำสั่งนี้ได้ถูกกล่าวถึงแล้วในส่วนที่แล้ว แต่การขัดจังหวะการวนซ้ำปัจจุบันไม่ได้ทำให้ความสามารถของคำสั่ง break หมดไป โดยทั่วไปแล้ว ไวยากรณ์ตัวแบ่งจะมีลักษณะดังนี้:
พารามิเตอร์ทางเลือก n ระบุจำนวนระดับของโครงสร้างการควบคุมที่สิ้นสุดโดยคำสั่ง break ตัวอย่างเช่น หากคำสั่ง break ซ้อนอยู่ภายในสองคำสั่ง while และคำสั่ง break ตามด้วยหมายเลข 2 ทั้งสองลูปจะออกจากระบบทันที ค่าเริ่มต้นสำหรับ n คือ 1; การออกไปยังระดับหนึ่งสามารถระบุได้โดยการระบุ 1 อย่างชัดเจน หรือโดยการระบุคำสั่ง break โดยไม่มีพารามิเตอร์ โปรดทราบว่าคำสั่ง i f ไม่ใช่หนึ่งในโครงสร้างการควบคุมที่สามารถขัดจังหวะโดยคำสั่ง break
PHP foreach loop สามารถใช้ได้ดังนี้:
foreach($array_name as $value)( //โค้ดที่จะดำเนินการ)
foreach($array_name as $key =>$value)( // //โค้ดที่ควรดำเนินการ)
ตัวอย่างการใช้ foreach loop กับอาร์เรย์ตัวเลข
ในตัวอย่างนี้ เราจะสร้างอาร์เรย์ที่มีห้าองค์ประกอบที่มีค่าตัวเลข จากนั้น PHP foreach loop จะถูกนำมาใช้เพื่อวนซ้ำผ่านอาร์เรย์นี้ ภายใน foreach loop เราใช้ echo เพื่อพิมพ์ค่าอาร์เรย์:
ดูการสาธิตและรหัส
ตัวอย่างคีย์อาร์เรย์และค่า
ตัวอย่างนี้อธิบายวิธีอื่นในการใช้ PHP foreach loop เมื่อต้องการทำเช่นนี้ เราได้สร้างอาร์เรย์ที่เชื่อมโยงขององค์ประกอบสามรายการ ประกอบด้วยชื่อพนักงาน ( เป็นกุญแจ) และจำนวนค่าจ้าง ( เป็นค่านิยม):
ดูการสาธิตและรหัส
ตัวอย่างการเปลี่ยนแปลงค่าขององค์ประกอบอาร์เรย์ในวง foreach
คุณยังสามารถใช้ PHP array foreach เพื่อเปลี่ยนค่าขององค์ประกอบอาร์เรย์ได้ ซึ่งทำได้โดยใช้ "&" ก่อน "$" สำหรับตัวแปรค่า ตัวอย่างเช่น:
&$value_of_element
ค่าจะมีการเปลี่ยนแปลง เพื่อให้ชัดเจนยิ่งขึ้น ลองพิจารณาตัวอย่างต่อไปนี้
ในตัวอย่างนี้ เราได้สร้างอาร์เรย์ตัวเลขที่มีห้าองค์ประกอบ หลังจากนั้นเราใช้ foreach loop เพื่อแสดงค่าขององค์ประกอบ
จากนั้นเราสร้าง foreach loop อีกวงหนึ่ง โดยที่ "& " จะถูกเพิ่มก่อน $value_of_element ภายในวงเล็บปีกกาเรากำหนดค่าใหม่ให้กับองค์ประกอบของอาร์เรย์
หากต้องการดูความแตกต่างก่อนและหลังการกำหนดค่าใหม่ อาร์เรย์จะแสดงโดยใช้ฟังก์ชัน print_r()
ดูการสาธิตและรหัส
PHP foreach loop ใช้ทำอะไร?
PHP foreach loop ใช้เพื่อทำงานกับอาร์เรย์ มันวนซ้ำแต่ละองค์ประกอบ
คุณยังสามารถใช้ for loop เพื่อทำงานกับอาร์เรย์ได้ ตัวอย่างเช่น การใช้คุณสมบัติ length เพื่อรับความยาวของอาร์เรย์ จากนั้นใช้เป็นตัวดำเนินการสูงสุด แต่ foreach ทำให้ง่ายขึ้นเนื่องจากได้รับการออกแบบให้ทำงานกับอาร์เรย์
หากคุณทำงานกับ MySQL วงจรนี้จะยิ่งเหมาะสมกว่าสำหรับสิ่งนี้ ตัวอย่างเช่น คุณสามารถเลือกหลายแถวจากตารางฐานข้อมูลและส่งผ่านไปยังอาร์เรย์ได้ หลังจากนั้น ให้ใช้ foreach loop วนซ้ำองค์ประกอบทั้งหมดของอาร์เรย์และดำเนินการบางอย่าง
โปรดทราบว่าคุณสามารถใช้ foreach loop กับอาร์เรย์หรือเพียงแค่อ็อบเจ็กต์ก็ได้
การใช้ลูป foreach
มีสองวิธีในการใช้ PHP foreach loop ใน PHP ทั้งสองอธิบายไว้ด้านล่าง
- ไวยากรณ์สำหรับวิธีแรกคือ:
foreach($array_name เป็น $value)( echo $value )
ในกรณีนี้ คุณต้องระบุชื่อของอาร์เรย์ จากนั้นระบุตัวแปร $value
สำหรับการวนซ้ำแต่ละครั้ง ค่าขององค์ประกอบปัจจุบันจะถูกกำหนดให้กับตัวแปร $value หลังจากการวนซ้ำเสร็จสิ้น ตัวแปรจะถูกกำหนดค่าขององค์ประกอบถัดไป และต่อไปเรื่อยๆ จนกว่าองค์ประกอบทั้งหมดของอาร์เรย์จะถูกวนซ้ำ
- ไวยากรณ์ของวิธีที่สอง ( PHP foreach เป็นค่าคีย์):
เหมาะสำหรับอาร์เรย์แบบเชื่อมโยงที่ใช้คู่คีย์/ค่า
ในระหว่างการวนซ้ำแต่ละครั้ง ค่าขององค์ประกอบปัจจุบันจะถูกกำหนดให้กับตัวแปร $value_of_element นอกจากนี้ คีย์องค์ประกอบยังถูกกำหนดให้กับตัวแปร $key_of_element
หากคุณกำลังทำงานกับอาร์เรย์ตัวเลข คุณสามารถใช้วิธีแรกซึ่งไม่จำเป็นต้องใช้คีย์องค์ประกอบ
เอกสารฉบับนี้เป็นการแปลบทความ “ PHP foreach loop มี 2 วิธีในการใช้งาน"จัดทำโดยทีมงานโครงการที่เป็นมิตร
ในสรุปลิงก์ที่น่าสนใจเกี่ยวกับ PHP ล่าสุด ฉันพบลิงก์ไปยังความคิดเห็นของ Nikita Popov ใน StackOverflow ซึ่งเขาพูดถึงรายละเอียดเกี่ยวกับกลไก "ภายใต้ประทุน" ของโครงสร้างการควบคุม foreach
เนื่องจากบางครั้ง foreach ทำงานในรูปแบบแปลกๆ เล็กน้อย ฉันคิดว่าการแปลคำตอบนี้น่าจะมีประโยชน์
ข้อควรสนใจ: ข้อความนี้ถือว่ามีความรู้พื้นฐานเกี่ยวกับการทำงานของ zvals ใน PHP โดยเฉพาะอย่างยิ่งคุณควรรู้ว่า refcount และ is_ref คืออะไร
foreach ทำงานร่วมกับเอนทิตีประเภทต่างๆ: กับอาร์เรย์ กับอ็อบเจ็กต์แบบธรรมดา (โดยที่คุณสมบัติมีอยู่ในรายการ) และกับอ็อบเจ็กต์ Traversable (หรือค่อนข้างเป็นอ็อบเจ็กต์ที่มีตัวจัดการ get_iterator ภายในกำหนดไว้) ส่วนใหญ่เรากำลังพูดถึงอาร์เรย์ที่นี่ แต่ฉันจะพูดถึงส่วนที่เหลือในตอนท้ายสุด
ก่อนที่เราจะเริ่มต้น คำสองสามคำเกี่ยวกับอาร์เรย์และการข้ามผ่าน ซึ่งมีความสำคัญต่อการทำความเข้าใจบริบท
การข้ามผ่านอาร์เรย์ทำงานอย่างไร
อาร์เรย์ใน PHP จะถูกเรียงลำดับตารางแฮช (องค์ประกอบแฮชจะรวมกันเป็นรายการที่เชื่อมโยงแบบทวีคูณ) และ foreach จะสำรวจอาร์เรย์ตามลำดับที่ระบุ
PHP มีสองวิธีในการสำรวจอาร์เรย์:
- วิธีแรกคือตัวชี้อาร์เรย์ภายใน ตัวชี้นี้เป็นส่วนหนึ่งของโครงสร้าง HashTable และเป็นเพียงตัวชี้ไปยังองค์ประกอบตารางแฮชปัจจุบัน ตัวชี้อาร์เรย์ภายในสามารถเปลี่ยนแปลงได้โดยไม่ต้องรับโทษ นั่นคือหากองค์ประกอบปัจจุบันถูกลบ ตัวชี้อาร์เรย์ภายในจะถูกย้ายไปยังองค์ประกอบถัดไป
- กลไกการวนซ้ำที่สองคือตัวชี้อาร์เรย์ภายนอกที่เรียกว่า HashPosition โดยพื้นฐานแล้วจะเหมือนกับตัวชี้อาร์เรย์ภายใน แต่ไม่ได้เป็นส่วนหนึ่งของ HashTable วิธีการวนซ้ำภายนอกนี้ไม่ปลอดภัยต่อการเปลี่ยนแปลง หากคุณลบองค์ประกอบที่ HashPosition ชี้ไป คุณจะเหลือตัวชี้ห้อย ซึ่งจะส่งผลให้เกิดข้อผิดพลาดในการแบ่งส่วน
ดังนั้น ควรใช้พอยน์เตอร์อาร์เรย์ภายนอกเฉพาะเมื่อคุณแน่ใจจริงๆ ว่าจะไม่มีการเรียกใช้โค้ดผู้ใช้ในระหว่างการข้ามผ่าน และโค้ดดังกล่าวอาจไปอยู่ในตำแหน่งที่ไม่คาดคิด เช่น ตัวจัดการข้อผิดพลาดหรือตัวทำลาย นี่คือสาเหตุที่ในกรณีส่วนใหญ่ PHP ต้องใช้ตัวชี้ภายในแทนตัวชี้ภายนอก หากไม่เป็นเช่นนั้น PHP อาจขัดข้องด้วยข้อผิดพลาดในการแบ่งส่วนทันทีที่ผู้ใช้เริ่มทำอะไรผิดปกติ
ปัญหาเกี่ยวกับตัวชี้ภายในคือมันเป็นส่วนหนึ่งของ HashTable ดังนั้นเมื่อคุณเปลี่ยน HashTable จะเปลี่ยนตามไปด้วย และเนื่องจากอาร์เรย์ใน PHP เข้าถึงได้ด้วยค่า (ไม่ใช่โดยการอ้างอิง) คุณจึงถูกบังคับให้คัดลอกอาร์เรย์เพื่อวนซ้ำองค์ประกอบต่างๆ
ตัวอย่างง่ายๆ ที่แสดงให้เห็นถึงความสำคัญของการคัดลอก (ไม่ใช่เรื่องแปลก) คือการวนซ้ำแบบซ้อน:
foreach ($array as $a) ( foreach ($array as $b) ( // ... ) )
ที่นี่คุณต้องการให้ทั้งสองลูปเป็นอิสระจากกัน และไม่ใช้พอยน์เตอร์ตัวเดียวโยนไปมาอย่างชาญฉลาด
เราจึงมาประกาศ
การสำรวจอาร์เรย์ใน foreach
ตอนนี้คุณรู้แล้วว่าทำไม foreach ต้องสร้างสำเนาของอาร์เรย์ก่อนจะสำรวจมัน แต่นี่ไม่ใช่เรื่องราวทั้งหมดอย่างชัดเจน PHP จะทำสำเนาหรือไม่นั้นขึ้นอยู่กับหลายปัจจัย:
- หากอาร์เรย์ที่ทำซ้ำได้เป็นข้อมูลอ้างอิง จะไม่มีการคัดลอกเกิดขึ้น แต่จะดำเนินการ addref แทน:
$อ้างอิง =& $อาร์เรย์; // $array มี is_ref=1 ตอนนี้ foreach ($array as $val) ( // ... )
ทำไม เนื่องจากการเปลี่ยนแปลงใดๆ ในอาร์เรย์จะต้องเผยแพร่โดยการอ้างอิง รวมถึงตัวชี้ภายในด้วย หาก foreach ทำสำเนาในกรณีนี้ จะทำให้ความหมายของลิงก์เสียหาย - หากอาร์เรย์มี refcount=1 การคัดลอกจะไม่เกิดขึ้นอีก refcount=1 หมายความว่าอาร์เรย์ไม่ได้ใช้ที่อื่นและ foreach สามารถใช้โดยตรงได้ หากการนับซ้ำมากกว่าหนึ่ง อาร์เรย์จะถูกแชร์กับตัวแปรอื่น ๆ และเพื่อหลีกเลี่ยงการแก้ไข foreach จะต้องคัดลอกมัน (โดยไม่คำนึงถึงกรณีอ้างอิงที่อธิบายไว้ข้างต้น)
- ถ้าอาร์เรย์มีการอ้างอิงข้ามผ่าน (foreach ($array as &$ref)) ดังนั้น โดยไม่คำนึงถึงฟังก์ชันคัดลอกหรือไม่คัดลอก อาร์เรย์จะกลายเป็นข้อมูลอ้างอิง
นี่คือส่วนแรกของปริศนานี้: ฟังก์ชันการคัดลอก ส่วนที่สองคือวิธีการดำเนินการวนซ้ำในปัจจุบัน และมันก็ค่อนข้างแปลกเช่นกัน รูปแบบการวนซ้ำ "ปกติ" ที่คุณรู้อยู่แล้ว (และซึ่งมักใช้ใน PHP - แยกจาก foreach) มีลักษณะดังนี้ (pseudocode):
รีเซ็ต(); ในขณะที่ (get_current_data(&data) == ความสำเร็จ) ( รหัส(); move_forward(); )
การวนซ้ำ foreach ดูแตกต่างออกไปเล็กน้อย:
รีเซ็ต(); ในขณะที่ (get_current_data(&data) == ความสำเร็จ) ( move_forward(); code(); )
ข้อแตกต่างก็คือ move_forward() จะถูกดำเนินการที่จุดเริ่มต้น แทนที่จะดำเนินการที่จุดสิ้นสุดของลูป ดังนั้น เมื่อโค้ดผู้ใช้ใช้องค์ประกอบ $i ตัวชี้อาร์เรย์ภายในจะชี้ไปที่องค์ประกอบ $i+1 แล้ว
โหมดการทำงานของ foreach นี้เป็นสาเหตุที่ทำให้ตัวชี้อาร์เรย์ภายในย้ายไปยังองค์ประกอบถัดไป หากองค์ประกอบปัจจุบันถูกลบ แทนที่จะเป็นองค์ประกอบก่อนหน้า (ตามที่คุณคาดหวัง) ทุกอย่างได้รับการออกแบบมาให้ทำงานได้ดีกับ foreach (แต่เห็นได้ชัดว่าจะใช้งานไม่ได้กับสิ่งอื่นทั้งหมด โดยข้ามองค์ประกอบต่างๆ)
ความหมายของรหัส
ผลลัพธ์แรกของพฤติกรรมข้างต้นคือ foreach จะคัดลอกอาร์เรย์ที่ทำซ้ำได้ในหลายกรณี (อย่างช้าๆ) แต่อย่ากลัวเลย: ฉันพยายามลบข้อกำหนดในการคัดลอกออก และไม่เห็นการเร่งความเร็วใดๆ เลย ยกเว้นในเกณฑ์มาตรฐานปลอม (ซึ่งทำซ้ำได้เร็วเป็นสองเท่า) ดูเหมือนว่าผู้คนจะทำซ้ำไม่เพียงพอ
ผลที่ตามมาประการที่ 2 คือ มักจะไม่มีผลอื่นตามมาอีก โดยทั่วไปแล้วพฤติกรรมของ foreach นั้นค่อนข้างเข้าใจได้สำหรับผู้ใช้และทำงานได้อย่างที่ควรจะเป็น คุณไม่ควรสนใจว่าการคัดลอกจะเกิดขึ้นอย่างไร (หรือเกิดขึ้นเลย) หรือเมื่อถึงเวลาใดที่ตัวชี้ถูกย้าย
และผลที่ตามมาประการที่สาม - และนี่คือจุดที่เกิดปัญหาของคุณ - คือบางครั้งเราเห็นพฤติกรรมแปลก ๆ ที่ยากจะเข้าใจ สิ่งนี้จะเกิดขึ้นโดยเฉพาะเมื่อคุณพยายามแก้ไขอาร์เรย์เอง ซึ่งคุณกำลังสำรวจในลูป
ชุดพฤติกรรม Edge Case จำนวนมากที่เกิดขึ้นเมื่อคุณแก้ไขอาร์เรย์ระหว่างการวนซ้ำสามารถพบได้ในการทดสอบ PHP คุณสามารถเริ่มต้นด้วยการทดสอบนี้ จากนั้นเปลี่ยน 012 เป็น 013 ในที่อยู่ และอื่นๆ คุณจะเห็นว่าพฤติกรรม foreach จะแสดงออกมาอย่างไรในสถานการณ์ต่างๆ (การรวมลิงก์ทุกประเภท ฯลฯ)
ตอนนี้เรากลับมาที่ตัวอย่างของคุณ:
ข้างหน้า ($array เป็น $item) ( echo "$item\n"; $array = $item; ) print_r($array); /* เอาต์พุตในลูป: 1 2 3 4 5 $array หลังลูป: 1 2 3 4 5 1 2 3 4 5 */
ที่นี่ $array มี refcount=1 ก่อนลูป ดังนั้นมันจะไม่ถูกคัดลอก แต่จะได้รับ addref เมื่อคุณกำหนดค่าให้กับ $array แล้ว zval จะถูกแบ่ง ดังนั้นอาร์เรย์ที่คุณกำลังเพิ่มองค์ประกอบเข้าไปและอาร์เรย์ที่คุณกำลังวนซ้ำจะเป็นอาร์เรย์สองตัวที่แตกต่างกัน
ข้างหน้า ($array as $key => $item) ( $array[$key + 1] = $item + 2; echo "$item\n"; ) print_r($array); /* เอาต์พุตในลูป: 1 2 3 4 5 $array หลังลูป: 1 3 4 5 6 7 */
สถานการณ์เดียวกันกับในการทดสอบครั้งแรก
// เลื่อนตัวชี้ไปทีละตัวเพื่อให้แน่ใจว่าจะไม่ส่งผลต่อ foreach var_dump(each($array)); foreach ($array เป็น $item) ( echo "$item\n"; ) var_dump(each($array)); /* อาร์เรย์เอาท์พุต(4) ( => int(1) ["value"]=> int(1) => int(0) ["key"]=> int(0) ) 1 2 3 4 5 bool( เท็จ) */
เรื่องเดียวกันอีกครั้ง ในระหว่างลูป foreach คุณมี refcount=1 และคุณได้รับเพียง addref เท่านั้น ตัวชี้ $array ภายในจะถูกแก้ไข เมื่อสิ้นสุดการวนซ้ำ ตัวชี้จะกลายเป็น NULL (ซึ่งหมายความว่าการวนซ้ำเสร็จสมบูรณ์) แต่ละคนแสดงให้เห็นสิ่งนี้โดยส่งคืนเท็จ
foreach ($array as $key => $item) ( echo "$item\n"; แต่ละ($array); ) /* เอาท์พุต: 1 2 3 4 5 */
ข้างหน้า ($array as $key => $item) ( echo "$item\n"; reset($array); ) /* เอาท์พุต: 1 2 3 4 5 */
แต่ตัวอย่างเหล่านี้ไม่น่าเชื่อถือเพียงพอ พฤติกรรมเริ่มคาดเดาไม่ได้จริงๆ เมื่อคุณใช้กระแสในวง:
foreach ($array as $val) ( var_dump(current($array)); ) /* เอาท์พุต: 2 2 2 2 2 */
ที่นี่คุณควรจำไว้ว่าปัจจุบันยังเข้าถึงได้โดยการอ้างอิง แม้ว่าไม่ได้แก้ไขอาร์เรย์ก็ตาม สิ่งนี้จำเป็นในการทำงานอย่างสม่ำเสมอกับฟังก์ชันอื่นๆ ทั้งหมด เช่น ถัดไป ที่เข้าถึงโดยการอ้างอิง (จริงๆ แล้ว current เป็นฟังก์ชันอ้างอิงที่ดีกว่า โดยสามารถรับค่าได้ แต่ใช้การอ้างอิงหากทำได้) การอ้างอิงหมายความว่าอาร์เรย์จะต้องถูกแยกออกจากกัน ดังนั้น $array และสำเนาของ $array ที่ foreach ใช้จะเป็นอิสระจากกัน ทำไมคุณได้รับ 2 และไม่ใช่ 1 ยังได้กล่าวถึงข้างต้น: foreach ตัวชี้อาร์เรย์ที่เพิ่มขึ้น ก่อนที่จะเริ่มรหัสผู้ใช้ไม่ใช่หลังจากนั้น ดังนั้นแม้ว่าโค้ดจะยังคงทำงานในองค์ประกอบแรก แต่ foreach ก็ได้ย้ายตัวชี้ไปที่องค์ประกอบที่สองแล้ว
ตอนนี้เรามาลองเปลี่ยนแปลงเล็กน้อย:
$อ้างอิง = &$อาร์เรย์; foreach ($array as $val) ( var_dump(current($array)); ) /* เอาท์พุต: 2 3 4 5 false */
ที่นี่เรามี is_ref=1 ดังนั้นจึงไม่มีการคัดลอกอาร์เรย์ (เหมือนกับด้านบน) แต่ตอนนี้เมื่อมี is_ref แล้ว อาร์เรย์ก็ไม่จำเป็นต้องแยกอีกต่อไป โดยผ่านการอ้างอิงไปยังกระแส ตอนนี้ current และ foreach ทำงานกับอาเรย์เดียวกัน คุณเห็นว่าอาร์เรย์ถูกเลื่อนไปทีละอันอย่างแม่นยำเนื่องจากวิธีที่ foreach ปฏิบัติต่อตัวชี้
คุณจะเห็นสิ่งเดียวกันเมื่อคุณสำรวจอาร์เรย์โดยการอ้างอิง:
foreach ($array as &$val) ( var_dump(current($array)); ) /* เอาท์พุต: 2 3 4 5 false */
สิ่งสำคัญที่นี่คือ foreach จะกำหนด $array is_ref=1 ของเราเมื่อมันวนซ้ำโดยการอ้างอิง ดังนั้นมันจะจบลงเหมือนกับข้างบน
รูปแบบเล็กๆ อีกรูปแบบหนึ่ง เราจะกำหนดอาร์เรย์ให้กับตัวแปรอื่น:
$foo = $อาร์เรย์; foreach ($array as $val) ( var_dump(current($array)); ) /* เอาท์พุต: 1 1 1 1 1 */
ในที่นี้ การนับซ้ำของ $array จะถูกตั้งค่าเป็น 2 เมื่อลูปเริ่มต้น ดังนั้นจึงต้องทำสำเนาก่อนเริ่มต้น ดังนั้น $array และอาร์เรย์ที่ใช้โดย foreach จะแตกต่างจากจุดเริ่มต้น นั่นเป็นเหตุผลที่คุณได้รับตำแหน่งของตัวชี้อาร์เรย์ภายในที่เกี่ยวข้องก่อนที่ลูปจะเริ่มต้น (ในกรณีนี้คืออยู่ในตำแหน่งแรก)
การวนซ้ำของวัตถุ
เมื่อวนซ้ำวัตถุ ควรพิจารณาสองกรณี:
อ็อบเจ็กต์ไม่สามารถข้ามผ่านได้ (หรือค่อนข้างจะไม่ได้กำหนดตัวจัดการ get_iterator ภายใน)
ในกรณีนี้ การวนซ้ำจะเกิดขึ้นเกือบจะเหมือนกับกับอาร์เรย์ ความหมายเดียวกันของการคัดลอก ข้อแตกต่างเพียงอย่างเดียวคือ foreach จะเรียกใช้โค้ดพิเศษบางส่วนเพื่อข้ามคุณสมบัติที่ไม่สามารถใช้งานได้ในขอบเขตปัจจุบัน ข้อเท็จจริงที่น่าสนใจอีกสองสามข้อ:
- สำหรับคุณสมบัติที่ประกาศไว้ PHP จะเพิ่มประสิทธิภาพตารางแฮชของคุณสมบัติอีกครั้ง หากคุณวนซ้ำวัตถุ มันจะต้องสร้างตารางแฮชนั้นขึ้นมาใหม่ (ซึ่งจะเพิ่มการใช้หน่วยความจำ) ไม่ใช่ว่าคุณควรกังวลเกี่ยวกับเรื่องนี้ เพียงแค่ระวัง
- ในการวนซ้ำแต่ละครั้ง ตารางแฮชคุณสมบัติจะได้รับอีกครั้ง ซึ่งหมายความว่า PHP จะเรียก get_properties ซ้ำแล้วซ้ำอีก สำหรับคุณสมบัติ "ปกติ" สิ่งนี้ไม่สำคัญนัก แต่ถ้าคุณสมบัติถูกสร้างขึ้นแบบไดนามิก (ซึ่งมักจะทำโดยคลาสในตัว) ตารางคุณสมบัติจะถูกคำนวณใหม่ทุกครั้ง
วัตถุที่เคลื่อนที่ได้
ในกรณีนี้ ทุกอย่างที่กล่าวมาข้างต้นจะไม่มีผลใช้บังคับแต่อย่างใด นอกจากนี้ PHP จะไม่คัดลอกและจะไม่ใช้เทคนิคใดๆ เช่น การเพิ่มตัวชี้ก่อนการวนซ้ำ ฉันคิดว่าโหมดการส่งผ่านวัตถุ Traversable นั้นสามารถคาดเดาได้ง่ายกว่ามากและไม่ต้องการคำอธิบายเพิ่มเติม
แทนที่ iterable ระหว่างการวนซ้ำ
อีกกรณีที่ผิดปกติที่ฉันไม่ได้พูดถึงก็คือ PHP อนุญาตให้มีความสามารถในการแทนที่การวนซ้ำระหว่างการวนซ้ำ คุณสามารถเริ่มต้นด้วยอาร์เรย์หนึ่งและดำเนินการต่อโดยแทนที่ด้วยอีกอาร์เรย์หนึ่งครึ่งทาง หรือเริ่มต้นด้วยอาร์เรย์แล้วแทนที่ด้วยวัตถุ:
$arr = ; $obj = (วัตถุ) ; $อ้างอิง =& $arr; foreach ($อ้างอิงเป็น $val) ( echo "$val\n"; if ($val == 3) ( $ref = $obj; ) ) /* ผลลัพธ์: 1 2 3 6 7 8 9 10 */
อย่างที่คุณเห็น PHP เพิ่งเริ่มสำรวจเอนทิตีอื่นทันทีที่มีการแทนที่เกิดขึ้น
การเปลี่ยนตัวชี้อาร์เรย์ภายในระหว่างการวนซ้ำ
รายละเอียดสุดท้ายของพฤติกรรม foreach ที่ผมไม่ได้กล่าวถึง (เพราะสามารถนำมาใช้ได้ พฤติกรรมแปลกจริงๆ): จะเกิดอะไรขึ้นหากคุณพยายามเปลี่ยนตัวชี้อาร์เรย์ภายในระหว่างการวนซ้ำ
ที่นี่คุณอาจไม่ได้สิ่งที่คุณคาดหวัง: หากคุณเรียกใช้ next หรือ prev ในเนื้อความของลูป (ในกรณีผ่านการอ้างอิง) คุณจะเห็นว่าตัวชี้ภายในถูกย้าย แต่สิ่งนี้ไม่มีผลกระทบต่อพฤติกรรมของ ตัววนซ้ำ เหตุผลก็คือ foreach ทำการสำรองตำแหน่งปัจจุบันและแฮชขององค์ประกอบปัจจุบันใน HashPointer หลังจากผ่านลูปแต่ละครั้ง ในการผ่านครั้งต่อไป foreach จะตรวจสอบว่าตำแหน่งของตัวชี้ภายในมีการเปลี่ยนแปลงหรือไม่ และพยายามกู้คืนโดยใช้แฮชนี้
เรามาดูกันว่า "จะพยายาม" หมายถึงอะไร ตัวอย่างแรกแสดงให้เห็นว่าการเปลี่ยนตัวชี้ภายในไม่เปลี่ยนโหมด foreach:
$อาร์เรย์ = ; $อ้างอิง =& $อาร์เรย์; foreach ($array เป็น $value) ( var_dump($value); รีเซ็ต($array); ) // เอาต์พุต: 1, 2, 3, 4, 5
ตอนนี้เรามาลองยกเลิกการตั้งค่าองค์ประกอบที่แต่ละ foreach จะเข้าถึงในการผ่านครั้งแรก (คีย์ 1):
$อาร์เรย์ = ; $อ้างอิง =& $อาร์เรย์; foreach ($array เป็น $value) ( var_dump($value); unset($array); รีเซ็ต($array); ) // เอาต์พุต: 1, 1, 3, 4, 5
ที่นี่คุณจะเห็นว่าตัวนับถูกรีเซ็ตเนื่องจากไม่พบองค์ประกอบที่มีแฮชที่ตรงกัน
โปรดทราบว่าแฮชเป็นเพียงแฮช การชนกันเกิดขึ้น มาลองกันตอนนี้:
$array = ["EzEz" => 1, "EzFY" => 2, "FYEz" => 3]; $อ้างอิง =& $อาร์เรย์; foreach ($array เป็น $value) ( unset($array["EzFY"]); $array["FYFZ"] = 4; รีเซ็ต($array); var_dump($value); ) // เอาต์พุต: 1 1 3 4
ทำงานตามที่เราคาดหวัง เราได้ลบคีย์ EzFY (อันที่ foreach อยู่) ดังนั้นจึงทำการรีเซ็ต เรายังเพิ่มคีย์พิเศษอีกด้วย ดังนั้นเราจึงเห็น 4 ในตอนท้าย
และนี่คือสิ่งที่ไม่รู้จักมา จะเกิดอะไรขึ้นหากคุณแทนที่คีย์ FYFY ด้วย FYFZ มาลองกัน:
$array = ["EzEz" => 1, "EzFY" => 2, "FYEz" => 3]; $อ้างอิง =& $อาร์เรย์; foreach ($array เป็น $value) ( unset($array["EzFY"]); $array["FYFY"] = 4; รีเซ็ต($array); var_dump($value); ) // เอาต์พุต: 1 4
ตอนนี้ลูปได้ย้ายไปยังองค์ประกอบใหม่โดยตรง โดยข้ามสิ่งอื่นทั้งหมดไป เนื่องจากคีย์ FYFY ขัดแย้งกับ EzFY (จริงๆ แล้วคีย์ทั้งหมดจากอาร์เรย์นี้ด้วย) นอกจากนี้ องค์ประกอบ FYFY ยังอยู่ที่ที่อยู่หน่วยความจำเดียวกันกับองค์ประกอบ EzFY ที่เพิ่งถูกลบ ดังนั้นสำหรับ PHP มันจะอยู่ในตำแหน่งเดียวกันและมีแฮชเดียวกัน ตำแหน่งจะถูก "กู้คืน" และการเปลี่ยนไปยังจุดสิ้นสุดของอาร์เรย์จะเกิดขึ้น
บ่อยครั้งที่คุณจำเป็นต้องวนซ้ำองค์ประกอบทั้งหมดของอาร์เรย์ PHP และดำเนินการบางอย่างกับแต่ละองค์ประกอบ ตัวอย่างเช่น คุณสามารถส่งออกแต่ละค่าไปยังตาราง HTML หรือกำหนดค่าใหม่ให้กับแต่ละองค์ประกอบได้
ในบทนี้ เราจะดูโครงสร้าง foreach เมื่อจัดระเบียบลูปบนอาร์เรย์ที่จัดทำดัชนีและที่เกี่ยวข้อง
วนซ้ำค่าองค์ประกอบ
กรณีการใช้งานที่ง่ายที่สุดสำหรับ foreach คือการวนซ้ำค่าในอาร์เรย์ที่จัดทำดัชนี ไวยากรณ์พื้นฐาน:
Foreach ($array as $value) ( // ทำบางสิ่งด้วย $value ) // นี่คือโค้ดที่จะถูกดำเนินการหลังจากการวนซ้ำเสร็จสิ้น
ตัวอย่างเช่น สคริปต์ต่อไปนี้วนซ้ำรายชื่อกรรมการในอาร์เรย์ที่จัดทำดัชนีแล้วพิมพ์ชื่อของแต่ละรายการ:
$directors = array("อัลเฟรด ฮิตช์ค็อก", "สแตนลีย์ คูบริก", "มาร์ติน สกอร์เซซี", "ฟริตซ์ แลง"); foreach ($ผู้กำกับเป็น $director) ( echo $director ”
";
}
รหัสข้างต้นจะส่งออก:
อัลเฟรด ฮิตช์ค็อก สแตนลีย์ คูบริก มาร์ติน สกอร์เซซี่ ฟริตซ์ แลง
ห่วงคีย์-ค่า
แล้วอาร์เรย์ที่เกี่ยวข้องล่ะ? เมื่อใช้อาร์เรย์ประเภทนี้ คุณมักจะต้องมีสิทธิ์เข้าถึงคีย์ของแต่ละองค์ประกอบตลอดจนค่าของมัน โครงสร้าง foreach มีวิธีการแก้ปัญหา:
Foreach ($array as $key => $value) ( // ทำบางอย่างด้วย $key และ/หรือ $value ) // นี่คือโค้ดที่จะถูกดำเนินการหลังจากการวนซ้ำเสร็จสิ้น
ตัวอย่างของการจัดระเบียบลูปผ่านอาร์เรย์ที่เกี่ยวข้องกับข้อมูลเกี่ยวกับภาพยนตร์ โดยการแสดงคีย์ของแต่ละองค์ประกอบและค่าของมันในรายการคำจำกัดความ HTML:
$movie = array("title" => "หน้าต่างด้านหลัง", "ผู้กำกับ" => "Alfred Hitchcock", "ปี" => 1954, "นาที" => 112); เสียงสะท้อน "
- "; foreach ($movie as $key => $value) ( echo "
- $คีย์: "; เอคโค่"
- $value "; ) เสียงสะท้อน"
เมื่อดำเนินการแล้ว สคริปต์นี้จะส่งออก:
หัวข้อ: หน้าต่างด้านหลัง ผู้กำกับ: Alfred Hitchcock ปี: 1954 นาที: 112
การเปลี่ยนค่าขององค์ประกอบ
แล้วการเปลี่ยนค่าขององค์ประกอบในขณะที่ลูปผ่านไปล่ะ? คุณสามารถลองใช้รหัสนี้:
Foreach ($myArray เป็น $value) ( $value = 123; )
อย่างไรก็ตามหากรันแล้วจะพบว่าค่าในอาร์เรย์ อย่าเปลี่ยน- เหตุผลก็คือ foreach ใช้งานได้ สำเนาค่าอาร์เรย์ไม่ใช่ค่าดั้งเดิม วิธีนี้ทำให้อาเรย์ดั้งเดิมยังคงไม่บุบสลาย
หากต้องการเปลี่ยนค่าอาร์เรย์ที่คุณต้องการ ลิงค์ถึงความหมาย ในการดำเนินการนี้ คุณจะต้องใส่เครื่องหมาย & หน้าตัวแปรค่าในโครงสร้าง foreach:
Foreach ($myArray เป็น &$value) ( $value = 123; )
ตัวอย่างเช่น สคริปต์ต่อไปนี้วนซ้ำแต่ละองค์ประกอบ (ชื่อผู้กำกับ) ในอาร์เรย์ $directors และใช้ฟังก์ชัน PHP explode() และโครงสร้างรายการเพื่อกลับชื่อและนามสกุล:
$directors = array("อัลเฟรด ฮิตช์ค็อก", "สแตนลีย์ คูบริก", "มาร์ติน สกอร์เซซี", "ฟริตซ์ แลง"); // เปลี่ยนรูปแบบชื่อสำหรับแต่ละองค์ประกอบ foreach ($directors as &$director) ( list($firstName, $lastName) = explode(" ", $director); $director = "$lastName, $firstName"; ) unset ($ผู้อำนวยการ); // พิมพ์ผลลัพธ์สุดท้าย foreach ($directors as $director) ( echo $director . "
";
}
สคริปต์จะส่งออก:
ฮิตช์ค็อก, อัลเฟรด คูบริก, สแตนลีย์ สกอร์เซซี, มาร์ติน แลง, ฟริตซ์
โปรดทราบว่าสคริปต์เรียกใช้ฟังก์ชัน unset() เพื่อลบตัวแปร $director หลังจากที่ลูปแรกเสร็จสิ้น นี่เป็นแนวปฏิบัติที่ดีหากคุณวางแผนที่จะใช้ตัวแปรในภายหลังในสคริปต์ในบริบทอื่น
หากคุณไม่ลบการอ้างอิง คุณจะเสี่ยงต่อการรันโค้ดเพิ่มเติมโดยไม่ได้ตั้งใจอ้างอิงองค์ประกอบสุดท้ายในอาเรย์ ("Lang, Fritz") หากคุณยังคงใช้ตัวแปร $director ต่อไป ซึ่งจะนำไปสู่ผลลัพธ์ที่ไม่ได้ตั้งใจ!
ประวัติย่อ
ในบทช่วยสอนนี้ เราได้ดูวิธีใช้โครงสร้าง foreach ของ PHP เพื่อวนซ้ำองค์ประกอบของอาร์เรย์ มีการพิจารณาประเด็นต่อไปนี้:
- วิธีการวนซ้ำองค์ประกอบอาร์เรย์
- วิธีเข้าถึงคีย์และค่าของแต่ละองค์ประกอบ
- วิธีใช้การอ้างอิงเพื่อเปลี่ยนค่าเมื่อคุณผ่านการวนซ้ำ
ลองนึกภาพคุณมีอาเรย์เชื่อมโยงที่คุณต้องการวนซ้ำ PHP จัดเตรียมวิธีง่ายๆ ในการใช้แต่ละองค์ประกอบของอาร์เรย์สลับกันโดยใช้โครงสร้าง Foreach
ในภาษาธรรมดาจะมีลักษณะดังนี้:
"สำหรับแต่ละองค์ประกอบในอาร์เรย์ที่ระบุ ให้รันโค้ดนี้"
แม้ว่ามันจะดำเนินต่อไปตราบใดที่ตรงตามเงื่อนไขบางประการ แต่ foreach loop จะยังคงดำเนินต่อไปจนกว่าจะผ่านทุกองค์ประกอบของอาเรย์
PHP Foreach: ตัวอย่าง
เรามีอาร์เรย์ที่เชื่อมโยงซึ่งจัดเก็บชื่อของบุคคลในบริษัทของเราตลอดจนอายุของพวกเขา เราต้องการทราบว่าพนักงานแต่ละคนอายุเท่าไร ดังนั้นเราจึงใช้ for-each loop เพื่อพิมพ์ชื่อและอายุของทุกคน
$employeeAges; $employeeAges["ลิซ่า"] = "28"; $employeeAges["แจ็ค"] = "16"; $employeeAges["ไรอัน"] = "35"; $employeeAges["ราเชล"] = "46"; $employeeAges["เกรซ"] = "34"; foreach($employeeAges as $key => $value)( echo "ชื่อ: $key, อายุ: $value
";
}
เราได้รับผลลัพธ์:
ชื่อ: ลิซ่า อายุ: 28 ชื่อ: แจ็ค อายุ: 16 ชื่อ: ไรอัน อายุ: 35 ชื่อ: ราเชล อายุ: 46 ชื่อ: เกรซ อายุ: 34
ผลลัพธ์เป็นสิ่งที่ดีและเข้าใจได้ แต่ไวยากรณ์ของโครงสร้าง foreach นั้นไม่ง่ายและเข้าใจได้มากนัก เรามาดูกันดีกว่า
สำหรับแต่ละไวยากรณ์: $something as $key => $value
ความบ้าคลั่งทั้งหมดนี้แปลคร่าวๆ ได้เป็น: “สำหรับแต่ละองค์ประกอบของอาร์เรย์ที่เชื่อมโยง $employeeAges ฉันต้องการเข้าถึง $key และค่าภายในนั้น นั่นคือ $value
ตัวดำเนินการ "=>" แสดงถึงความสัมพันธ์ระหว่างคีย์และค่า ในตัวอย่างของเรา เราตั้งชื่อให้เป็นคีย์ - $key และ value - $value อย่างไรก็ตาม มันจะง่ายกว่าถ้าคิดว่าเป็นชื่อและอายุ ด้านล่างนี้ในตัวอย่างของเรา เราจะทำเช่นนี้ และโปรดทราบว่าผลลัพธ์จะเหมือนกันเนื่องจากเราเปลี่ยนชื่อของตัวแปรที่เกี่ยวข้องกับคีย์และค่าเท่านั้น
$employeeAges; $employeeAges["ลิซ่า"] = "28"; $employeeAges["แจ็ค"] = "16"; $employeeAges["ไรอัน"] = "35"; $employeeAges["ราเชล"] = "46"; $employeeAges["เกรซ"] = "34"; foreach($employeeAges as $name => $age)( echo "ชื่อ: $name, อายุ: $age
";
}
ผลลัพธ์ที่เราทำซ้ำก็เหมือนเดิม