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' + 1 | 7 | ลำดับแบบที่คนนับ 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;
}
"%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' | 2 | 0 × 10 + 2 = 2 |
'5' | 5 | 2 × 10 + 5 = 25 |
'6' | 6 | 25 × 10 + 6 = 256 |
'9' | 9 | 256 × 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มีisdigitisalphaisupperislowerisspacetouppertolowerใช้ในifตรงๆ อย่าเทียบกับ 1