3.1 การเปรียบเทียบและ if
ทุกโปรแกรมในบทที่ 1 และ 2 ทำงานครบทุกบรรทัดจากบนลงล่าง ข้อมูลเข้าเป็นอะไรก็เดินทางเดิม แต่โจทย์ส่วนใหญ่ต้องการคำตอบที่ต่างกันตามข้อมูล เช่น คะแนนถึง 50 ให้พิมพ์ pass ไม่ถึงให้พิมพ์ fail หน้านี้มีสองเรื่องที่ทำให้โปรแกรมเลือกทางได้ คือการ เปรียบเทียบ ค่าสองค่า และคำสั่ง if ที่ใช้ผลเปรียบเทียบนั้นตัดสินว่าจะทำคำสั่งไหน
เปรียบเทียบสองค่า
ภาษา C มีตัวดำเนินการเปรียบเทียบ 6 ตัว
| ตัวดำเนินการ | ความหมาย | ตัวอย่างที่เป็นจริง |
|---|---|---|
== | เท่ากับ ใช้เครื่องหมายเท่ากับสองตัว | 5 == 5 |
!= | ไม่เท่ากับ | 5 != 3 |
< | น้อยกว่า | 3 < 5 |
> | มากกว่า | 5 > 3 |
<= | น้อยกว่าหรือเท่ากับ | 5 <= 5 |
>= | มากกว่าหรือเท่ากับ | 5 >= 5 |
ตัวที่มีสองเครื่องหมาย เขียนติดกันห้ามเว้นวรรค และ = อยู่หลังเสมอ อ่านตามลำดับที่เขียนว่า "น้อยกว่า หรือเท่ากับ" จะไม่สลับกัน
ผลของการเปรียบเทียบคือตัวเลข ลองพิมพ์ออกมาดู
#include <stdio.h>
int main()
{
int score = 72;
printf("%d\n", score > 50);
printf("%d\n", score < 50);
printf("%d\n", score >= 72);
printf("%d\n", score <= 71);
printf("%d\n", score == 72);
printf("%d\n", score != 72);
return 0;
}
1
0
1
0
1
0
ในภาษา C จริงคือ 1 เท็จคือ 0 ผลของการเปรียบเทียบเป็น int ธรรมดา จึงพิมพ์ด้วย %d ได้ และเอาไปบวกลบต่อได้ด้วย ภาษา C++ มีชนิด bool ที่เก็บจริงเท็จโดยเฉพาะ ซึ่งจะเจอในบทที่ 15 ระหว่างนี้เราใช้ int ไปก่อน
= ตัวเดียว กับ == สองตัว
= ตัวเดียวคือ กำหนดค่า เอาค่าทางขวาใส่กล่องทางซ้าย ตามหน้า 1.1 ส่วน == สองตัวคือ ถามว่าเท่ากันไหม ไม่เปลี่ยนค่าอะไรเลย สองตัวนี้สลับกันแล้วยังคอมไพล์ผ่าน แต่ได้ผลผิด ดูได้ในส่วนลองทำให้พัง
คำสั่ง if
if แปลว่า "ถ้า" รูปแบบคือ
if (เงื่อนไข) {
คำสั่งที่ทำเมื่อเงื่อนไขเป็นจริง
}
ถ้าเงื่อนไขเป็นจริง ทำคำสั่งในปีกกา ถ้าเป็นเท็จ ข้ามปีกกาทั้งก้อนไปเลย แล้วทำบรรทัดถัดจากปีกกาปิดต่อ
#include <stdio.h>
int main()
{
int score;
scanf("%d", &score);
if (score >= 50) { // (1)!
printf("pass\n"); // (2)!
}
printf("done\n"); // (3)!
return 0;
}
- เงื่อนไขต้องอยู่ในวงเล็บเสมอ และ ไม่มี
;หลังวงเล็บปิด เพราะifยังไม่จบ ต้องมีคำสั่งตามมา - คำสั่งในปีกกาเยื้องเข้าไป 4 ช่อง ให้เห็นว่าอยู่ใต้
ifcompiler ไม่สนการเยื้อง แต่คนอ่านสน - บรรทัดนี้อยู่นอกปีกกา ทำทุกครั้งไม่ว่าเงื่อนไขจะเป็นอะไร
72
pass
done
รันใหม่แล้วป้อน 35 เงื่อนไข 35 >= 50 เป็นเท็จ บรรทัดที่ 8 ถูกข้าม จะได้แค่ done บรรทัดเดียว ทุกครั้งที่เขียน if ให้ลองป้อนค่าที่ทำให้เงื่อนไขจริงหนึ่งครั้ง และเท็จหนึ่งครั้ง และอย่าลืมค่าที่อยู่ตรงขอบพอดี ในที่นี้คือ 50 ซึ่งต้องได้ pass เพราะใช้ >=
หลายคำสั่งในปีกกาเดียว
ของที่อยู่ในปีกกาเรียกว่า block (ก้อนคำสั่ง) ใส่กี่คำสั่งก็ได้ ทั้งหมดจะทำหรือไม่ทำไปพร้อมกัน ร้านค้าแห่งหนึ่งลดให้ 100 บาทเมื่อซื้อครบ 1000 บาท
#include <stdio.h>
int main()
{
int price;
scanf("%d", &price);
if (price >= 1000) {
printf("discount 100\n");
price = price - 100;
}
printf("pay %d\n", price);
return 0;
}
1250
discount 100
pay 1150
ถ้าป้อน 800 ทั้งสองบรรทัดในปีกกาถูกข้าม ได้ pay 800 ราคาไม่ถูกลด
ถ้า block มีคำสั่งเดียว ภาษา C ยอมให้ไม่ใส่ปีกกา เช่น if (score >= 50) printf("pass\n"); แต่เอกสารนี้ ใส่ปีกกาทุกครั้ง เพราะวันหนึ่งที่เพิ่มคำสั่งที่สองเข้าไป คำสั่งนั้นจะไม่อยู่ใต้ if อย่างที่ตาเห็น ดูแบบที่ 3 ในส่วนลองทำให้พัง
if else ทางเลือกสองทาง
บ่อยครั้งเราต้องการให้ทำอย่างหนึ่งเมื่อจริง และอีกอย่างหนึ่งเมื่อเท็จ ใช้ else (มิฉะนั้น) ต่อท้าย if โปรแกรมจะเดินทางใดทางหนึ่งเท่านั้น ไม่มีทางทำทั้งสองก้อน และไม่มีทางข้ามทั้งสองก้อน
ตัวอย่างที่ใช้บ่อยที่สุด คือบอกว่าเป็นเลขคู่หรือเลขคี่ เลขคู่คือเลขที่หารด้วย 2 ลงตัว หรือ n % 2 == 0 ตามที่เห็นในหน้า 1.2
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
if (n % 2 == 0) {
printf("even\n");
} else {
printf("odd\n");
}
return 0;
}
17
odd
else ไม่มีเงื่อนไขของตัวเอง มันคือ "กรณีที่เหลือทั้งหมด" ตำแหน่งของ else ต้องต่อจากปีกกาปิดของ if ทันที เอกสารนี้เขียน } else { ไว้บรรทัดเดียวกัน
ตัวอย่าง ส่วนต่างที่ไม่ติดลบ
น้องสองคนสูง 150 กับ 163 เซนติเมตร สูงต่างกันกี่เซนติเมตร ถ้าเอาคนแรกลบคนที่สองจะได้ -13 แต่ส่วนต่างควรเป็น 13 ไม่ว่าจะป้อนใครก่อน วิธีคือลบไปก่อน แล้วถ้าผลติดลบ ค่อยกลับเครื่องหมาย ค่าที่ได้เรียกว่า ค่าสัมบูรณ์ (absolute value)
#include <stdio.h>
int main()
{
int first, second;
scanf("%d %d", &first, &second);
int diff = first - second;
if (diff < 0) {
diff = -diff;
}
printf("%d\n", diff);
return 0;
}
150 163
13
ตัวอย่างนี้ใช้ if ที่ไม่มี else เพื่อ แก้ค่าตัวแปรเฉพาะบางกรณี ถ้าผลไม่ติดลบก็ปล่อยไว้อย่างนั้น รูปแบบนี้จะเจออีกในหน้า 3.2 ตอนหาค่ามากสุด
เปรียบเทียบตัวอักษร
จากหน้า 1.4 char คือตัวเลขรหัส ASCII จึงเปรียบเทียบด้วยตัวดำเนินการชุดเดียวกันได้เลย
#include <stdio.h>
int main()
{
char answer;
scanf(" %c", &answer);
if (answer == 'y') {
printf("start\n");
} else {
printf("stop\n");
}
return 0;
}
y
start
- ตัวอักษรต้องอยู่ในเครื่องหมายคำพูดเดี่ยว
'y'เสมอ ถ้าเขียน"y"จะเป็นข้อความ เทียบกับcharไม่ได้ compiler จะเตือนว่าcomparison between pointer and integer(pointer จะได้เรียนในบทที่ 10) - ตัวพิมพ์ใหญ่กับพิมพ์เล็กมีรหัสต่างกัน
'Y'คือ 89 ส่วน'y'คือ 121 ป้อนYโปรแกรมนี้จึงตอบstopวิธีรับได้ทั้งสองแบบอยู่ในหน้า 3.3 <กับ>ใช้กับตัวอักษรได้ด้วย'a' < 'b'เป็นจริงเพราะ 97 น้อยกว่า 98 เรื่องนี้ใช้ตรวจว่าเป็นตัวพิมพ์ใหญ่หรือไม่ในหน้า 3.3
ค่าอะไรนับว่าจริง
if ไม่ได้รับแค่ผลเปรียบเทียบ รับตัวเลขอะไรก็ได้ กฎคือ 0 เป็นเท็จ ค่าอื่นทุกค่าเป็นจริง รวมถึงเลขติดลบด้วย
#include <stdio.h>
int main()
{
int stock;
scanf("%d", &stock);
if (stock) {
printf("in stock\n");
} else {
printf("sold out\n");
}
return 0;
}
0
sold out
if (stock) ทำงานเหมือน if (stock != 0) ทุกอย่าง จะเห็นแบบสั้นนี้ในโค้ดของคนอื่นบ่อย แต่ตอนเริ่มเรียนให้เขียน != 0 ให้ชัดไปเลย กฎข้อนี้สำคัญเพราะเป็นต้นเหตุของกับดักแบบที่ 1 ข้างล่าง
ลองทำให้พัง
ทำทีละแบบ อ่านข้อความที่ได้ แล้วแก้กลับให้ถูกก่อนลองแบบถัดไป
แบบที่ 1 ใช้ = แทน ==
อยากพิมพ์ perfect เมื่อได้ 100 คะแนน แต่พิมพ์ = ไปตัวเดียว
#include <stdio.h>
int main()
{
int score;
scanf("%d", &score);
if (score = 100) {
printf("perfect\n");
}
printf("score = %d\n", score);
return 0;
}
perfect.c:7:9: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
7 | if (score = 100) {
| ^~~~~
75
perfect
score = 100
ได้ 75 แต่โปรแกรมบอกว่า perfect แถมคะแนนกลายเป็น 100 เพราะ score = 100 ไม่ได้ถาม แต่ เอา 100 ใส่ลง score แล้วค่าของทั้งนิพจน์คือ 100 ซึ่งไม่ใช่ 0 จึงนับว่าจริงทุกครั้ง ไม่ว่าจะป้อนอะไร
คำเตือนแนะว่าให้ใส่วงเล็บเพิ่ม ซึ่งเป็นทางออกสำหรับคนที่ตั้งใจกำหนดค่าใน if จริงๆ แต่กรณีของเราไม่ได้ตั้งใจ วิธีแก้ที่ถูกคือเปลี่ยนเป็น == เห็นคำเตือนนี้เมื่อไร ให้ไปดูตำแหน่งที่ ^ ชี้ก่อนเลย
แบบที่ 2 ใส่ ; หลัง if
ในโปรแกรม pass เผลอพิมพ์ ; ต่อท้ายวงเล็บเงื่อนไข
#include <stdio.h>
int main()
{
int score;
scanf("%d", &score);
if (score >= 50); {
printf("pass\n");
}
printf("done\n");
return 0;
}
pass.c:7:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
7 | if (score >= 50); {
| ^~
pass.c:7:23: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
7 | if (score >= 50); {
| ^
30
pass
done
ได้ 30 ก็ยังผ่าน ; ตัวเดียวนับเป็นหนึ่งคำสั่งที่ไม่ทำอะไร if จึงคุมแค่คำสั่งว่างนั้น ส่วนปีกกาที่ตามมากลายเป็น block ลอยๆ ที่ทำทุกครั้ง คำเตือนบอกว่า if นี้ "does not guard" คือไม่ได้คุมคำสั่งที่ ^ ตัวที่สองชี้ แม้ตำแหน่งจะทำให้ดูเหมือนคุมอยู่
แบบที่ 3 ลืมปีกกา
เขียน if แบบไม่มีปีกกาไว้ แล้วมาเพิ่มคำสั่งที่สองทีหลัง
#include <stdio.h>
int main()
{
int score;
scanf("%d", &score);
if (score >= 50)
printf("pass\n");
printf("well done\n");
printf("done\n");
return 0;
}
pass.c:7:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
7 | if (score >= 50)
| ^~
pass.c:9:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
9 | printf("well done\n");
| ^~~~~~
30
well done
done
ไม่มีปีกกา if คุมได้แค่ คำสั่งเดียวถัดจากมัน คือบรรทัดที่ 8 บรรทัดที่ 9 เยื้องเข้าไปก็จริง แต่ compiler ไม่ดูการเยื้อง มันจึงทำทุกครั้ง ได้ 30 คะแนนแล้วยังได้คำชม
ถ้าในสภาพนี้เติม else ต่อท้าย จะคอมไพล์ไม่ผ่านเลย
if (score >= 50)
printf("pass\n");
printf("well done\n");
else
printf("fail\n");
pass.c:7:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
7 | if (score >= 50)
| ^~
pass.c:9:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
9 | printf("well done\n");
| ^~~~~~
pass.c:10:5: error: 'else' without a previous 'if'
10 | else
| ^~~~
if จบไปตั้งแต่บรรทัดที่ 8 แล้ว บรรทัดที่ 9 เป็นคำสั่งธรรมดา else ในบรรทัดที่ 10 จึงหา if ของตัวเองไม่เจอ แก้ทั้งสองแบบด้วยวิธีเดียว คือใส่ปีกกาทุกครั้ง
แบบที่ 4 ตรวจเลขคี่ด้วย == 1
เลขคี่หาร 2 เหลือเศษ 1 จึงน่าจะเขียนแบบนี้ได้
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
if (n % 2 == 1) {
printf("odd\n");
} else {
printf("even\n");
}
return 0;
}
-7
even
-7 เป็นเลขคี่ แต่โปรแกรมบอกว่าคู่ จำจากหน้า 1.2 ได้ไหมว่าเศษจาก % มีเครื่องหมายตามตัวตั้ง -7 % 2 จึงได้ -1 ไม่ใช่ 1 ไม่มีคำเตือนอะไรเลย ชุดทดสอบที่มีแต่เลขบวกก็ผ่านหมด ทางที่ปลอดภัยคือเทียบกับ 0 เสมอ n % 2 == 0 เป็นคู่ n % 2 != 0 เป็นคี่ ใช้ได้ทั้งเลขบวกและลบ
แบบที่ 5 เขียน =< สลับกัน
ในโปรแกรม pass เขียน score =< 50
pass.c:7:16: error: expected expression before '<' token
7 | if (score =< 50) {
| ^
compiler อ่าน = เป็นการกำหนดค่า แล้วเจอ < ตามมาโดยไม่มีค่าอยู่ข้างหน้า ข้อความไม่ได้บอกว่าสลับเครื่องหมาย แต่ ^ ชี้ตรงจุดนั้นพอดี จำแค่ว่า = อยู่หลังเสมอ
สรุป
- เปรียบเทียบด้วย
== != < > <= >=ผลเป็นintจริงได้ 1 เท็จได้ 0 if (เงื่อนไข) { ... }ทำ block เมื่อเงื่อนไขจริง ถ้ามีelseจะทำ block ใด block หนึ่งเสมอ- ใน
ifค่า 0 คือเท็จ ค่าอื่นทุกค่าคือจริง =คือกำหนดค่า==คือถามว่าเท่ากันไหม สลับกันแล้วได้แค่คำเตือน- ห้ามมี
;หลังวงเล็บเงื่อนไข และใส่ปีกกาทุกครั้ง - ตรวจคู่คี่ด้วย
n % 2 == 0หรือ!= 0ไม่ใช่== 1 - ทดสอบทุก
ifด้วยค่าที่ทำให้จริง ค่าที่ทำให้เท็จ และค่าตรงขอบ