วิธีการทำงานของ foreach loop ทำ while และ foreach ลูป ห่วงคีย์-ค่า

โครงสร้าง 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
"; }

ผลลัพธ์ที่เราทำซ้ำก็เหมือนเดิม