3.4 switch และ ? :
เมื่อต้องเอาตัวแปรตัวเดียวไปเทียบกับค่าตายตัวหลายค่า เช่น เลขวันเป็นชื่อวัน เลขเดือนเป็นไตรมาส ตัวอักษรเกรดเป็นแต้ม else if ทำได้ แต่ต้องเขียน day == ซ้ำทุกบรรทัด ภาษา C มีคำสั่ง switch ที่เขียนเรื่องแบบนี้ได้เป็นระเบียบกว่า ท้ายหน้ามีตัวดำเนินการ ? : สำหรับเลือกค่าหนึ่งในสองค่าให้จบในบรรทัดเดียว
คำสั่ง switch
#include <stdio.h>
int main()
{
int day;
scanf("%d", &day);
switch (day) { // (1)!
case 1: // (2)!
printf("Monday\n");
break; // (3)!
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
case 4:
printf("Thursday\n");
break;
case 5:
printf("Friday\n");
break;
case 6:
printf("Saturday\n");
break;
case 7:
printf("Sunday\n");
break;
default: // (4)!
printf("invalid\n");
}
return 0;
}
- ค่าในวงเล็บคือค่าที่จะเอาไปเทียบ ต้องเป็นจำนวนเต็มหรือ
char caseตามด้วยค่าคงที่หนึ่งค่า และเครื่องหมาย:ถ้าdayเท่ากับค่านี้ โปรแกรมจะกระโดดมาเริ่มทำตรงนี้breakคือออกจากswitchไปทำบรรทัดถัดจากปีกกาปิดของมันdefaultทำเมื่อไม่ตรงกับcaseไหนเลย เหมือนelseตัวท้าย จะมีหรือไม่มีก็ได้
3
Wednesday
โปรแกรมนี้ทำงานเหมือนสาย if (day == 1) ... else if (day == 2) ... ทุกอย่าง แต่อ่านง่ายกว่าเพราะเห็นค่าที่เทียบเรียงกันเป็นแถว ถ้าป้อน 9 จะได้ invalid ถ้าตัด default ออกแล้วป้อน 9 โปรแกรมจะไม่พิมพ์อะไรเลย ในโจทย์ที่รับประกันว่าข้อมูลเข้าถูกต้องเสมอ ไม่ใส่ default ก็ได้ แต่ใส่ไว้ช่วยจับตอนป้อนผิดระหว่างทดสอบ
ไม่มี break แล้วไหลต่อ
สิ่งที่ switch ต่างจาก else if มากที่สุด คือ case เป็นแค่ จุดเริ่มต้น ไม่ใช่ block ที่มีขอบ พอกระโดดมาที่ case ที่ตรงแล้ว โปรแกรมจะทำคำสั่งไล่ลงไปเรื่อยๆ ผ่าน case ตัวถัดไปด้วย จนกว่าจะเจอ break หรือถึงปีกกาปิดของ switch การไหลต่อแบบนี้เรียกว่า fall through
ส่วนใหญ่มันคือบั๊ก ดูแบบที่ 1 ในส่วนลองทำให้พัง แต่มีกรณีหนึ่งที่ใช้ประโยชน์ได้ คือให้หลายค่าทำสิ่งเดียวกัน โดยวาง case ติดกันไว้โดยไม่มีคำสั่งคั่น
#include <stdio.h>
int main()
{
int month;
scanf("%d", &month);
switch (month) {
case 1:
case 2:
case 3:
printf("Q1\n");
break;
case 4:
case 5:
case 6:
printf("Q2\n");
break;
case 7:
case 8:
case 9:
printf("Q3\n");
break;
case 10:
case 11:
case 12:
printf("Q4\n");
break;
default:
printf("invalid\n");
}
return 0;
}
8
Q3
เดือน 8 กระโดดมาที่ case 8: บรรทัดที่ 19 ซึ่งไม่มีคำสั่ง จึงไหลลงไป case 9: ซึ่งก็ไม่มีคำสั่งอีก แล้วไหลต่อถึง printf("Q3\n") และ break วิธีนี้ใช้บ่อยกับโจทย์จำนวนวันในแต่ละเดือน ที่เดือน 1 3 5 7 8 10 12 มี 31 วันเหมือนกัน
switch กับตัวอักษร
char คือตัวเลข จึงใช้กับ switch ได้ ใส่ตัวอักษรในเครื่องหมายคำพูดเดี่ยวหลัง case ตัวอย่างนี้แปลงเกรดเป็นแต้ม และรับได้ทั้งตัวพิมพ์เล็กและพิมพ์ใหญ่ด้วยการวาง case คู่กัน
#include <stdio.h>
int main()
{
char grade;
scanf(" %c", &grade);
switch (grade) {
case 'A':
case 'a':
printf("4\n");
break;
case 'B':
case 'b':
printf("3\n");
break;
case 'C':
case 'c':
printf("2\n");
break;
case 'D':
case 'd':
printf("1\n");
break;
case 'F':
case 'f':
printf("0\n");
break;
default:
printf("unknown\n");
}
return 0;
}
b
3
switch ทำอะไรไม่ได้
switch เทียบได้แค่ว่า เท่ากับค่าคงที่ไหม เท่านั้น จึงมีข้อจำกัดที่ต้องรู้
- เทียบช่วงไม่ได้ ตัดเกรดจากคะแนนด้วย
case score >= 80:ไม่ได้ ต้องใช้else ifแบบหน้า 3.2 - ใช้กับ
doubleไม่ได้ ค่าในวงเล็บต้องเป็นจำนวนเต็มหรือchar - ค่าหลัง
caseต้องเป็นค่าคงที่ เช่น5หรือ'A'ใส่ชื่อตัวแปรไม่ได้ - ค่าหลัง
caseห้ามซ้ำกัน
ถ้าลองฝ่าฝืน จะได้ error ตอนคอมไพล์ ดูได้ในส่วนลองทำให้พัง กฎง่ายๆ ในการเลือกคือ ค่าตายตัวไม่กี่ค่าใช้ switch ช่วงหรือเงื่อนไขซับซ้อนใช้ if
? : เลือกค่าแบบสั้น
บางครั้งเราแค่อยากได้ ค่า หนึ่งในสองค่าตามเงื่อนไข เช่น ค่าที่มากกว่าของสองตัว ถ้าใช้ if else ต้องเขียนห้าบรรทัด ตัวดำเนินการ ? : ทำได้ในนิพจน์เดียว
เงื่อนไข ? ค่าเมื่อจริง : ค่าเมื่อเท็จ
#include <stdio.h>
int main()
{
int a, b;
scanf("%d %d", &a, &b);
int larger = a > b ? a : b;
printf("larger = %d\n", larger);
return 0;
}
14 9
larger = 14
อ่านบรรทัดที่ 7 ว่า "a มากกว่า b ไหม ถ้าใช่เอา a ถ้าไม่ใช่เอา b" ได้ผลเหมือนเขียน if (a > b) { larger = a; } else { larger = b; } ตัวดำเนินการนี้มีสามส่วน จึงมักเรียกว่า ternary operator
เพราะ ? : ให้ผลเป็นค่า จึงวางไว้ใน printf ได้เลย ใช้บ่อยกับการเลือกคำให้ถูกกับจำนวน
#include <stdio.h>
int main()
{
int count;
scanf("%d", &count);
printf("%d %s\n", count, count == 1 ? "item" : "items");
return 0;
}
1
1 item
ป้อน 3 จะได้ 3 items ใช้ ? : เฉพาะตอนที่เลือกค่าง่ายๆ แบบนี้ ถ้าต้องทำหลายคำสั่ง หรือมีมากกว่าสองทาง ให้กลับไปใช้ if การเอา ? : มาซ้อนกันหลายชั้นเขียนได้ แต่อ่านยากมาก
ลองทำให้พัง
แบบที่ 1 ลืม break
โปรแกรมชื่อวันที่ตัดเหลือสามวัน และลืม break ของสอง case แรก
#include <stdio.h>
int main()
{
int day;
scanf("%d", &day);
switch (day) {
case 1:
printf("Monday\n");
case 2:
printf("Tuesday\n");
case 3:
printf("Wednesday\n");
break;
default:
printf("other\n");
}
return 0;
}
1
Monday
Tuesday
Wednesday
ป้อน 1 ได้สามวัน เพราะเริ่มที่ case 1: แล้วไหลผ่าน case 2: กับ case 3: ไปจนเจอ break บรรทัดที่ 14 ป้อน 2 จะได้ Tuesday กับ Wednesday ส่วนป้อน 3 ได้ถูกต้องเพราะมี break พอดี
ที่น่ากลัวคือ -Wall ไม่เตือนเรื่องนี้เลย ถ้าเปิดคำเตือนเพิ่มด้วย -Wextra (ในหน้า Compiler Flags เดียวกับที่ติ๊ก -Wall ไว้ในบทที่ 0 มีช่อง Enable extra compiler warnings [-Wextra]) จะได้
day.c:9:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
9 | printf("Monday\n");
| ^~~~~~~~~~~~~~~~~~
day.c:10:9: note: here
10 | case 2:
| ^~~~
day.c:11:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
11 | printf("Tuesday\n");
| ^~~~~~~~~~~~~~~~~~~
day.c:12:9: note: here
12 | case 3:
| ^~~~
คำเตือนนี้ไม่ขึ้นกับ case ที่วางติดกันโดยไม่มีคำสั่งคั่นแบบโปรแกรม quarter เพราะแบบนั้นชัดเจนว่าตั้งใจ นิสัยที่ช่วยได้มากกว่าคำเตือน คือเขียน case แล้วพิมพ์ break; ปิดไว้ก่อนทันที ค่อยเติมคำสั่งตรงกลาง
แบบที่ 2 ใส่ช่วงหลัง case
switch (score) {
case score >= 80:
printf("A\n");
break;
grade.c:8:9: error: case label does not reduce to an integer constant
8 | case score >= 80:
| ^~~~
case label คือค่าหลัง case ข้อความบอกว่ามันไม่ได้เป็นค่าคงที่จำนวนเต็ม เพราะมีตัวแปร score อยู่ข้างใน ค่าของมันรู้ตอนรันเท่านั้น switch ต้องรู้ทุกค่าตั้งแต่ตอนคอมไพล์ งานแบ่งช่วงให้ใช้ else if
แบบที่ 3 switch กับ double
double height;
scanf("%lf", &height);
switch (height) {
case 1.5:
height.c:7:13: error: switch quantity not an integer
7 | switch (height) {
| ^~~~~~
height.c:8:9: error: case label does not reduce to an integer constant
8 | case 1.5:
| ^~~~
ได้สอง error ตัวแรกบอกว่าค่าที่เอามา switch ไม่ใช่จำนวนเต็ม ตัวที่สองบอกว่า 1.5 ไม่ใช่ค่าคงที่จำนวนเต็ม ทศนิยมเทียบเท่ากันตรงๆ ไม่ได้อยู่แล้วตามหน้า 3.3 ภาษา C จึงไม่ยอมตั้งแต่ต้น
แบบที่ 4 case ซ้ำ
ในโปรแกรม quarter พิมพ์ case 4: ผิดเป็น case 3:
quarter.c:13:9: error: duplicate case value
13 | case 3:
| ^~~~
quarter.c:10:9: note: previously used here
10 | case 3:
| ^~~~
ค่าเดียวกันอยู่สองที่ C ไม่รู้ว่าจะกระโดดไปที่ไหน จึงไม่ยอมคอมไพล์ และบอกตำแหน่งให้ทั้งสองจุด บรรทัด note คือข้อมูลเสริมของ error ข้างบน ชี้ไปที่ตัวแรกที่ใช้ค่านี้ไปแล้ว
สรุป
switchเทียบตัวแปรหนึ่งตัวกับค่าคงที่หลายค่า ใช้ได้กับจำนวนเต็มและchar- ทุก
caseต้องจบด้วยbreakไม่อย่างนั้นจะไหลต่อไปcaseถัดไป และ-Wallไม่เตือน - วาง
caseติดกันโดยไม่มีคำสั่งคั่น เพื่อให้หลายค่าทำสิ่งเดียวกัน defaultทำเมื่อไม่ตรงกับcaseไหน เหมือนelseตัวท้าย- ช่วงตัวเลข ทศนิยม และเงื่อนไขที่มี
&&||ใช้if เงื่อนไข ? ก : ขให้ค่า ก เมื่อจริง ข เมื่อเท็จ ใช้กับการเลือกค่าง่ายๆ เท่านั้น
กลับไปเช็กรายการ ผ่านบทนี้เมื่อ แล้วไปทำแบบฝึกหัด