4.3 break continue และอ่านจนหมดข้อมูล
ลูปในสองหน้าก่อนวนครบทุกรอบเสมอ แต่งานจริงบางทีอยากเลิกกลางทาง เช่น เจอสิ่งที่หาแล้วก็พอ หรืออยากข้ามข้อมูลบางตัวไป หน้านี้รู้จัก break กับ continue แล้วใช้มันอ่านข้อมูลแบบที่โจทย์ไม่บอกว่ามีกี่ตัว ซึ่งมีสองแบบ คือ อ่านจนเจอค่าหยุด และอ่านจนหมดไฟล์
break ออกจากลูปทันที
ครูอยากรู้ว่านักเรียนคนแรกในรายชื่อที่สอบตก (ได้ต่ำกว่า 50) คือคนที่เท่าไร พอเจอคนแรกแล้ว ไม่ต้องดูคนที่เหลืออีก
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
int fail_at = 0;
for (int i = 1; i <= n; i++) {
int score;
scanf("%d", &score);
if (score < 50) {
fail_at = i;
break;
}
}
if (fail_at == 0) {
printf("everyone passed\n");
} else {
printf("first fail is student %d\n", fail_at);
}
return 0;
}
6
70 85 49 62 48 50
first fail is student 3
breakหยุดลูปที่มันอยู่ทันที แล้วกระโดดไปทำบรรทัดถัดจากปีกกาปิดของลูป ในตัวอย่างคือifบรรทัดที่ 16fail_atเป็นตัวแปรที่จำว่าเจอหรือยัง เริ่มที่ 0 ซึ่งไม่ใช่ลำดับของนักเรียนคนไหน ถ้าลูปวนจนครบโดยไม่เจอ ค่าก็ยังเป็น 0 ตัวแปรที่ใช้บอกสถานะแบบนี้เรียกว่า flag (ธง)
break ทิ้งข้อมูลที่ยังไม่ได้อ่าน
หลัง break ตัวเลข 62 48 50 ไม่ได้ถูกอ่านเลย ในตัวอย่างนี้ไม่เป็นไรเพราะโปรแกรมจบแล้ว แต่ถ้าหลังลูปยังต้องอ่านข้อมูลส่วนอื่นต่อ scanf ตัวถัดไปจะได้ 62 แทนที่จะได้ข้อมูลที่ตั้งใจ แบบนั้นห้ามใช้ break ให้วนอ่านจนครบ แล้วใช้ flag จำผลแทน
continue ข้ามไปรอบถัดไป
คะแนน -1 หมายถึงนักเรียนคนนั้นขาดสอบ ให้หาค่าเฉลี่ยของคนที่มาสอบ และนับคนที่ขาด
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
int total = 0, present = 0, absent = 0;
for (int i = 1; i <= n; i++) {
int score;
scanf("%d", &score);
if (score == -1) {
absent++;
continue;
}
total += score;
present++;
}
printf("absent = %d\n", absent);
printf("average = %.2f\n", (double)total / present);
return 0;
}
5
80 -1 75 -1 90
absent = 2
average = 81.67
continue ข้ามคำสั่งที่เหลือในรอบนี้ทั้งหมด แล้วไปเริ่มรอบถัดไป ใน for มันจะไปทำส่วนเปลี่ยนค่า i++ ก่อน แล้วค่อยตรวจเงื่อนไข เหมือนรอบนั้นจบตามปกติ
จริงๆ แล้วเขียนด้วย if else แทนก็ได้ทุกครั้ง continue แค่ช่วยให้โค้ดไม่ต้องซ้อนลึก อ่านแล้วเข้าใจทันทีว่า "ตัวนี้ไม่เอา ข้ามไป" ถ้าใช้แล้วงง ใช้ if else ไปเลยไม่ผิด
อ่านจนเจอค่าหยุด
บางโจทย์ไม่บอกว่ามีข้อมูลกี่ตัว แต่บอกว่า "ข้อมูลจบด้วย -1" หรือ "จบด้วย 0" ค่าที่บอกว่าหมดแล้วนี้เรียกว่า ค่าหยุด (sentinel) แปลตรงตัวว่ายามเฝ้าประตู มันไม่ใช่ข้อมูลจริง ห้ามเอาไปคำนวณ
#include <stdio.h>
int main()
{
int total = 0, count = 0;
while (1) {
int score;
scanf("%d", &score);
if (score == -1) {
break;
}
total += score;
count++;
}
if (count == 0) {
printf("no scores\n");
} else {
printf("count = %d\n", count);
printf("average = %.2f\n", (double)total / count);
}
return 0;
}
72 65 88 91 -1
count = 4
average = 79.00
while (1)คือเงื่อนไขที่จริงตลอด เพราะภาษา C ถือว่า 0 คือเท็จ และค่าอื่นที่ไม่ใช่ 0 คือจริง ลูปนี้จึงจะไม่จบเอง ทางออกเดียวคือbreak- ลำดับสำคัญมาก อ่าน ตรวจค่าหยุด แล้วค่อยใช้ ถ้าสลับเป็นใช้ก่อนตรวจ -1 จะหลุดเข้าไปในผลรวม ดูในส่วนลองทำให้พัง
- ถ้าข้อมูลมีแค่
-1ตัวเดียวcountเป็น 0 ถ้าหารเลยจะได้ค่าเฉลี่ยที่ไม่มีความหมาย จึงต้องแยกกรณีนี้ไว้ ชุดทดสอบที่ไม่มีข้อมูลเลยเป็นชุดขอบที่เจอบ่อย
อีกแบบที่เจอในหนังสือหลายเล่ม คืออ่านตัวแรกก่อนเข้าลูป แล้วอ่านตัวถัดไปที่ท้ายลูป ได้ผลเหมือนกัน
scanf("%d", &score);
while (score != -1) {
total += score;
count++;
scanf("%d", &score);
}
อ่านจนหมดไฟล์
โจทย์บางข้อไม่มีทั้งจำนวนข้อมูลและค่าหยุด บอกแค่ว่า "อ่านจนจบข้อมูลเข้า" ใน grader ข้อมูลเข้ามาจากไฟล์ เราจึงต้องรู้ว่าไฟล์หมดแล้วหรือยัง คำตอบอยู่ที่ค่าที่ scanf ส่งกลับมา ซึ่งเราไม่เคยสนใจมาก่อน
#include <stdio.h>
int main()
{
int score;
int result = scanf("%d", &score);
printf("result = %d\n", result);
return 0;
}
70
result = 1
scanf ส่งกลับมาว่าอ่านค่าใส่ตัวแปรสำเร็จกี่ตัว รันโปรแกรมนี้กับข้อมูลเข้าสามแบบได้ผลแบบนี้
| ข้อมูลเข้า | result | ความหมาย |
|---|---|---|
70 | 1 | อ่านได้ 1 ตัว |
abc | 0 | เจอข้อมูลแต่ไม่ใช่ตัวเลข อ่านไม่ได้สักตัว |
| ไม่มีอะไรเลย | -1 | ข้อมูลหมดแล้ว ค่านี้มีชื่อว่า EOF (end of file) |
ดังนั้น "อ่านจนหมด" เขียนได้ว่า ตราบใดที่ scanf อ่านได้ครบตามจำนวนที่ขอ ให้ทำต่อ
#include <stdio.h>
int main()
{
int score;
int total = 0, count = 0;
while (scanf("%d", &score) == 1) {
total += score;
count++;
}
printf("count = %d\n", count);
printf("total = %d\n", total);
return 0;
}
70 85 90
62 48
count = 5
total = 355
ทุกรอบ scanf อ่านตัวเลขหนึ่งตัวใส่ score แล้วส่งกลับ 1 เงื่อนไขจริง จึงทำตัวลูป พออ่านครบห้าตัวแล้วไม่มีอะไรเหลือ scanf ส่งกลับ -1 เงื่อนไขเป็นเท็จ ลูปจบ ถ้ารับทีละสองค่า เช่น scanf("%d %d", &a, &b) ก็เทียบกับ 2 แทน
จบข้อมูลเองในหน้าต่างดำ
ตอนกด F9 ทดลองใน Code::Blocks ข้อมูลมาจากคีย์บอร์ด ซึ่งไม่มีวันหมดเอง โปรแกรมจะรอเราพิมพ์ไปเรื่อยๆ ต้องบอก Windows ว่าข้อมูลจบแล้ว ทำแบบนี้
- พิมพ์ข้อมูลให้ครบ แล้วกด Enter ให้เคอร์เซอร์ขึ้นบรรทัดใหม่
- ที่ต้นบรรทัดใหม่ กด Ctrl+Z จะเห็น
^Zขึ้นมา - กด Enter อีกครั้ง
scanfจะได้ EOF แล้วโปรแกรมพิมพ์ผลลัพธ์
70 85 90
62 48
^Z
count = 5
total = 355
กด Ctrl+Z ที่ต้นบรรทัดใหม่เท่านั้น อย่ากดต่อท้ายตัวเลขในบรรทัดเดียวกัน ส่วนใน grader ไม่ต้องทำอะไร ไฟล์ข้อมูลเข้ามีจุดจบอยู่แล้ว
ลองทำให้พัง
แบบที่ 1 ไม่เทียบกับ 1
ในโปรแกรม until-eof ตัด == 1 ออก เหลือ while (scanf("%d", &score))
คอมไพล์ผ่านไม่มีคำเตือน แต่ป้อน 70 85 90 แล้วจบข้อมูล โปรแกรมไม่พิมพ์อะไรเลยและไม่จบ ต้องหยุดเองตามวิธีในหน้า 4.1 สาเหตุคือตอนข้อมูลหมด scanf ส่งกลับ -1 ซึ่งไม่ใช่ 0 จึงถือว่าจริง ลูปวนต่อไป scanf ก็ส่ง -1 กลับมาอีก วนอย่างนี้ไม่มีที่สิ้นสุด ใน grader ข้อนี้จะได้ Time Limit Exceeded ทุกชุด
บางคนแก้เป็น != EOF ซึ่งจบได้ตอนไฟล์หมดก็จริง แต่ถ้าข้อมูลมีตัวที่ไม่ใช่ตัวเลขปนมา scanf จะส่ง 0 ซ้ำๆ โดยไม่ขยับไปไหน แล้วค้างแบบเดิม เทียบ == 1 ปลอดภัยที่สุด
แบบที่ 2 continue ใน while
อยากพิมพ์ 1 ถึง 10 ยกเว้นเลขที่หารด้วย 3 ลงตัว ถ้าใช้ for ทำงานถูก
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) {
continue;
}
printf("%d\n", i);
}
return 0;
}
1
2
4
5
7
8
10
แต่ถ้าแปลงเป็น while ตรงๆ
int i = 1;
while (i <= 10) {
if (i % 3 == 0) {
continue;
}
printf("%d\n", i);
i++;
}
โปรแกรมพิมพ์ 1 กับ 2 แล้วนิ่งไม่จบ พอ i เป็น 3 continue กระโดดกลับไปตรวจเงื่อนไขทันที ข้าม i++ ที่อยู่ท้ายลูปไปด้วย i จึงเป็น 3 ตลอดกาล ใน for ไม่มีปัญหานี้เพราะ i++ อยู่ในวงเล็บของ for ซึ่ง continue ไม่ได้ข้าม
แบบที่ 3 นับค่าหยุดเป็นข้อมูล
เห็นว่าต้องอ่านอย่างน้อยหนึ่งครั้ง เลยเลือก do while แต่ใช้ค่าก่อนตรวจ
#include <stdio.h>
int main()
{
int score;
int total = 0, count = 0;
do {
scanf("%d", &score);
total += score;
count++;
} while (score != -1);
printf("count = %d\n", count);
printf("average = %.2f\n", (double)total / count);
return 0;
}
72 65 88 91 -1
count = 5
average = 63.00
-1 ถูกนับเป็นคะแนนคนที่ห้า ผลรวมจึงเป็น 315 หารด้วย 5 ได้ 63.00 แทนที่จะเป็น 79.00 ค่าหยุดต้องถูกตรวจก่อนนำไปใช้เสมอ
สรุป
breakออกจากลูปทันทีcontinueข้ามส่วนที่เหลือของรอบนี้ไปรอบถัดไป- ใช้ flag จำว่าเจอหรือยัง และระวังว่า
breakทิ้งข้อมูลที่ยังไม่ได้อ่าน continueในwhileข้ามการเปลี่ยนค่าตัวนับ อาจกลายเป็นลูปไม่รู้จบ- อ่านจนเจอค่าหยุด ใช้
while (1)อ่าน ตรวจ แล้วbreakก่อนนำไปใช้ - อ่านจนหมดไฟล์ ใช้
while (scanf("%d", &x) == 1)ต้องมี== 1 - ทดลองในหน้าต่างดำ จบข้อมูลด้วย Enter Ctrl+Z Enter