Part of 10306103 หลักการเขียนโปรแกรมเบื้องต้น (Fundamentals of Programming)
บทที่ 5 โครงสร้างควบคุมแบบวนซ้ำ
แผนบริหารการสอนประจำบทที่ 5
บทนี้ว่าด้วยโครงสร้างควบคุมแบบวนซ้ำของภาษา C ต่อยอดจากโครงสร้างควบคุมแบบทางเลือก (if/else) และตัวดำเนินการเปรียบเทียบ/ตรรกะในบทที่ 4 โดยอธิบายลูปสามชนิดคือ for, while และ do-while การเลือกชนิดลูปให้เหมาะกับโจทย์ การใช้ตัวแปรนับและตัวแปรสะสม และการควบคุมการวนซ้ำด้วย break และ continue เพื่อให้เขียนโปรแกรมที่ทำงานซ้ำตามเงื่อนไขได้อย่างถูกต้องและอ่านง่าย
| ผลลัพธ์ | ข้อความ | หลักฐาน | CLO |
|---|---|---|---|
| LLO5.1 | อธิบายและใช้ลูป for, while และ do-while ในการทำงานซ้ำ | งานข้อ 1 (ปฏิบัติการที่ 5 Lab A) | CLO1 |
| LLO5.2 | เลือกชนิดลูปให้เหมาะกับโจทย์ (จำนวนรอบที่รู้ล่วงหน้า เทียบกับเงื่อนไข) และใช้ตัวแปรนับ/สะสม | งานข้อ 2 (ปฏิบัติการที่ 5 Lab A) | CLO2 |
| LLO5.3 | ใช้ break และ continue ควบคุมการวนซ้ำได้ถูกต้อง | งานข้อ 3 (ปฏิบัติการที่ 5 Lab B) | CLO2 |
| LLO5.4 | ปฏิบัติตามมาตรฐานการเขียนโค้ด (การจัดย่อหน้า การตั้งชื่อสื่อความหมาย การใส่คอมเมนต์) ในงานโค้ดของบทนี้ | โค้ดทุกข้อที่ส่งในบทนี้ | CLO5 |
CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):
- CLO1 อธิบายและใช้ตัวแปร ชนิดข้อมูล การรับ-แสดงผล และโครงสร้างควบคุม (if/else, switch, loop) เพื่อแก้ปัญหาและได้ผลลัพธ์ที่ถูกต้อง
- CLO2 อธิบายขั้นตอนการทำงาน (Logic) ของภาษาโปรแกรมและเลือกใช้โครงสร้างควบคุม (Control Structure) ให้สอดคล้องกับโจทย์ที่กำหนดได้
บทที่ 5 เป็นบทที่ทำให้โปรแกรมทำงานซ้ำได้โดยไม่ต้องเขียนคำสั่งเดิมหลายครั้ง จึงเป็นสะพานเชื่อมความรู้เรื่องการเลือกทำงานในบทที่ 4 ไปสู่การออกแบบฟังก์ชันในบทที่ 6 และการประมวลผลอาร์เรย์ในบทที่ 7 งานทั้งสามข้อในบทนี้สะสมหลักฐานของ CLO1 และ CLO2 ต่อเนื่องจากบทก่อน งานของบทนี้อยู่ในหมวดงานท้ายบทตามแผนคะแนนที่ผู้สอนยืนยันไว้ ดูรายละเอียดในบันทึกสำหรับผู้สอนท้ายบท
วิธีสอน
- ทบทวนการเลือกทำงานด้วย
ifและตัวดำเนินการเปรียบเทียบจากบทที่ 4 แล้วตั้งโจทย์ที่ต้องทำงานซ้ำหลายรอบเพื่อชี้ให้เห็นความจำเป็นของลูป - แนะนำลูป
forด้วยตัวแปรนับ โดยสาธิตส่วนประกอบสามส่วน (ค่าเริ่ม เงื่อนไข ปรับค่า) บนเครื่องจริง - เปรียบเทียบ
whileที่ตรวจเงื่อนไขก่อนทำ กับdo-whileที่ทำก่อนอย่างน้อยหนึ่งรอบ พร้อมชี้กับดักลูปไม่รู้จบ - สาธิตรูปแบบตัวแปรสะสมและตัวแปรนับ แล้วให้ลงมือปฏิบัติตามใบงาน โดยกำหนดจุดตรวจระหว่างทางเพื่อให้ข้อมูลป้อนกลับทันที
- สรุปการใช้
breakและcontinueตามเจตนา และย้ำข้อผิดพลาดที่พบบ่อย เช่น การตั้งค่าเริ่มต้นตัวสะสมผิดและการลืมปรับตัวนับ
สื่อการเรียนการสอน
- เครื่องคอมพิวเตอร์ที่เข้าถึงแพลตฟอร์มการเรียนของรายวิชาสำหรับเขียน คอมไพล์ และรันโปรแกรมภาษา C (มาตรฐาน C11) บนเว็บ
- เอกสารประกอบบทที่ 5 และสไลด์สรุปลูปสามชนิดและการใช้ break/continue
- ไฟล์โปรแกรมตัวอย่าง
ch5_sum.cและch5_loops.c
การวัดผลและการประเมินผล
กำหนดส่ง: งานข้อ 1–3 ภายใน 23:59 น. ของวันก่อนวันปฏิบัติการในสัปดาห์ที่ 6 ส่วนชุดฝึกท้ายบทและแบบฝึกหัดวัดความเข้าใจของบทนี้ ภายใน 23:59 น. ของวันก่อนวันบรรยายในสัปดาห์ที่ 6 ดูวันที่จริงของตอนเรียนได้ในห้องเรียนออนไลน์ (มติผู้สอน 28 ต.ค. 2569)
| ข้อ | งาน | หลักฐาน | ผลลัพธ์ | CLO |
|---|---|---|---|---|
| 1 | เขียนลูปคำนวณผลรวม 1..n ด้วย for และแฟกทอเรียลด้วย while | ไฟล์ซอร์สโค้ด + ผลการรัน | LLO5.1 | CLO1 |
| 2 | เลือกชนิดลูปให้เหมาะกับโจทย์ที่กำหนด พร้อมใช้ตัวแปรสะสม/ตัวนับ | ไฟล์ซอร์สโค้ด + ผลการรัน | LLO5.2 | CLO2 |
| 3 | ใช้ continue ข้ามกรณี และ break หยุดลูปตามเงื่อนไข | ไฟล์ซอร์สโค้ด + ผลการรัน | LLO5.3 | CLO2 |
เงื่อนไขผ่าน: งานข้อ 2 ประเมินจากไฟล์เดียวกับงานข้อ 1 ที่ต่อยอดการเลือกชนิดลูปและออกแบบตัวแปรสะสม ดังนั้นงานข้อ 1 ต้องให้ผลรวมและแฟกทอเรียลถูกต้องและคอมไพล์ผ่านก่อนจึงจะประเมินข้อ 2 ได้
คะแนนใบงาน (10 คะแนน)
| ผลลัพธ์ | สิ่งที่ตรวจ | คะแนน |
|---|---|---|
| LLO5.1 | งานข้อที่ระบุผลลัพธ์นี้ | 3 |
| LLO5.2 | งานข้อที่ระบุผลลัพธ์นี้ | 3 |
| LLO5.3 | งานข้อที่ระบุผลลัพธ์นี้ | 3 |
| LLO5.4 | มาตรฐานโค้ด ตรวจจากโค้ดทุกข้อที่ส่ง | 1 |
ใบงานบทที่ 1–13 รวมกันคิดเป็น 15% ของคะแนนรายวิชา ชิ้นละเท่ากัน ส่งช้าหัก 10% ของคะแนนงานต่อสัปดาห์ที่ช้า สูงสุด 50% และรับงานถึงสิ้นสัปดาห์ที่ 15 งานที่ผู้สอนส่งกลับให้แก้นับเวลาจากการส่งครั้งแรก ดูคะแนนของตนเองได้ที่หน้า “คะแนนของฉัน” ในห้องเรียนออนไลน์ของรายวิชา
เกณฑ์ให้คะแนน
งานข้อ 1 และงานข้อ 3 ใช้เกณฑ์ผ่าน/ไม่ผ่าน:
| เกณฑ์ | ผ่าน | ยังไม่ผ่าน |
|---|---|---|
| งานข้อ 1 | ผลรวมและแฟกทอเรียลถูกต้องทุกค่าทดสอบ และไม่มีลูปไม่รู้จบ | ผลไม่ตรงบางค่า หรือเกิดลูปไม่รู้จบ |
| งานข้อ 3 | continue/break ทำงานตามเจตนา ผลลัพธ์ตรง และคอมไพล์ผ่านไม่มีการแจ้งเตือน | ผลไม่ตรง หรือใช้ break/continue ผิดเจตนา |
งานข้อ 2 ใช้รายการตรวจ (checklist): เลือกชนิดลูปเหมาะกับลักษณะโจทย์, ตั้งค่าเริ่มต้นตัวสะสมถูก (0 สำหรับบวก, 1 สำหรับคูณ), ปรับตัวนับครบทุกรอบ, จัดย่อหน้า 4 ช่องว่างและมีคอมเมนต์ส่วนหัว
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
บทนำ
โปรแกรมจำนวนมากต้องทำงานเดิมซ้ำหลายครั้ง เช่น การรวมตัวเลขหลายจำนวนหรือการตรวจเงื่อนไขซ้ำจนกว่าจะพบคำตอบ การเขียนคำสั่งเดิมซ้ำ ๆ ด้วยมือย่อมยืดยาวและเกิดข้อผิดพลาดง่าย โครงสร้างควบคุมแบบวนซ้ำ (loop) จึงเข้ามาแก้ปัญหานี้ บทที่ 4 ได้แนะนำการเลือกทำงานด้วย if และตัวดำเนินการเปรียบเทียบไปแล้ว บทนี้นำเงื่อนไขแบบเดียวกันมาใช้ควบคุมการทำงานซ้ำด้วยลูป for, while และ do-while พร้อมการใช้ตัวแปรนับและตัวแปรสะสม และการควบคุมการวนซ้ำด้วย break และ continue
ทักษะการวนซ้ำในบทนี้เป็นเครื่องมือพื้นฐานของทุกบทที่ตามมา การวนซ้ำจะถูกใช้ร่วมกับฟังก์ชันในบทที่ 6 และใช้เป็นหัวใจของการประมวลผลอาร์เรย์ในบทที่ 7 ส่วนการออกแบบตัวแปรสะสมและการเลือกชนิดลูปช่วยให้แก้โจทย์ที่ซับซ้อนขึ้นได้อย่างเป็นระบบตลอดรายวิชา
| สิ่งที่ได้จากบทนี้ | ใช้ต่อในบท |
|---|---|
| ลูป for, while และ do-while | บท 6 (ฟังก์ชัน), บท 7 (อาร์เรย์) เป็นต้นไป |
| การเลือกชนิดลูปและตัวแปรนับ/สะสม | ทุกบทที่มีการประมวลผลซ้ำ |
| การควบคุมการวนซ้ำด้วย break และ continue | บท 7 (วนอาร์เรย์) และบทที่ต้องหยุด/ข้ามรอบ |
ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์
บทนี้ต้องใช้คอมไพเลอร์ภาษา C และโปรแกรมแก้ไขข้อความหรือ IDE และต้องมีความรู้เรื่องการเลือกทำงานด้วย if กับตัวดำเนินการเปรียบเทียบจากบทที่ 4 การรับข้อมูลด้วย scanf จากบทที่ 3 และตัวแปรกับตัวดำเนินการจากบทที่ 2 ตารางด้านล่างเป็นรุ่นเครื่องมือที่ใช้ทดสอบโค้ดตัวอย่างในบทนี้จริง
| เครื่องมือ | รุ่นที่ทดสอบ | หน้าที่ |
|---|---|---|
| คอมไพเลอร์ gcc | 13.3.0 (Ubuntu) | แปลซอร์สโค้ด .c เป็นไฟล์ปฏิบัติการ |
| มาตรฐานภาษา | C11 (-std=c11) | กำหนดไวยากรณ์ภาษา C ที่ใช้อ้างอิง |
โค้ดตัวอย่างในบทนี้ทดสอบด้วย gcc 13.3.0 บนระบบปฏิบัติการลินุกซ์ โดยการรับข้อมูลทดสอบด้วยการป้อนค่าผ่านทางเข้ามาตรฐาน (standard input) หากใช้คอมไพเลอร์หรือระบบปฏิบัติการอื่น ผลการแสดงข้อความภาษาไทยอาจต่างกันเล็กน้อย แต่ซอร์สโค้ดภาษา C มาตรฐานใช้ได้เหมือนกัน
วิธีใช้บทเรียนและใบงานในหน้านี้
ทบทวน บทที่ 4 โครงสร้างควบคุมแบบทางเลือก โดยอธิบายให้ได้ว่าเงื่อนไขเป็นศูนย์หรือไม่เป็นศูนย์ส่งผลอย่างไร จากนั้นเปลี่ยนคำถามจาก “เลือกทำอะไร” เป็น “ทำอีกครั้งหรือหยุดแล้ว”
ใบงานในหน้านี้เป็นตารางสำหรับคัดลงสมุดหรือเอกสารของตนเอง ไม่ใช่ช่องส่งคำตอบบนเว็บไซต์ ทำตามลำดับ ทำนายผล → ไล่สถานะ → คอมไพล์ → รัน → อธิบายความต่าง โดยเก็บผลที่ทำนายไว้แม้ทายผิด
คะแนนและเกณฑ์ผ่าน 50 ในกรณีศึกษาเป็นข้อมูลสมมติสำหรับฝึกโปรแกรม ไม่ใช่คะแนนนักศึกษาหรือเกณฑ์ประเมินรายวิชา ชุดสอนยังใช้ LLO5.1–5.3 และงานส่งเดิม ส่วนเวลาและแนวคำตอบอยู่ในส่วนผู้สอน
สำหรับการรับจำนวนเต็ม ใช้ค่าที่แทนด้วย int ได้และตรวจจำนวนข้อมูลที่ scanf อ่านสำเร็จ การตรวจผลอ่านไม่ใช่ตัวแปลงเลขทุกขนาด และ 12x อาจอ่านสำเร็จเป็น 12 โดยเหลือ x ไว้ จึงยังไม่ใช่การตรวจทั้งบรรทัด
5.1 แนวคิดการวนซ้ำและลูป for
ระดับที่ 1: การวนซ้ำ (loop) คือการสั่งให้โปรแกรมทำกลุ่มคำสั่งเดิมซ้ำหลายรอบโดยอัตโนมัติ แทนการเขียนคำสั่งเดิมซ้ำด้วยมือ ลูปจะทำงานต่อไปตราบที่เงื่อนไขยังเป็นจริง และหยุดเมื่อเงื่อนไขเป็นเท็จ
ระดับที่ 2: ลูป for เหมาะเมื่อรู้จำนวนรอบล่วงหน้า โครงสร้างประกอบด้วยสามส่วนในวงเล็บ คั่นด้วยเครื่องหมายอัฒภาค ; คือ ค่าเริ่มต้น เงื่อนไขทำซ้ำ และการปรับค่าต่อรอบ ดังรูปแบบ for (ค่าเริ่ม; เงื่อนไข; ปรับค่า) ตัวอย่างการนับจาก 1 ถึง 5
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}ตัวแปร i เรียกว่าตัวแปรนับ (counter) เริ่มที่ 1 ตรวจเงื่อนไข i <= 5 ก่อนทำงานแต่ละรอบ และเพิ่มค่าทีละหนึ่งด้วย i++ จากบทที่ 2 จนเงื่อนไขเป็นเท็จจึงออกจากลูป
ระดับที่ 3: กับดักที่พบบ่อยของลูป for คือความคลาดเคลื่อนที่ขอบเขต (off-by-one) เช่น การใช้ i < 5 แทน i <= 5 ทำให้วนน้อยไปหนึ่งรอบ หรือเริ่มตัวนับผิดค่า การตรวจค่าตัวนับในรอบแรกและรอบสุดท้ายทุกครั้งช่วยลดข้อผิดพลาดนี้ได้ นอกจากนี้ตัวแปรนับที่ประกาศภายใน for จะใช้ได้เฉพาะภายในลูปเท่านั้น
คำถามตรวจความเข้าใจ 5.1: ลูป for มีส่วนประกอบสามส่วนอะไรบ้าง และส่วนใดทำหน้าที่หยุดการวนซ้ำ
ใบงาน 1: นับรอบทำงานให้ต่างจากรอบตรวจเงื่อนไข
จากตัวอย่าง for ที่แสดง 1–5 ให้คัดตารางนี้แล้วกรอกก่อนรัน บรรทัดสุดท้ายเป็นการตรวจเงื่อนไขที่ไม่เข้าสู่ตัวลูป จึงไม่ควรนับเป็นรอบที่พิมพ์ข้อมูล
| ครั้งที่ตรวจ | i ก่อนตรวจ | i <= 5 จริงหรือเท็จ | พิมพ์อะไร | i หลังปรับค่า |
|---|---|---|---|---|
| 1 | 1 | |||
| 2 | 2 | |||
| 3 | 3 | |||
| 4 | 4 | |||
| 5 | 5 | |||
| 6 | 6 |
ทำอีกครั้งโดยเปลี่ยนเป็น i < 5 และเปลี่ยนจุดเริ่มเป็น 0 ทีละอย่าง บันทึกค่าที่พิมพ์ตัวแรก ตัวสุดท้าย และจำนวนครั้ง แยกคำอธิบายว่าเปลี่ยน “ชุดค่าที่ผ่านเงื่อนไข” หรือเปลี่ยน “จุดเริ่ม”
สำหรับผลรวม 1..n ให้ทำตารางเพิ่มคอลัมน์ sum ก่อนบวกและหลังบวก เมื่อ n เป็น 1, 3 และ 5 อย่าวาง sum = 0 ภายในลูป เพราะต้องเก็บผลจากรอบก่อนหน้าไว้
5.2 ลูป while และ do-while
ระดับที่ 1: ลูป while และ do-while ใช้วนซ้ำตามเงื่อนไขเช่นเดียวกับ for แต่เหมาะกับกรณีที่ยังไม่รู้จำนวนรอบแน่นอน และขึ้นกับเงื่อนไขที่ตรวจระหว่างทาง
ระดับที่ 2: ลูป while (เงื่อนไข) ตรวจเงื่อนไขก่อนทำงานทุกรอบ หากเงื่อนไขเป็นเท็จตั้งแต่แรกจะไม่ทำงานเลยแม้แต่รอบเดียว ส่วนลูป do { } while (เงื่อนไข); ทำงานก่อนอย่างน้อยหนึ่งรอบแล้วจึงตรวจเงื่อนไขท้ายรอบ ความต่างสรุปได้ดังตาราง
| ชนิดลูป | ตรวจเงื่อนไข | จำนวนรอบอย่างน้อย | เหมาะกับ |
|---|---|---|---|
while | ก่อนทำงานแต่ละรอบ | 0 รอบ | งานที่อาจไม่ต้องทำเลยถ้าเงื่อนไขเป็นเท็จแต่แรก |
do-while | หลังทำงานแต่ละรอบ | 1 รอบ | งานที่ต้องทำอย่างน้อยหนึ่งครั้งก่อนตรวจ เช่น เมนู |
ตัวอย่างการนับถอยด้วย while
int k = 3;
while (k > 0) {
printf("%d ", k);
k--;
}ระดับที่ 3: ความเสี่ยงสำคัญของ while และ do-while คือลูปไม่รู้จบ (infinite loop) ซึ่งเกิดเมื่อเงื่อนไขไม่มีวันเป็นเท็จ หรือลืมปรับค่าตัวนับภายในลูป เช่น ลืมคำสั่ง k-- ในตัวอย่างข้างต้น ทุกลูปที่อาศัยตัวนับจึงต้องมีคำสั่งปรับค่าที่ทำให้เงื่อนไขเข้าใกล้ความเป็นเท็จในที่สุด
คำถามตรวจความเข้าใจ 5.2: ลูป while กับ do-while ต่างกันอย่างไรเมื่อเงื่อนไขเป็นเท็จตั้งแต่แรก
ใบงาน 2: เงื่อนไขเริ่มต้นเท็จและสิ่งที่ยังต้องทำหนึ่งครั้ง
สร้างไฟล์ loop_counts.c เพื่อเปรียบเทียบจำนวนครั้งที่เข้าตัวลูปด้วยค่าขีดจำกัดเดียวกัน ช่วง 0–3 ใช้เพื่อให้ trace ได้ครบ ไม่ใช่ข้อจำกัดของชนิดลูป
/* loop_counts.c : compare pre-test and post-test loops */
#include <stdio.h>
int main(void)
{
int limit;
if (scanf("%d", &limit) != 1) {
puts("Input error");
return 1;
}
if (limit < 0 || limit > 3) {
puts("Range error");
return 1;
}
int i = 0;
int for_count = 0;
for (i = 0; i < limit; ++i) {
++for_count;
}
i = 0;
int while_count = 0;
while (i < limit) {
++while_count;
++i;
}
i = 0;
int do_count = 0;
do {
++do_count;
++i;
} while (i < limit);
printf("for=%d while=%d do=%d\n",
for_count, while_count, do_count);
return 0;
}| limit | คาดว่า for ทำกี่ครั้ง | คาดว่า while ทำกี่ครั้ง | คาดว่า do-while ทำกี่ครั้ง | ผลจริงตรงหรือไม่ |
|---|---|---|---|---|
| 0 | ||||
| 1 | ||||
| 3 |
รันทีละค่าและชี้ตำแหน่งตรวจเงื่อนไขบนซอร์สโค้ด หากนับเฉพาะจำนวนบรรทัดที่แสดงผลจะตอบไม่ได้ เพราะทั้งสามลูปแสดงผลสรุปร่วมกันเพียงบรรทัดเดียว
กรณีศึกษา: เมนูที่ต้องแสดงก่อนเลือกออก
สร้างไฟล์ repeat_menu.c เมนูมีเพียง 1 เพื่อแสดงข้อความตัวอย่าง และ 0 เพื่อออก หมายเลขอื่นแสดงข้อผิดพลาดแล้วให้เลือกใหม่ ข้อมูลไม่ใช่จำนวนเต็มหรือจบข้อมูลก่อนเลือกออกให้รายงานแล้วจบ
/* repeat_menu.c : show a menu at least once */
#include <stdio.h>
int main(void)
{
int choice;
do {
puts("1: help, 0: exit");
if (scanf("%d", &choice) != 1) {
puts("Input error");
return 1;
}
switch (choice) {
case 1:
puts("Practice one step at a time");
break;
case 0:
puts("Bye");
break;
default:
puts("Invalid choice");
break;
}
} while (choice != 0);
return 0;
}ทำนายข้อความและจำนวนครั้งที่แสดงเมนูเมื่อป้อน 0, 1 0 และ 9 1 0 แล้วลอง x สังเกตว่า break ใน switch จบการเลือกเมนู ส่วนเงื่อนไขท้าย do-while เป็นผู้ตัดสินว่าจะวนอีกครั้งหรือไม่
หากใส่ break หลัง switch แต่ยังอยู่ในตัวลูป ผลจะต่างกันอย่างไร ให้วาดลูกศรเส้นทางก่อนแก้โค้ด ความหมายของ break ขึ้นกับโครงสร้างที่ครอบอยู่ใกล้ที่สุด ไม่ใช่ชื่อเมนูที่กำลังทำ
5.3 การเลือกชนิดลูปและตัวแปรนับ/สะสม
ระดับที่ 1: การเลือกชนิดลูปพิจารณาจากลักษณะโจทย์ หากรู้จำนวนรอบล่วงหน้าให้ใช้ for หากวนตามเงื่อนไขที่ยังไม่รู้จำนวนรอบให้ใช้ while และหากต้องทำอย่างน้อยหนึ่งรอบก่อนตรวจให้ใช้ do-while
ระดับที่ 2: ภายในลูปมักใช้ตัวแปรสองแบบ คือ ตัวแปรสะสม (accumulator) ที่เก็บผลสะสมทีละรอบ และตัวแปรนับ (counter) ที่นับจำนวนครั้ง รูปแบบที่ใช้บ่อยคือการบวกสะสมด้วย sum += i; และการนับด้วย count++; ซึ่งอาศัยตัวดำเนินการจากบทที่ 2 ตัวอย่างการรวม 1 ถึง n ด้วย for
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}ระดับที่ 3: การตั้งค่าเริ่มต้นของตัวแปรสะสมต้องถูกต้องตามการดำเนินการ สำหรับการบวกสะสมให้เริ่มที่ 0 เพราะ 0 เป็นค่าที่ไม่เปลี่ยนผลบวก แต่สำหรับการคูณสะสมต้องเริ่มที่ 1 เพราะการเริ่มที่ 0 จะทำให้ผลคูณเป็น 0 เสมอ การเริ่มค่าตัวสะสมผิดเป็นข้อผิดพลาดที่พบบ่อยเมื่อคำนวณแฟกทอเรียล
คำถามตรวจความเข้าใจ 5.3: โจทย์ "รวมเลข 1 ถึง n" ควรใช้ลูปชนิดใด เพราะเหตุใด และควรตั้งค่าเริ่มต้นของตัวแปรสะสมเป็นเท่าใด
5.4 break และ continue
ระดับที่ 1: คำสั่ง break และ continue ใช้เปลี่ยนการไหลของการวนซ้ำภายในลูป โดย break ใช้ออกจากลูป ส่วน continue ใช้ข้ามไปเริ่มรอบถัดไป
ระดับที่ 2: เมื่อโปรแกรมพบคำสั่ง break ในลูป จะออกจากลูปที่ครอบอยู่ใกล้ที่สุดทันทีและไปทำคำสั่งถัดจากลูปนั้น เหมาะกับการหยุดเมื่อพบคำตอบแล้ว ส่วนคำสั่ง continue จะข้ามคำสั่งที่เหลือในรอบนั้นสำหรับ for จะทำส่วนปรับค่าก่อนตรวจเงื่อนไข ส่วน while และ do-while จะไปตรวจเงื่อนไขของตน เหมาะกับการข้ามบางกรณีที่ไม่ต้องประมวลผล ทั้งสองคำสั่งมักใช้ร่วมกับ if จากบทที่ 4 ดังตัวอย่างการข้ามเลขคี่
for (int i = 1; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
printf("%d ", i);
}ระดับที่ 3: break และ continue ช่วยให้โค้ดกระชับขึ้น แต่ควรใช้เท่าที่จำเป็นเพื่อคงความอ่านง่าย การใช้ continue ในลูป while ต้องระวังเป็นพิเศษ เพราะหากข้ามไปก่อนถึงคำสั่งปรับตัวนับ ตัวนับจะไม่ถูกปรับและเกิดลูปไม่รู้จบ การวางคำสั่งปรับตัวนับให้ทำงานทุกรอบจึงสำคัญเมื่อใช้ continue
คำถามตรวจความเข้าใจ 5.4: break กับ continue ต่างกันอย่างไร และเหตุใดการใช้ continue ในลูป while จึงต้องระวังเรื่องตัวนับ
ใบงาน 3: ตามเส้นทาง continue ไปถึงจุดปรับค่า
ใช้ตัวอย่างข้ามเลขคี่ในหัวข้อ 5.4 แล้วกรอกแถว i เท่ากับ 1, 2, 3 และ 4 โดยแยก “เข้าตัวลูป” ออกจาก “พิมพ์ค่า” การข้ามไม่ได้ลดจำนวนครั้งที่เข้าตัวลูปตามขอบเขตเดิม
| i | ข้ามเพราะเป็นเลขคี่หรือไม่ | พิมพ์หรือไม่ | ทำส่วน i++ หรือไม่ | ค่าที่ใช้ตรวจครั้งถัดไป |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 |
ใน for เส้นทาง continue ยังไปทำส่วนปรับค่า ก่อนกลับไปตรวจเงื่อนไข ส่วน while จะกลับไปตรวจเงื่อนไขทันที และ do-while จะไปตรวจเงื่อนไขท้ายลูป ไม่มีการเพิ่มตัวนับให้อัตโนมัติในสองชนิดหลัง
วิเคราะห์ลูปนับ 1–5 ที่ใช้ while และวาง i++ ท้ายบล็อก ถ้าข้ามเลขคี่ก่อนถึง i++ จะติดอยู่ที่ค่าใด ไม่ต้องรันลูปค้างเพื่อพิสูจน์ ให้ trace สองครั้งที่มีสถานะเดิม แล้วแก้เส้นทางให้ตัวนับเปลี่ยนแน่นอน
5.5 กรณีศึกษาต่อเนื่อง: รับคะแนนหลายครั้งโดยไม่ทำข้อมูลหาย
ลูปไม่ได้มีไว้ย่อข้อความที่เขียนซ้ำเท่านั้น แต่ช่วยรักษากฎเดิมขณะจัดการข้อมูลหลายชิ้น สมมติรับคะแนนสมมติห้าค่าเพื่อหาผลรวม ค่าเฉลี่ย และจำนวนคะแนนที่ถึงเกณฑ์ฝึกหัด 50 เราต้องแยกตัวแปรตามหน้าที่ ได้แก่ตัวนับจำนวนข้อมูลที่รับแล้ว ตัวสะสมผลรวม และตัวนับข้อมูลที่ผ่านเงื่อนไข
5.5.1 ไล่สถานะก่อนอ่าน ระหว่างทำ และหลังจบรอบ
ใช้คะแนน 40, 60, 50, 80 และ 70 โดยเริ่ม sum = 0 และ passed = 0 ในแต่ละรอบอ่านค่าหนึ่ง ตรวจช่วง สะสมผลรวม แล้วเพิ่ม passed เมื่อคะแนนอย่างน้อย 50 การเริ่มตัวสะสมที่ศูนย์หมายถึงยังไม่มีข้อมูลใดถูกรวม ไม่ใช่เพราะทุกตัวแปรต้องเริ่มศูนย์เสมอ
| หลังประมวลผลค่า | จำนวนที่ประมวลผล | sum | passed |
|---|---|---|---|
| ยังไม่มี | 0 | 0 | 0 |
| 40 | 1 | 40 | 0 |
| 60 | 2 | 100 | 1 |
| 50 | 3 | 150 | 2 |
| 80 | 4 | 230 | 3 |
| 70 | 5 | 300 | 4 |
ตารางบอกความสัมพันธ์ว่า หลังประมวลผลไปจำนวนหนึ่ง sum ต้องเท่าผลรวมของค่าที่รับมาแล้วทั้งหมด ความสัมพันธ์ที่รักษาไว้ในแต่ละรอบนี้ช่วยตรวจโค้ดได้ เช่น ถ้านำ sum = 0; ไปไว้ภายในลูป ผลรวมเก่าจะถูกล้างทุกครั้ง และแถวสุดท้ายอาจเหลือเพียง 70
เมื่อคำนวณเฉลี่ยต้องใช้จำนวนข้อมูลที่นำมารวมจริง ถ้าข้ามคะแนนผิดช่วงด้วย continue แต่ยังหารด้วยห้าทั้งที่รับถูกเพียงสี่ค่า ผลจะไม่ตรงกับความหมายของค่าเฉลี่ย ต้องเลือกกติกาให้ชัดว่าจะปฏิเสธทั้งชุด รับใหม่จนได้ห้าค่าที่ถูก หรือเฉลี่ยเฉพาะค่าที่ผ่าน แล้วใช้ตัวนับให้ตรงกติกานั้น
5.5.2 ตัวอย่างลูปจำนวนรอบแน่นอน
#include <stdio.h>
int main(void)
{
int sum = 0;
int passed = 0;
for (int i = 0; i < 5; ++i) {
int score;
if (scanf("%d", &score) != 1) {
puts("Input error");
return 1;
}
if (score < 0 || score > 100) {
puts("Range error");
return 1;
}
sum += score;
if (score >= 50) {
++passed;
}
}
printf("sum=%d average=%.2f passed=%d\n",
sum, sum / 5.0, passed);
return 0;
}สำหรับข้อมูลในตาราง ผลคือ sum=300 average=60.00 passed=4 โปรแกรมนี้เลือกหยุดทั้งชุดเมื่อพบข้อมูลผิด ไม่ได้ข้ามแล้วทำเหมือนประมวลผลครบห้าค่า ผู้เรียนควรบอกนโยบายนี้ได้จากเส้นทาง return ไม่ใช่ดูเพียงข้อความผิดพลาด
ใบงาน 4: ข้อมูลถูกห้าค่าหรืออ่านห้าครั้ง
คัดตารางของชุด 40 60 50 80 70 แล้วปิดคอลัมน์ผลในตัวอย่างเดิม ลองคำนวณ sum และ passed ด้วยมืออีกครั้ง จากนั้นใช้ชุด 0 49 50 99 100 เพื่อดูค่าตรงเกณฑ์ ไม่ใช้เฉพาะข้อมูลที่ผ่านทั้งหมด
| ชุดข้อมูล | จะอ่านครบห้าค่าหรือหยุดก่อน | ค่าที่ทำให้หยุด | ผลที่ทำนาย | ผลจริง |
|---|---|---|---|---|
40 60 50 80 70 | ||||
0 49 50 99 100 | ||||
40 200 60 50 80 70 | ||||
40 60 x | ||||
มีเพียง 40 60 แล้วจบข้อมูล |
โปรแกรมเดิมหยุดทั้งชุดเมื่อพบค่าผิดช่วง จึงไม่ใช่ฉบับรับใหม่จนได้ห้าค่าถูกต้อง ในงานขยายหัวข้อ 5.7.2 ให้ใช้ตัวนับที่ยอมรับจริงควบคุมการจบ และแยกจำนวนครั้งที่อ่านเมื่ออยากรายงานการลองป้อน
ก่อนใช้ continue ให้บอกได้ว่ารอบที่มี 200 จะไม่เพิ่มตัวสะสมและตัวนับใด หากเปลี่ยนกติกาแต่ยังเพิ่มตัวนับทุกครั้ง จะจบเร็วทั้งที่มีคะแนนถูกต้องไม่ครบ
5.6 ออกแบบลูปที่ไม่รู้จำนวนข้อมูลล่วงหน้า
5.6.1 sentinel เป็นสัญญาณจบ ไม่ใช่ข้อมูล
ถ้ารับคะแนนจนพบ -1 ค่า -1 เป็นสัญญาณจบ ไม่ต้องสะสมและไม่เพิ่มจำนวนข้อมูล ต้องตรวจสัญญาณจบทันทีหลังอ่านสำเร็จ จากนั้นจึงตรวจว่าคะแนนอยู่ใน 0–100 และค่อยประมวลผล การนำ -1 มารวมก่อนแล้วหักคืนภายหลังทำให้โค้ดผูกกับรายละเอียดมากขึ้นและตรวจยาก
ลอง trace ข้อมูล 40 60 -1 จะมีข้อมูลจริงสองค่า ผลรวม 100 เฉลี่ย 50 ส่วนข้อมูล -1 ทันทีมีข้อมูลจริงศูนย์ค่า จึงต้องรายงานว่าไม่มีข้อมูล ไม่คำนวณ sum / count เพราะตัวหารเป็นศูนย์ การตรวจกรณีว่างเป็นส่วนจำเป็นของอัลกอริทึม ไม่ใช่งานเก็บรายละเอียดหลังเขียนเสร็จ
5.6.2 ทุกเส้นทางต้องทำให้เข้าใกล้จุดจบ
เมื่อใช้ while อ่านข้อมูล ต้องคิดว่าเมื่ออ่านสำเร็จ อ่านผิด และพบสัญญาณจบ เกิดอะไรต่อ หากอ่าน x ด้วย scanf("%d", ...) ไม่สำเร็จแล้ววนกลับทันทีโดยไม่จบหรือจัดการข้อมูลค้าง อักขระเดิมอาจยังอยู่และทำให้ผิดซ้ำไม่สิ้นสุด ในระดับนี้การรายงานแล้วจบเป็นแนวทางที่อธิบายได้ชัด
ระวัง continue ในลูป while ที่เพิ่มตัวนับไว้ท้ายบล็อก เพราะการข้ามอาจข้ามการเพิ่มค่าด้วย ส่วน for จะไปทำส่วนปรับค่าก่อนตรวจเงื่อนไขรอบถัดไป ให้ trace กรณีข้ามเลขคู่จาก 1–5 โดยระบุจุดที่ตัวนับเปลี่ยน ถ้าอธิบายเส้นทางไม่ได้ ยังไม่ควรแก้ด้วยการเพิ่มตัวนับหลายจุดโดยเดา
5.6.3 break จบลูปชั้นใด
ในลูปซ้อนสำหรับตรวจอุปกรณ์สามกล่อง กล่องละสี่ชิ้น break ภายในลูปชิ้นจบเพียงลูปชั้นใน ไม่ได้ออกจากลูปกล่องทั้งหมด หากโจทย์ต้องหยุดงานทั้งชุดเมื่อพบของชำรุด ต้องออกแบบสัญญาณสถานะหรือแยกงานเป็นฟังก์ชันในบทต่อไป ไม่ถือว่าการเขียน break หนึ่งครั้งหมายถึงจบโปรแกรมเสมอ
ตัวอย่างพร้อมรัน: หยุดด้วยสัญญาณและรักษาจำนวนข้อมูลจริง
สร้างไฟล์ sentinel_scores.c รับคะแนนสมมติ 0–100 จนพบ -1 โปรแกรมข้ามจำนวนเต็มผิดช่วงและรับต่อ แต่หยุดเมื่ออ่านไม่สำเร็จ จำกัดจำนวนค่าที่ยอมรับไว้ 100 ค่า จึงสะสมผลรวมไม่เกิน 10000 ซึ่งอยู่ในช่วงขั้นต่ำของ int
/* sentinel_scores.c : sentinel, accepted count, and bounded sum */
#include <stdio.h>
int main(void)
{
int count = 0;
int sum = 0;
while (count < 100) {
int score;
if (scanf("%d", &score) != 1) {
puts("Input error");
return 1;
}
if (score == -1) {
break;
}
if (score < 0 || score > 100) {
puts("Skipped");
continue;
}
sum += score;
++count;
}
if (count == 0) {
puts("No data");
} else {
printf("count=%d sum=%d average=%.2f\n",
count, sum, sum / (double)count);
}
if (count == 100) {
puts("Limit reached");
}
return 0;
}ในตัวอย่างนี้อ่านข้อมูลใหม่ที่ต้นทุกรอบ แม้ข้ามค่าผิดช่วง จึงไม่ติดอยู่กับค่าเดิม แต่ถ้ามีค่าผิดช่วงส่งเข้ามาต่อเนื่อง โปรแกรมยังรอค่าที่ถูกหรือสัญญาณจบได้ จำนวนค่าที่ยอมรับไม่ใช่ตัวนับจำนวนครั้งที่ลองทั้งหมด
| ข้อมูลที่จะลอง | ประเด็นที่ต้องสังเกต | ทำนายก่อนรัน | ผลจริง |
|---|---|---|---|
-1 | ไม่มีข้อมูลและไม่หารด้วยศูนย์ | ||
40 60 -1 | สัญญาณจบไม่ใช่คะแนน | ||
40 200 60 -1 | ค่าผิดช่วงไม่นับรวม | ||
0 100 -1 | ศูนย์เป็นข้อมูลที่ยอมรับ | ||
x หรือจบข้อมูลทันที | อ่านไม่สำเร็จแล้วจบ |
เปรียบเทียบกับโปรแกรมคะแนนห้าค่า ระบุสิ่งที่เปลี่ยนสามอย่าง ได้แก่เงื่อนไขจบ กติกาค่าผิดช่วง และตัวหารเฉลี่ย การคัดลูปมาโดยไม่เปลี่ยนตัวหารจาก 5 เป็นจำนวนที่รับจริงยังทำให้ผลผิดได้
5.7 ขอบเขตของการคำนวณซ้ำและเวิร์กช็อปขยาย
5.7.1 แฟกทอเรียลไม่ได้โตช้าเหมือนตัวนับ
ผลรวมของ 1–5 เท่ากับ 15 แต่ผลคูณของ 1–5 เท่ากับ 120 เมื่อเพิ่มจำนวนรอบ ผลคูณโตเร็วมาก จึงไม่ควรให้ตัวอย่างแฟกทอเรียลรับจำนวนใดก็ได้แล้วสะสมใน int การเกิด signed integer overflow ไม่ใช่กฎการวนกลับที่พึ่งพาได้
สำหรับแบบฝึกหัดพกพาระดับต้น สามารถกำหนดรับเพียง 0–8 และเก็บผลใน unsigned long เพราะ 8! เท่ากับ 40320 ซึ่งอยู่ในช่วงขั้นต่ำที่ชนิดนี้ต้องรองรับ ใช้ค่าตั้งต้น 1 เพื่อให้กรณี 0! ได้ 1 เมื่อไม่มีรอบคูณ การเลือก 1 แทน 0 มาจากบทบาทของเอกลักษณ์การคูณ ถ้าเริ่ม 0 ทุกผลคูณจะคงเป็นศูนย์
แหล่งอ่านขยายคือ GNU C: Loop Statements สำหรับโครงสร้างลูป และ GNU C: Integer Arithmetic สำหรับการคิดเรื่องชนิดของการคำนวณ
5.7.2 ทดลองสามงานที่ใช้ลูปต่างกัน
งานแรกคำนวณผลรวมของจำนวน 1–10 ใช้ลูปจำนวนรอบแน่นอนและ trace ทุกแถว งานที่สองรับคะแนนจนพบสัญญาณจบ ใช้ลูปตามเงื่อนไขและทดสอบกรณีไม่มีข้อมูล งานที่สามแสดงเมนูอย่างน้อยหนึ่งครั้งก่อนตัดสินใจวนต่อ ใช้โครงสร้างที่สะท้อนการทำก่อนตรวจหลัง อธิบายว่าทำไมการเลือกชนิดลูปจึงสอดคล้องกับข้อกำหนด
จากนั้นนำโปรแกรมคะแนนห้าค่ามาปรับให้รับใหม่เมื่อพบค่าที่เป็นจำนวนเต็มแต่ผิดช่วง โดยจำนวนค่าที่ถูกต้องยังต้องครบห้า แยกตัวนับ “จำนวนครั้งที่ลองป้อน” กับ “จำนวนคะแนนที่ยอมรับ” และทดลองข้อมูล 40 200 60 50 80 70 จะลองอ่านหกครั้งแต่ใช้คะแนนห้าค่า
บันทึกผลก่อนและหลังการเปลี่ยนกติกา รวมทั้ง trace รอบที่มี 200 ระบุชัดว่ารอบนั้นเปลี่ยนตัวนับใดและไม่เปลี่ยนตัวสะสมใด ผู้เรียนต้องแสดงได้ว่าการเพิ่ม continue ไม่ได้ทำให้เงื่อนไขจบคลาดเคลื่อน และกรณีอ่านไม่สำเร็จยังไม่เกิดลูปไม่สิ้นสุด
ท้ายงานให้สร้างชุดข้อมูลแยกข้อผิดพลาดสามแบบ ได้แก่เริ่มตัวสะสมผิด วนเกินหนึ่งรอบ และหารด้วยจำนวนข้อมูลที่ไม่ตรงกับค่าที่รวม สิ่งที่ส่งควรมีทั้งกรณีทดสอบและเหตุผลที่กรณีนั้นจับความผิดได้ เพื่อเชื่อมไปสู่อาร์เรย์และการทดสอบในบทถัดไป
5.8 ตัวอย่างเปรียบเทียบ: ลูปซ้อนสร้างรายงานจำนวนชุด
ต้องการแสดงตารางว่าสาย 0–5 เส้นจัดได้กี่ชุดเมื่อขนาดชุดเป็น 2 และ 3 เส้น สามารถคิดเป็นแถวของจำนวนสายและคอลัมน์ของขนาดชุด แต่ละช่องคำนวณได้จากข้อมูลแถวกับคอลัมน์ ไม่ต้องสร้างตัวแปรใหม่ให้ทุกช่อง
เริ่ม trace แบบเล็กที่จำนวนสาย 0–2 และขนาดชุด 2–3 ก่อน จะมีการคำนวณหกครั้ง คือ (0,2), (0,3), (1,2), (1,3), (2,2), (2,3) วงรอบนอกเปลี่ยนจำนวนสาย วงรอบในเดินครบขนาดชุด เมื่อจบแถวต้องขึ้นบรรทัดใหม่เพียงครั้งเดียว ตำแหน่งคำสั่งขึ้นบรรทัดจึงเปลี่ยนรูปตารางได้แม้สูตรถูก
| จำนวนสาย | ชุดละ 2 | ชุดละ 3 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 0 |
| 2 | 1 | 0 |
| 3 | 1 | 1 |
| 4 | 2 | 1 |
| 5 | 2 | 1 |
นับจำนวนการคำนวณในตารางจริงเป็นหกแถวคูณสองคอลัมน์ ได้สิบสองครั้ง หากวงรอบในเริ่มที่ขนาดชุดศูนย์ จะเกิดการหารด้วยศูนย์ในสูตรจัดชุด แสดงว่าขอบเขตลูปไม่ใช่เรื่องจำนวนรอบเท่านั้น แต่ต้องรักษาขอบเขตที่นิพจน์ด้านในยอมรับด้วย
ลองเพิ่มเงื่อนไขให้แสดงเฉพาะกรณีไม่มีของเหลือ โดยใช้ continue อย่างมีเหตุผลแล้วอธิบายว่าข้ามการแสดงช่องหรือข้ามทั้งแถว ผลรูปแบบอาจไม่เป็นตารางสี่เหลี่ยมเหมือนเดิม จึงควรเลือกแสดงเป็นรายการคู่ข้อมูลแทน ไม่ฝืนใช้รูปแบบเดิมโดยไม่คิดผล
ท้ายกิจกรรมให้ผู้เรียนทำนายจำนวนรอบก่อนรัน เปรียบเทียบกับ trace และขยายช่วงข้อมูลทีละน้อย การสังเกตจำนวนงานที่โตตามผลคูณของสองช่วงเป็นฐานสำหรับเรื่องประสิทธิภาพ ไม่ต้องใช้ข้อมูลใหญ่จนเกิด output จำนวนมากเพื่อพิสูจน์สิ่งที่นับได้จากโครงสร้าง
ใบงาน 5: break อยู่ใกล้ลูปใด
สร้างไฟล์ nested_break.c รับหมายเลขชิ้นที่จะหยุดตรวจในแต่ละกล่อง ช่วง 1–4 มีกล่องสามกล่อง โปรแกรมนี้ใช้หมายเลขเดียวกันทุกกล่องเพื่อให้เห็นผลของ break ชัดก่อนเพิ่มเงื่อนไขจริง
/* nested_break.c : break only the inner item loop */
#include <stdio.h>
int main(void)
{
int stop_item;
if (scanf("%d", &stop_item) != 1) {
puts("Input error");
return 1;
}
if (stop_item < 1 || stop_item > 4) {
puts("Range error");
return 1;
}
for (int box = 1; box <= 3; ++box) {
printf("box=%d:", box);
for (int item = 1; item <= 4; ++item) {
if (item == stop_item) {
break;
}
printf(" %d", item);
}
puts("");
}
return 0;
}ทดสอบ 1, 2 และ 4 ก่อนรันให้วาดเส้นครอบลูปกล่องกับลูปชิ้น ระบุว่าพบ break แล้วไปคำสั่งใดต่อ คำสั่งพิมพ์ชิ้นอยู่หลังการตรวจ จึงไม่พิมพ์ชิ้นที่ตรงกับหมายเลขหยุด
หากต้องหยุดทุกกล่องเมื่อพบปัญหา ต้องออกแบบสถานะร่วมกับเงื่อนไขลูปนอก ไม่เพิ่ม break โดยเดาตำแหน่ง ลองเขียนขั้นตอนภาษาไทยก่อน ส่วนการแยกเป็นฟังก์ชันจะต่อยอดในบทที่ 6
5.9 อ่านโค้ดที่ผิดและทำความเข้าใจข้อความวินิจฉัย
ตัวอย่างต่อไปนี้เป็นโค้ดฝึกแยกจากงานที่ต้องส่ง แสดงข้อผิดพลาดที่สัมพันธ์กับเนื้อหาบท พร้อมข้อความวินิจฉัยจริง วิธีอ่าน และฉบับแก้ คำว่า diagnostic ใช้รวมข้อความ error, warning และ note จึงไม่ควรเรียกทุกบรรทัดว่า error หรือถือว่าการไม่มี error แปลว่าโปรแกรมถูกตามโจทย์
ที่มาของ log: ผู้เขียนทดสอบด้วย GCC 11.4.0 บน Ubuntu ผ่าน Linux environment เมื่อ 24 ตุลาคม 2569 ใช้ C11 และตัวเลือกดังนี้ ชื่อไฟล์ เลขบรรทัด คอลัมน์ ถ้อยคำ และคำเตือนที่เปิดใช้อาจต่างจากแพลตฟอร์มการเรียน นักศึกษาไม่จำเป็นต้องเปลี่ยนเครื่องมือหรือการติดตั้งตามชุดทดสอบของผู้เขียน
-std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0ข้อความรูปแบบ bad.c:5:17 อ่านว่าไฟล์ bad.c บรรทัด 5 คอลัมน์ 17 ส่วน ^ และ ~ ช่วยชี้บริเวณที่ตรวจพบ จุดนี้อาจเป็นปลายเหตุจากบรรทัดก่อนหน้า error ทำให้การสร้างโปรแกรมล้มในกรณีนั้น warning เตือนความเสี่ยงแม้คำสั่งสร้างอาจจบสำเร็จ และ note ให้บริบท เช่น declaration เดิม ต้องอ่านคู่กัน (GCC: Diagnostics)
ตัวอย่างที่ผิด 1: ใช้ตัวนับนอกขอบเขตของ for
ประเภท: ข้อผิดพลาดขณะคอมไพล์
ตัวแปร i ที่ประกาศในส่วนต้นของ for มีขอบเขตสำหรับลูปนั้น การนำมาใช้หลังจบลูปจึงไม่พบ declaration ที่ใช้งานได้ กรณีนี้ต่างจากตัวนับมีค่าไม่ถูกต้อง เพราะ compiler ยังไม่ยอมรับการอ้างชื่อนี้
โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; ++i) {
printf("item=%d\n", i);
}
printf("last=%d\n", i);
return 0;
}คำสั่งสร้างและข้อความที่ได้จริง
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o badbad.c: In function 'main':
bad.c:7:25: error: 'i' undeclared (first use in this function)
7 | printf("last=%d\n", i);
| ^
bad.c:7:25: note: each undeclared identifier is reported only once for each function it appears inสถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 1 คำสั่งล้มและไม่ได้รัน executable ของตัวอย่างนี้ การรันไฟล์เก่าที่อาจค้างจาก build ครั้งก่อนจะไม่ใช่หลักฐานของโค้ดปัจจุบัน
อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร
undeclared ที่บรรทัด printf หลังลูปไม่ได้แปลว่าไม่เคยมีชื่อ i ในไฟล์ แต่หมายถึงไม่เห็น declaration ในขอบเขตนี้ ต้องดูวงเล็บและจุดประกาศ ไม่ย้ายทุกตัวแปรเป็น global เพื่อทำให้ใช้ได้ทุกแห่ง
โค้ดที่แก้แล้ว
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; ++i) {
printf("item=%d\n", i);
}
return 0;
}เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ
ถ้าโจทย์ต้องแสดงแต่รายการ ให้ลบการอ่านนอกลูปดังฉบับแก้ หากต้องใช้จำนวนที่ประมวลผลต่อจริง ให้ประกาศตัวแปร count ที่สื่อบทบาทและขอบเขตเหมาะสม ไม่อาศัยชื่อ i เป็นคำตอบทุกงาน
ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง
item=0
item=1
item=2ตัวอย่างที่ผิด 2: ลูปรันเกินจำนวนที่ต้องการหนึ่งรอบ
ประเภท: ข้อผิดพลาดเชิงตรรกะที่ compiler อาจไม่เตือน
ต้องการทำงานสามรอบโดยเริ่มตัวนับที่ 0 แต่ <= ยอมให้ i เท่ากับ 3 ด้วย จึงทำงานที่ 0, 1, 2 และ 3 รวมสี่รอบ ตัวอย่างนี้นับรอบอย่างเดียวจึงไม่อ่านอาร์เรย์เกินขอบเขตและสามารถรันดูผลได้อย่างกำหนดแน่นอน
โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้
#include <stdio.h>
int main(void)
{
int rounds = 0;
int count = 3;
for (int i = 0; i <= count; ++i) {
++rounds;
}
printf("rounds=%d\n", rounds);
return 0;
}คำสั่งสร้างและข้อความที่ได้จริง
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o badไม่มีข้อความวินิจฉัยจาก compiler ในการทดลองนี้ ทั้ง stdout และ stderr ว่าง การไม่มีข้อความเป็นผลที่สังเกต ไม่ใช่การรับรองความถูกต้องของโปรแกรม
สถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 0 ขั้นสร้างสำเร็จ จากนั้นจึงตรวจพฤติกรรมขณะรันหรือใช้ชุดทดสอบตามประเภทข้างต้น
ผลจากการรันฉบับผิด
stdout:
rounds=4stderr ว่าง ไม่มี runtime diagnostic จากระบบในการทดลองนี้ ต้องใช้การเทียบผลกับข้อกำหนดเป็นหลักฐาน
สถานะจบที่ตัวเก็บผลอ่านได้: 0
ผลตามข้อกำหนดที่ใช้เทียบ:
rounds=3อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร
ไม่มี compiler diagnostic ไม่ได้แปลว่าจำนวนรอบถูก ใช้ตาราง i ก่อนตรวจเงื่อนไข ผลเงื่อนไข และ rounds หลังทำงาน เพื่ออธิบายว่ารอบเกินมาจากจุดใด ไม่ตีความ exit code 0 เป็นการยืนยันข้อกำหนด
โค้ดที่แก้แล้ว
#include <stdio.h>
int main(void)
{
int rounds = 0;
int count = 3;
for (int i = 0; i < count; ++i) {
++rounds;
}
printf("rounds=%d\n", rounds);
return 0;
}เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ
เปลี่ยนเป็น i < count ตามช่วง 0 ถึง count-1 ทดสอบ count เป็น 0, 1 และ 3 เพื่อไม่ให้การแก้ตอบได้เฉพาะกรณีเดียว และเชื่อมกับขอบเขตดัชนีที่จะเรียนในบทอาร์เรย์
ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง
rounds=3ลำดับการอ่าน log เมื่อลองโปรแกรมของบทนี้
- ระบุว่าเกิดในขั้น compile, link, run หรือ test และแยกข้อความจากระบบออกจากข้อความที่โปรแกรมพิมพ์เอง
- อ่าน diagnostic แรกกับบริบทบรรทัดก่อนหน้า แล้วใช้ note เพื่อเทียบ declaration หรือชนิดข้อมูล อย่าแก้ทุกข้อความตามลำดับโดยไม่หาสาเหตุร่วม
- แก้ทีละสาเหตุ สร้างโปรแกรมใหม่ และตรวจว่ากำลังรันผล build ใหม่ การเติมวงเล็บ ปิด warning หรือลบ test อาจทำให้ log เงียบโดยยังผิดโจทย์
- ใช้ข้อมูลเดิมที่ทำให้ผิดทดสอบซ้ำ แล้วเพิ่มกรณีขอบเขตที่สัมพันธ์กับบท การรันจบสถานะศูนย์เพียงครั้งเดียวไม่แทนการตรวจข้อกำหนดครบถ้วน
ความหมายตามภาษาอ้างจากร่างมาตรฐาน C11 โดยข้อความ diagnostic เฉพาะถ้อยคำเป็นของเครื่องมือที่ใช้ ไม่ได้กำหนดให้ compiler ทุกตัวพิมพ์เหมือนกัน (WG14 N1570, GCC: Warning Options)
ปฏิบัติการที่ 5 โครงสร้างควบคุมแบบวนซ้ำ
ปฏิบัติการนี้แบ่งเป็นสองส่วน ส่วนแรกฝึกเขียนลูป for และ while เพื่อคำนวณผลรวมและแฟกทอเรียลพร้อมออกแบบตัวแปรสะสม ส่วนที่สองฝึกควบคุมการวนซ้ำด้วย continue และ break ทั้งสองส่วนทำแยกกันได้ แต่ควรทำตามลำดับเพื่อให้เห็นการใช้ลูปตั้งแต่พื้นฐานไปจนถึงการควบคุมการไหลของลูป
ไฟล์ซอร์สโค้ดในปฏิบัติการนี้ให้ตั้งชื่อเป็นภาษาอังกฤษแบบ snake_case และบันทึกไว้ในโฟลเดอร์ของผู้เรียนเอง คำสั่งคอมไพล์ในเอกสารนี้ทดสอบด้วย gcc 13.3.0 โดยการรับข้อมูลทดสอบด้วยการป้อนค่าผ่านทางเข้ามาตรฐาน
Lab A: ผลรวมและแฟกทอเรียล
สถานการณ์ ต้องการรับจำนวนเต็มบวก n ในช่วง 1–10 เพื่อให้ผลคูณอยู่ในช่วง long ที่ C11 รับประกัน แล้วคำนวณผลรวมของจำนวน 1 ถึง n ด้วยลูป for และคำนวณแฟกทอเรียล n! ด้วยลูป while เพื่อฝึกออกแบบตัวแปรสะสมและเลือกชนิดลูป
เป้าหมาย ฝึก LLO5.1 และ LLO5.2 คือเขียนลูป for และ while และออกแบบตัวแปรสะสม/ตัวนับให้ถูกต้อง
ขั้นปฏิบัติ
- สร้างไฟล์ชื่อ
ch5_sum.cด้วยโปรแกรมแก้ไขข้อความหรือ IDE - พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
/* ch5_sum.c : ผลรวมและผลคูณสะสมด้วย for และ while */
#include <stdio.h>
int main(void) {
int n;
printf("ป้อนจำนวนเต็มบวก n: ");
if (scanf("%d", &n) != 1 || n < 1 || n > 10) {
puts("Enter an integer from 1 to 10");
return 1;
}
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
printf("ผลรวม 1..%d = %d\n", n, sum);
long factorial = 1;
int k = 1;
while (k <= n) {
factorial *= k;
k++;
}
printf("%d! = %ld\n", n, factorial);
return 0;
}- คอมไพล์และรันด้วยคำสั่ง แล้วทดสอบด้วยค่า n = 5 และ n = 10
gcc -std=c11 -Wall ch5_sum.c -o ch5_sum
./ch5_sumผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0 ป้อนค่า 5 และ 10)
ป้อนจำนวนเต็มบวก n: ผลรวม 1..5 = 15
5! = 120
ป้อนจำนวนเต็มบวก n: ผลรวม 1..10 = 55
10! = 3628800สังเกตว่าตัวแปรสะสม sum เริ่มที่ 0 เพราะเป็นการบวก ส่วนตัวแปร factorial เริ่มที่ 1 เพราะเป็นการคูณ หากเริ่ม factorial ที่ 0 ผลคูณจะเป็น 0 เสมอ ลูป for ใช้คำนวณผลรวมเพราะรู้จำนวนรอบ (1 ถึง n) ส่วนลูป while ใช้คำนวณแฟกทอเรียลเพื่อฝึกการตรวจเงื่อนไขก่อนทำและการปรับตัวนับด้วย k++
ตัดสินใจเอง A.1 ต่อยอดโปรแกรมโดยเพิ่มการนับจำนวนเลขคู่ในช่วง 1 ถึง n ด้วยลูปเดียว โดยใช้ตัวแปรนับและเงื่อนไข % 2 == 0 จากบทที่ 4 แล้วแสดงจำนวนเลขคู่ที่นับได้ โดยเลือกชนิดลูปและตั้งชื่อตัวแปรให้เหมาะสม
ผลลัพธ์ที่คาดหวัง A.1 โปรแกรมคอมไพล์ผ่านไม่มีการแจ้งเตือน และแสดงจำนวนเลขคู่ในช่วง 1 ถึง n ได้ถูกต้อง เช่น เมื่อ n = 10 ควรนับได้ 5 จำนวน (ผลที่คาดว่าจะเห็นสำหรับโปรแกรมที่ผู้เรียนออกแบบเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับการออกแบบของแต่ละคน)
จุดตรวจ A ผลรวมและแฟกทอเรียลถูกต้องทุกค่าทดสอบ ตั้งค่าเริ่มต้นตัวสะสมถูก (0 สำหรับบวก, 1 สำหรับคูณ) ปรับตัวนับครบทุกรอบ ไม่มีลูปไม่รู้จบ และโค้ดมีการจัดย่อหน้าและคอมเมนต์ส่วนหัว
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| แฟกทอเรียลได้ 0 เสมอ | ตั้งค่าเริ่มต้น factorial เป็น 0 | เปลี่ยนค่าเริ่มต้นเป็น 1 สำหรับการคูณสะสม |
| ผลรวมขาดหรือเกินหนึ่งจำนวน | ใช้เงื่อนไขขอบเขตผิด เช่น i < n แทน i <= n | ตรวจค่าตัวนับในรอบแรกและรอบสุดท้ายให้ตรงช่วง |
| โปรแกรมค้างไม่หยุด | ลืมปรับตัวนับ k++ ในลูป while | เพิ่มคำสั่งปรับตัวนับให้ทำงานทุกรอบ |
เปิดหลังผู้เรียนออกแบบด้วยตนเองแล้ว
ใบงานตรวจ Lab A: ขอบเขตและเอกลักษณ์ของตัวสะสม
ทดสอบ n ตั้งแต่ 1 ถึง 10 และเลือก -1, 0, 11, x กับการจบข้อมูลเป็นกรณีปฏิเสธ ตรวจแยกว่าจบด้วยข้อความผิดพลาดหรือได้ผลคำนวณ การยอมรับค่า 0 เป็นการเปลี่ยนข้อกำหนดของ Lab A ไม่ใช่การแก้ให้ตรงโจทย์เดิม
| n ที่ใช้ | ผลรวมที่ทำนาย | แฟกทอเรียลที่ทำนาย | จำนวนรอบบวก/คูณ | ผลจริง |
|---|---|---|---|---|
| 1 | ||||
| 3 | ||||
| 5 | ||||
| 10 |
หัวข้อ 5.7 เสนอแบบฝึกต่างหากที่ยอมรับ 0–8 จึงพูดถึง 0! ได้ ส่วน Lab A ยังคงรับ 1–10 และเก็บใน long อย่าเปลี่ยนช่วงข้อมูลโดยไม่บันทึกว่ากำลังทำฉบับใด
งาน A.1 ให้ทดสอบจำนวนเลขคู่เมื่อ n เป็น 1, 2, 3, 9 และ 10 เพื่อครอบคลุมทั้งปลายช่วงคี่และคู่ ส่วนงานรวมเลขคู่ท้ายบทต้องรวม “ค่าเลขคู่” ไม่ใช่นับ “จำนวนเลขคู่” ใช้ผลต่างสองอย่างนี้ตรวจว่าตัวแปรทำหน้าที่ตรงโจทย์
Lab B: break และ continue
สถานการณ์ ต้องการแสดงเลขคู่ในช่วง 1 ถึง 10 โดยใช้ continue ข้ามเลขคี่ และหาตัวหารแรกของจำนวนที่กำหนดโดยใช้ break หยุดลูปทันทีที่พบ เพื่อฝึกควบคุมการไหลของการวนซ้ำ
เป้าหมาย ฝึก LLO5.3 คือใช้ continue ข้ามกรณี และ break หยุดลูปตามเงื่อนไขได้ถูกต้อง
ขั้นปฏิบัติ
- สร้างไฟล์ชื่อ
ch5_loops.cด้วยโปรแกรมแก้ไขข้อความหรือ IDE - พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
/* ch5_loops.c : break และ continue */
#include <stdio.h>
int main(void) {
int count = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
count++;
printf("%d ", i);
}
printf("\nมีเลขคู่ %d จำนวน\n", count);
int num = 15;
for (int d = 2; d <= num; d++) {
if (num % d == 0) {
printf("ตัวหารแรกของ %d คือ %d\n", num, d);
break;
}
}
return 0;
}- คอมไพล์และรันด้วยคำสั่ง
gcc -std=c11 -Wall ch5_loops.c -o ch5_loops
./ch5_loopsผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)
2 4 6 8 10
มีเลขคู่ 5 จำนวน
ตัวหารแรกของ 15 คือ 3ลูปแรกใช้ continue ข้ามเลขคี่ จึงแสดงและนับเฉพาะเลขคู่ ส่วนลูปที่สองใช้ break หยุดทันทีที่พบตัวหารแรกของ 15 คือ 3 โดยไม่วนต่อจนจบ หากไม่ใช้ break ลูปจะวนต่อและพบตัวหารอื่นอีก การใช้ break จึงช่วยหยุดเมื่อได้คำตอบที่ต้องการแล้ว
ตัดสินใจเอง B.1 ดัดแปลงลูปที่สองให้รับจำนวนเต็มบวกด้วย scanf แล้วตรวจว่าเป็นจำนวนเฉพาะหรือไม่ โดยวนหาตัวหารตั้งแต่ 2 ถึงค่าก่อนตัวมันเอง หากพบตัวหารให้ใช้ break แล้วสรุปว่าไม่ใช่จำนวนเฉพาะ หากวนครบโดยไม่พบตัวหารให้สรุปว่าเป็นจำนวนเฉพาะ
ผลลัพธ์ที่คาดหวัง B.1 โปรแกรมคอมไพล์ผ่านไม่มีการแจ้งเตือน และสรุปได้ถูกต้องว่าจำนวนที่รับเข้าเป็นจำนวนเฉพาะหรือไม่ เช่น 15 ไม่ใช่จำนวนเฉพาะ (มีตัวหาร 3) ส่วน 7 เป็นจำนวนเฉพาะ (ผลที่คาดว่าจะเห็นสำหรับโปรแกรมที่ผู้เรียนออกแบบเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับการออกแบบของแต่ละคน)
จุดตรวจ B continue ข้ามกรณีที่กำหนดได้ถูกต้อง break หยุดลูปตามเงื่อนไขได้ถูกต้อง ผลลัพธ์ตรงโจทย์ คอมไพล์ผ่านไม่มีการแจ้งเตือน และโค้ดมีการจัดย่อหน้าและคอมเมนต์ส่วนหัว
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| โปรแกรมค้างเมื่อใช้ continue ในลูป while | ข้ามไปก่อนถึงคำสั่งปรับตัวนับ จึงเกิดลูปไม่รู้จบ | วางคำสั่งปรับตัวนับให้ทำงานทุกรอบก่อน continue |
| ผลลัพธ์กลับด้านระหว่างข้ามกับหยุด | สับสนหน้าที่ของ break กับ continue | ทบทวนว่า break ออกจากลูป ส่วน continue ข้ามรอบ |
| หาตัวหารแล้วยังวนต่อ | ลืมใส่ break เมื่อพบตัวหาร | เพิ่ม break ภายใน if ที่ตรวจพบตัวหาร |
เปิดหลังผู้เรียนส่งงานแล้ว
ใบงานตรวจ Lab B: พบตัวหารกับตัดสินจำนวนเฉพาะเป็นคนละขั้น
ในโค้ดตั้งต้น num = 15 ให้บันทึกค่าของ d ที่ลองและค่าที่ทำให้จบ แล้วทดลองเปลี่ยนเป็น 2, 7 และ 9 เพื่อเข้าใจเส้นทาง หาตัวหารแรกที่รวมตัวเลขเองยังไม่ใช่การตัดสินจำนวนเฉพาะโดยตรง
| จำนวนในงาน B.1 | คำถามก่อนเขียนเงื่อนไข |
|---|---|
| 1 | จำนวนที่เล็กกว่า 2 จัดอย่างไร |
| 2 | ถ้าไม่เข้าสู่ลูปหาตัวหาร จะสรุปจากอะไร |
| 7 | ลองแล้วไม่พบตัวหาร ต้องเก็บสถานะอย่างไร |
| 9 | เมื่อพบตัวหารแล้วหยุดที่ใด |
| 15 | การตรวจต้องมีทั้งเลขคู่และเลขคี่หรือไม่ |
จำนวนเฉพาะคือจำนวนเต็มมากกว่า 1 ที่มีตัวหารบวกเพียง 1 และตัวมันเอง สำหรับการลองในห้องใช้ช่วง 1–100 และตรวจช่วงก่อนวน ค่า 1 จึงไม่ใช่จำนวนเฉพาะ แม้ลูปหาตัวหารจะไม่ได้ทำงาน
เขียนเหตุผลเลือก break พร้อมแยกผล “ไม่พบตัวหาร” ออกจาก “ไม่ได้ตรวจเพราะอยู่นอกโดเมน” สำหรับงานท้ายบทที่ข้ามพหุคูณของ 3 และหยุดหลัง 15 ให้ trace ค่า 15 กับ 16 เพื่อทดสอบขอบเขตโดยตรง
เก็บกวาด
เมื่อจบปฏิบัติการ ให้ลบไฟล์ปฏิบัติการที่สร้างจากการคอมไพล์ (เช่น ch5_sum และ ch5_loops) และเก็บเฉพาะไฟล์ซอร์สโค้ด .c ไว้ เพื่อให้โฟลเดอร์งานสะอาดและส่งไฟล์ซอร์สโค้ด .c พร้อมบันทึกผลการรันตามที่กำหนด
ตรวจความเข้าใจก่อนเริ่มบทที่ 6
ตอบโดยอ้างอิงสถานะตัวแปรหรือเส้นทางคำสั่ง ไม่ใช้เพียงคำว่า “จำรูปแบบได้”
- เหตุใดลูปพิมพ์ 1–5 จึงตรวจเงื่อนไขมากกว่าจำนวนรอบที่พิมพ์
- เมื่อตั้ง limit เป็น 0 เหตุใด
do-whileยังเพิ่มตัวนับได้หนึ่งครั้ง continueในforยังทำส่วนใด และต่างจากwhileอย่างไร- ถ้าชุดข้อมูลเริ่มที่ -1 จะหลีกเลี่ยงการหารด้วยศูนย์ตรงไหน
- เหตุใดเริ่มผลคูณที่ 0 จึงไม่ใช่ข้อผิดพลาดแบบเดียวกับลืมเพิ่มตัวนับ
breakในswitchที่อยู่ในเมนูแบบวนซ้ำทำให้จบโปรแกรมเสมอหรือไม่
เมื่ออธิบายได้ ให้เลือกงานผลรวมและงานตัดสินจำนวนเฉพาะมาเขียนเป็นกลุ่มขั้นตอนที่มีข้อมูลเข้าและผลลัพธ์ชัดเจน เพื่อเตรียมแยกงานเป็นฟังก์ชันใน บทที่ 6 ฟังก์ชัน
สรุป
บทนี้ต่อยอดโครงสร้างควบคุมแบบทางเลือกจากบทที่ 4 ไปสู่การทำงานซ้ำด้วยลูปสามชนิด เริ่มจากลูป for ที่เหมาะเมื่อรู้จำนวนรอบ พร้อมส่วนประกอบสามส่วน (ค่าเริ่ม เงื่อนไข ปรับค่า) และกับดักความคลาดเคลื่อนที่ขอบเขต ต่อด้วยลูป while ที่ตรวจเงื่อนไขก่อนทำและ do-while ที่ทำก่อนอย่างน้อยหนึ่งรอบ รวมถึงความเสี่ยงลูปไม่รู้จบเมื่อลืมปรับตัวนับ จากนั้นอธิบายการเลือกชนิดลูปตามลักษณะโจทย์ การใช้ตัวแปรสะสม sum += i; และตัวนับ count++; และการตั้งค่าเริ่มต้นตัวสะสมให้ถูก (0 สำหรับบวก, 1 สำหรับคูณ) สุดท้ายคือการควบคุมการวนซ้ำด้วย break ที่ออกจากลูป และ continue ที่ข้ามไปรอบถัดไป ทักษะเหล่านี้เป็นพื้นฐานของการประมวลผลซ้ำในบทถัดไป
สิ่งที่ส่งต่อไปยังบทถัดไป
| สิ่งที่ส่งต่อ | ค่า/รูปแบบ | ใช้ในบท |
|---|---|---|
| ลูป for, while และ do-while | for (เริ่ม; เงื่อนไข; ปรับค่า), while, do-while | บท 6 (ฟังก์ชัน), บท 7 (อาร์เรย์) เป็นต้นไป |
| ตัวแปรนับและตัวแปรสะสม | count++;, sum += i;, เริ่ม 0 สำหรับบวก / 1 สำหรับคูณ | ทุกบทที่มีการประมวลผลซ้ำ |
| การควบคุมการวนซ้ำ | break ออกจากลูป, continue ข้ามรอบ | บท 7 (วนอาร์เรย์) เป็นต้นไป |
สรุปคำสั่ง
| คำสั่ง | หน้าที่ |
|---|---|
gcc -std=c11 -Wall ch5_sum.c -o ch5_sum | คอมไพล์ซอร์สโค้ด ch5_sum.c เป็นไฟล์ปฏิบัติการ ch5_sum พร้อมเปิดการแจ้งเตือน |
gcc -std=c11 -Wall ch5_loops.c -o ch5_loops | คอมไพล์ซอร์สโค้ด ch5_loops.c เป็นไฟล์ปฏิบัติการ ch5_loops พร้อมเปิดการแจ้งเตือน |
./ch5_sum หรือ ./ch5_loops | รันไฟล์ปฏิบัติการที่คอมไพล์แล้ว |
แบบฝึกหัดท้ายบท
ตอนที่ 1 ฝึกทบทวน (15 ข้อ ตรวจคำตอบให้ทันที)
ตอนที่ 2 แบบฝึกหัดเขียนตอบ (5 ข้อ)
กลุ่ม ก (เข้าใจ)
- ก1 (LLO5.1) จงอธิบายส่วนประกอบสามส่วนของลูป
forพร้อมระบุหน้าที่ของแต่ละส่วน - ก2 (LLO5.1) จงอธิบายความแตกต่างระหว่างลูป
whileกับdo-whileเมื่อเงื่อนไขเป็นเท็จตั้งแต่แรก
กลุ่ม ข (ประยุกต์)
- ข1 (LLO5.2) จงเขียนโปรแกรมภาษา C ที่รับจำนวนเต็มบวก n ด้วย
scanfแล้วใช้ลูปคำนวณผลรวมของเลขคู่ในช่วง 1 ถึง n พร้อมตั้งค่าเริ่มต้นตัวสะสมให้ถูกต้อง แล้วคอมไพล์และรันให้ได้ผลจริง - ข2 (LLO5.3) จงเขียนโปรแกรมที่วนแสดงจำนวน 1 ถึง 20 แต่ข้ามจำนวนที่หารด้วย 3 ลงตัวด้วย
continueและหยุดลูปด้วยbreakเมื่อถึงจำนวนที่มากกว่า 15
กลุ่ม ค (วิเคราะห์)
- ค1 (LLO5.2) จากโปรแกรมคำนวณแฟกทอเรียลที่ตั้งค่าเริ่มต้นตัวสะสมเป็น 0 และลูป
whileที่ลืมคำสั่งปรับตัวนับ จงวิเคราะห์ว่าเกิดข้อผิดพลาดใดได้บ้าง และเสนอวิธีแก้ให้ถูกต้อง
การศึกษาค้นคว้าเพิ่มเติม
- ศึกษาโครงสร้างควบคุมแบบวนซ้ำ
for,whileและdo-whileเพิ่มเติมจาก Kernighan และ Ritchie (1988) บทที่ 3 ว่าด้วยการควบคุมการไหล แล้วทดลองแปลงลูปforเป็นwhileที่ให้ผลเท่ากัน - ทบทวนความหมายของคำสั่ง
breakและcontinueในมาตรฐานภาษา C ตาม ISO/IEC 9899 เพื่อเข้าใจขอบเขตการทำงานของทั้งสองคำสั่งภายในลูป - ทดลองออกแบบโปรแกรมที่ใช้ตัวแปรสะสมหลายตัวในลูปเดียว เช่น นับทั้งจำนวนเลขคู่และผลรวม แล้วเปรียบเทียบความกระชับกับการวนหลายลูป
เอกสารอ้างอิง
International Organization for Standardization. (2011). ISO/IEC 9899:2011 — Information technology — Programming languages — C. ISO.
Kernighan, B. W., & Ritchie, D. M. (1988). The C programming language (2nd ed.). Prentice Hall.
ตำราหลักภาษาไทยของรายวิชา. (ผู้สอนเติมรายละเอียดบรรณานุกรม — เพิ่มตามมติผู้สอน 21 ต.ค. 2569).
แหล่งปฐมภูมิที่ตรวจประกอบการปรับปรุง 23 ตุลาคม 2569
ใช้เอกสารข้างต้นตรวจหลักการก่อนเรียบเรียงส่วนขยายและสร้างข้อมูลสมมติ ตัวอย่างใหม่ที่เป็นโปรแกรม C ครบไฟล์ตรวจด้วย GCC 12.2.0 (Debian Linux) และ GCC 6.3.0 (MinGW-w64), C11 และคำเตือน -Wall -Wextra -Wpedantic เมื่อ 23 ตุลาคม 2569 แยกจากผลทดสอบ GCC 13.3.0 ของตัวอย่างเดิม ค่ารูปแบบและผลคำนวณตรวจจากการรัน ส่วนเวลา CPU ขึ้นกับสภาพแวดล้อม ไม่ใช่ผลตายตัว
แหล่งปฐมภูมิเพิ่มสำหรับชุดสอนพร้อมใช้บทที่ 5
- GNU C: for Statement — ลำดับค่าเริ่ม ตรวจ ทำ และปรับค่า
- GNU C: while Statement — ตรวจเงื่อนไขก่อนทำ
- GNU C: do-while Statement — ทำก่อนตรวจท้ายรอบ
- GNU C: continue Statement — ส่วนปรับค่าของ for ยังทำงาน
- GNU C: break Statement — ออกจากลูปหรือ switch ที่ครอบใกล้ที่สุด
ตรวจเอกสารก่อนเขียนส่วนเพิ่มเมื่อ 23 ตุลาคม 2569 ตัวอย่างใช้ C11 ไม่ใช้ส่วนขยาย GNU ชุดข้อมูลเป็นข้อมูลสมมติและผลลัพธ์ของการคำนวณ เวลาในแผนสอนไม่ใช่ผลวัดจากการอ่านบทความ