[0]zero-index clytie0x/zero-index

6.1 ตัวอักษรคือตัวเลข

ในหน้า 1.4 น้องเห็นแล้วว่า char เก็บรหัส ASCII ของตัวอักษร 'B' + 1 ได้ 'C' และ '7' - '0' ได้ 7 หน้านี้เอาความจริงข้อเดียวนี้ไปทำงานจริง คือดูว่าตัวอักษรเป็นชนิดไหน เปลี่ยนตัวพิมพ์ใหญ่เล็ก แปลงตัวอักษรตัวเลขเป็นค่าจริง และเลื่อนตัวอักษรแบบวนรอบ ทั้งหมดนี้คือชิ้นส่วนที่โจทย์สตริงทุกข้อในหน้าต่อๆ ไปต้องใช้

char เป็นตัวเลขขนาดเล็ก

char คือจำนวนเต็มขนาด 1 byte ที่ printf แสดงเป็นตัวอักษรได้เมื่อใช้ %c เพราะเป็นจำนวนเต็ม จึงบวก ลบ เปรียบเทียบ และใช้เป็นตัวนับในลูปได้เหมือน int

#include <stdio.h>

int main()
{
    for (char letter = 'a'; letter <= 'z'; letter++) {
        printf("%c", letter);
    }
    printf("\n");
    for (char letter = 'Z'; letter >= 'A'; letter--) {
        printf("%c", letter);
    }
    printf("\n");
    return 0;
}
abcdefghijklmnopqrstuvwxyz
ZYXWVUTSRQPONMLKJIHGFEDCBA

letter++ คือบวกรหัสทีละหนึ่ง ได้ตัวถัดไปเรื่อยๆ ลูปนี้ทำงานได้เพราะตาราง ASCII เรียงตัวอักษรภาษาอังกฤษติดกันครบ 26 ตัว ทั้งชุดตัวพิมพ์เล็กและชุดตัวพิมพ์ใหญ่

ตารางในหน้า 1.4 มีสามช่วง หน้านี้เพิ่มรหัสที่จะเจอบ่อยในบทนี้อีกสองตัว

ตัวอักษรรหัสทำไมต้องรู้
' ' ช่องว่าง32คั่นคำในประโยค ใช้ตอนนับคำ
'\n' ขึ้นบรรทัดใหม่10ติดมากับข้อความที่รับทั้งบรรทัด จะเจอในหน้า 6.3
'0' ถึง '9'48 ถึง 57ตัวเลขในรูปตัวอักษร
'A' ถึง 'Z'65 ถึง 90ตัวพิมพ์ใหญ่มาก่อน
'a' ถึง 'z'97 ถึง 122ตัวพิมพ์เล็กห่างจากตัวพิมพ์ใหญ่ 32 เสมอ

