[0]zero-index clytie0x/zero-index

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;
}
  1. เงื่อนไขต้องอยู่ในวงเล็บเสมอ และ ไม่มี ; หลังวงเล็บปิด เพราะ if ยังไม่จบ ต้องมีคำสั่งตามมา
  2. คำสั่งในปีกกาเยื้องเข้าไป 4 ช่อง ให้เห็นว่าอยู่ใต้ if compiler ไม่สนการเยื้อง แต่คนอ่านสน
  3. บรรทัดนี้อยู่นอกปีกกา ทำทุกครั้งไม่ว่าเงื่อนไขจะเป็นอะไร
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 โปรแกรมจะเดินทางใดทางหนึ่งเท่านั้น ไม่มีทางทำทั้งสองก้อน และไม่มีทางข้ามทั้งสองก้อน

score >= 50 pass fail next line จริง เท็จ

ตัวอย่างที่ใช้บ่อยที่สุด คือบอกว่าเป็นเลขคู่หรือเลขคี่ เลขคู่คือเลขที่หารด้วย 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 ด้วยค่าที่ทำให้จริง ค่าที่ทำให้เท็จ และค่าตรงขอบ