สังเกตว่าระหว่าง 'Z' (90) กับ 'a' (97) มีช่องว่างอยู่ 6 รหัส เป็นเครื่องหมาย [ \ ] ^ _ ` เรื่องนี้จะกลับมากัดในส่วนลองทำให้พัง

ดูว่าเป็นตัวอักษรชนิดไหน

หน้า 3.3 แยกตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก และตัวเลขด้วยเงื่อนไขช่วงอย่าง c >= 'a' && c <= 'z' ไปแล้ว ตอนนี้รู้เหตุผลแล้วว่าทำไมใช้ได้ คือรหัสของ 'a' ถึง 'z' เรียงติดกัน ตัวพิมพ์ใหญ่กับตัวเลขก็เช่นกัน โปรแกรมนี้ใช้เงื่อนไขชุดเดิม แต่อ่านทีละตัวไปจนหมดข้อมูล

#include <stdio.h>

int main()
{
    char c;
    while (scanf(" %c", &c) == 1) {
        if (c >= 'a' && c <= 'z') {
            printf("%c lowercase\n", c);
        } else if (c >= 'A' && c <= 'Z') {
            printf("%c uppercase\n", c);
        } else if (c >= '0' && c <= '9') {
            printf("%c digit\n", c);
        } else {
            printf("%c other\n", c);
        }
    }
    return 0;
}
q 7 M ?
q lowercase
7 digit
M uppercase
? other

" %c" ที่มีช่องว่างข้างหน้าข้ามช่องว่างและการขึ้นบรรทัดให้ตามที่เรียนในบทที่ 2 จึงได้ทีละตัวที่ไม่ใช่ช่องว่าง ในโค้ดเขียน 'a' กับ 'z' ตรงๆ ไม่ต้องจำว่าเป็น 97 กับ 122

เปลี่ยนตัวพิมพ์ใหญ่เล็กเอง

ถ้า c เป็นตัวพิมพ์เล็ก c - 'a' คือลำดับของมันในตัวอักษร นับจาก 0 เช่น 'a' ได้ 0 'g' ได้ 6 พอรู้ลำดับแล้ว เอาไปบวก 'A' ก็ได้ตัวพิมพ์ใหญ่ที่ลำดับเดียวกัน

นิพจน์เมื่อ c คือ 'g'ความหมาย
c - 'a'6ลำดับนับจาก 0 ใช้เป็นเลขช่องของอาเรย์ได้
c - 'a' + 17ลำดับแบบที่คนนับ g เป็นตัวที่ 7
c - 'a' + 'A''G'ตัวพิมพ์ใหญ่ที่ลำดับเดียวกัน

โปรแกรมนี้สลับตัวพิมพ์ใหญ่เป็นเล็กและเล็กเป็นใหญ่ ตัวอื่นพิมพ์ตามเดิม

#include <stdio.h>

int main()
{
    char c;
    while (scanf("%c", &c) == 1 && c != '\n') { // (1)!
        if (c >= 'a' && c <= 'z') {
            c = c - 'a' + 'A';
        } else if (c >= 'A' && c <= 'Z') {
            c = c - 'A' + 'a';
        }
        printf("%c", c);
    }
    printf("\n");
    return 0;
}
  1. "%c" ที่ไม่มีช่องว่างข้างหน้า อ่านทุกตัวรวมทั้งช่องว่างและ '\n' ลูปจึงหยุดเองเมื่อเจอ '\n' ซึ่งก็คือจบบรรทัด รูปแบบนี้ใช้อ่านทั้งบรรทัดทีละตัวได้ทุกครั้ง
Hello World 2026
hELLO wORLD 2026

บางคนเขียน c - 32 แทน c - 'a' + 'A' ได้ผลเท่ากันเพราะ 'a' - 'A' คือ 32 แต่คนอ่านต้องหยุดคิดว่า 32 มาจากไหน เขียนด้วยตัวอักษรอ่านง่ายกว่าและไม่ต้องจำเลข

ตัวอักษรตัวเลขกับค่าจริง

'7' - '0' ได้ 7 ทำให้อ่านตัวเลขทีละหลักแล้วประกอบเป็นค่าเองได้ วิธีคือทุกครั้งที่ได้หลักใหม่ ให้คูณค่าเดิมด้วย 10 แล้วบวกหลักใหม่ต่อท้าย

#include <stdio.h>

int main()
{
    char c;
    int year = 0;
    while (scanf("%c", &c) == 1 && c != '\n') {
        year = year * 10 + (c - '0');
    }
    printf("%d\n", year + 1);
    return 0;
}
2569
2570
อ่านได้c - '0'year หลังบรรทัดที่ 8
'2'20 × 10 + 2 = 2
'5'52 × 10 + 5 = 25
'6'625 × 10 + 6 = 256
'9'9256 × 10 + 9 = 2569

บวก 1 แล้วได้ 2570 แปลว่า year เป็นตัวเลขจริง ไม่ใช่ตัวอักษรสี่ตัว scanf("%d") ข้างในก็ทำแบบนี้ แต่การรู้วิธีเองมีประโยชน์ตอนโจทย์ให้ตัวเลขปนกับตัวอักษร เช่น a12b3 ซึ่ง %d อ่านตรงๆ ไม่ได้

ทางกลับก็ใช้หลักเดียวกัน ค่า 0 ถึง 9 บวก '0' ได้ตัวอักษรตัวเลข

#include <stdio.h>

int main()
{
    int digit = 7;
    char c = '0' + digit;
    printf("%c %d\n", c, c);
    return 0;
}
7 55

เลื่อนตัวอักษรแบบวนรอบ

โจทย์รหัสลับชอบให้เลื่อนตัวอักษรไปข้างหน้า k ตัว โดยเลยจาก z แล้ววนกลับมา a ถ้าบวกตรงๆ จะเลยออกไปนอกตัวอักษร ต้องแปลงเป็นลำดับ 0 ถึง 25 ก่อน บวก k แล้วใช้ % 26 ให้วนกลับ แล้วค่อยแปลงกลับเป็นตัวอักษร

#include <stdio.h>

int main()
{
    char letter;
    int steps;
    scanf(" %c %d", &letter, &steps);
    printf("%c\n", letter + steps);
    int position = letter - 'a';
    position = (position + steps) % 26;
    printf("%c\n", 'a' + position);
    return 0;
}
x 5
}
c

บรรทัดแรกบวกตรงๆ 'x' คือ 120 บวก 5 ได้ 125 ซึ่งคือ } ส่วนบรรทัดที่สอง x อยู่ลำดับ 23 บวก 5 ได้ 28 เอา 28 mod 26 ได้ 2 คือ c ถูกต้องตามที่ต้องการ y z a b c

ctype.h ทำให้ไม่ต้องเขียนช่วงเอง

การเช็กช่วงและเปลี่ยนตัวพิมพ์ใช้บ่อยจนภาษา C มีให้ใน ctype.h แล้ว ใส่ #include <ctype.h> ไว้บนโปรแกรมก็ใช้ได้

เรียกได้อะไรเขียนเองได้ว่า
isdigit(c)จริงถ้าเป็นตัวเลขc >= '0' && c <= '9'
isalpha(c)จริงถ้าเป็นตัวอักษรภาษาอังกฤษ ใหญ่หรือเล็กตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่
isupper(c)จริงถ้าเป็นตัวพิมพ์ใหญ่c >= 'A' && c <= 'Z'
islower(c)จริงถ้าเป็นตัวพิมพ์เล็กc >= 'a' && c <= 'z'
isspace(c)จริงถ้าเป็นช่องว่าง '\n' หรือตัวเว้นวรรคอื่นหลายตัว เขียนเองยาว
toupper(c)ตัวพิมพ์ใหญ่ของ c ถ้า c ไม่ใช่ตัวพิมพ์เล็กได้ c คืนมาเหมือนเดิมหัวข้อที่แล้ว
tolower(c)ตัวพิมพ์เล็กของ c ถ้า c ไม่ใช่ตัวพิมพ์ใหญ่ได้ c คืนมาเหมือนเดิมหัวข้อที่แล้ว
#include <stdio.h>
#include <ctype.h>

int main()
{
    char c;
    int letters = 0, digits = 0;
    while (scanf("%c", &c) == 1 && c != '\n') {
        if (isalpha(c)) {
            letters++;
        } else if (isdigit(c)) {
            digits++;
        }
        printf("%c", toupper(c));
    }
    printf("\n%d letters %d digits\n", letters, digits);
    return 0;
}
Room 4B, floor 12
ROOM 4B, FLOOR 12
10 letters 3 digits
  • toupper ไม่แตะตัวที่ไม่ใช่ตัวพิมพ์เล็ก ช่องว่าง จุลภาค และตัวเลขจึงออกมาเหมือนเดิม ไม่ต้องเช็กก่อนเรียก
  • is... ทุกตัวใช้ใน if ได้ตรงๆ แต่อย่าเอาไปเทียบกับ 1 เหตุผลอยู่ในส่วนลองทำให้พัง

เขียนเองกับใช้ ctype.h ได้ผลเท่ากัน ใช้แบบไหนก็ได้ แต่ ต้องเข้าใจแบบเขียนเองก่อน เพราะ ctype.h ไม่มีฟังก์ชันหาลำดับของตัวอักษรหรือเลื่อนแบบวนรอบ งานสองอย่างนั้นต้องใช้ c - 'a' อยู่ดี

ลองทำให้พัง

แบบที่ 1 เช็กตัวอักษรด้วยช่วง 'A' ถึง 'z'

อยากนับตัวอักษรทั้งใหญ่และเล็ก แล้วคิดว่าเขียนช่วงเดียวจาก 'A' ถึง 'z' ก็ครอบคลุมทั้งหมด

#include <stdio.h>

int main()
{
    char c;
    int letters = 0;
    while (scanf("%c", &c) == 1 && c != '\n') {
        if (c >= 'A' && c <= 'z') {
            letters++;
        }
    }
    printf("%d\n", letters);
    return 0;
}
top_score[1]
11

ตัวอักษรจริงมี 8 ตัว คือ topscore แต่ได้ 11 เพราะ _ [ และ ] มีรหัส 95 91 และ 93 ซึ่งอยู่ในช่องว่างระหว่าง 'Z' กับ 'a' ที่เห็นในตารางตอนต้น ต้องแยกเป็นสองช่วงเชื่อมด้วย || หรือใช้ isalpha(c)

แบบที่ 2 ลบ 0 แทน '0'

ในโปรแกรม year แก้บรรทัดที่ 8 เป็น year = year * 10 + (c - 0); ลืมเครื่องหมายคำพูดไปคู่เดียว

#include <stdio.h>

int main()
{
    char c;
    int year = 0;
    while (scanf("%c", &c) == 1 && c != '\n') {
        year = year * 10 + (c - 0);
    }
    printf("%d\n", year + 1);
    return 0;
}
2569
55898

ไม่มีคำเตือน เพราะลบ 0 เป็นเรื่องถูกไวยากรณ์ แต่ c - 0 ก็คือรหัสของ c เอง '2' จึงกลายเป็น 50 แทนที่จะเป็น 2 ถ้าเห็นคำตอบที่ใหญ่ผิดปกติในโจทย์ที่อ่านตัวเลขทีละตัว ให้ดูจุดนี้ก่อน

แบบที่ 3 เทียบผลของ isalpha กับ 1

#include <stdio.h>
#include <ctype.h>

int main()
{
    char c;
    int letters = 0;
    while (scanf("%c", &c) == 1 && c != '\n') {
        if (isalpha(c) == 1) {
            letters++;
        }
    }
    printf("%d\n", letters);
    return 0;
}

บนเครื่องที่เขียนเอกสารนี้ ป้อน Hello แล้วได้ 1 แทนที่จะเป็น 5 เพราะ isalpha ไม่ได้สัญญาว่าจะคืน 1 เมื่อจริง สัญญาแค่ว่า ไม่ใช่ 0 บนเครื่องนี้ isalpha('H') ได้ 1 แต่ isalpha('e') ได้ 2 จึงนับได้แค่ H ตัวเดียว เครื่องอื่นอาจได้ค่าอื่น ใช้ if (isalpha(c)) ตรงๆ ถูกเสมอ ส่วนถ้าอยากได้เท็จ ใช้ if (!isalpha(c))

สรุป

  • char เป็นจำนวนเต็ม บวก ลบ เปรียบเทียบ และใช้เป็นตัวนับในลูปได้
  • เช็กชนิดด้วยช่วง เช่น c >= 'a' && c <= 'z' อย่ารวมสองช่วงเป็น 'A' ถึง 'z'
  • c - 'a' คือลำดับ 0 ถึง 25 บวก 'A' ได้ตัวพิมพ์ใหญ่ บวก k แล้ว % 26 ได้การเลื่อนแบบวนรอบ
  • c - '0' แปลงตัวอักษรตัวเลขเป็นค่า และ value * 10 + digit ประกอบหลักเป็นตัวเลข
  • ctype.h มี isdigit isalpha isupper islower isspace toupper tolower ใช้ใน if ตรงๆ อย่าเทียบกับ 1