2.3 รับข้อมูลด้วย scanf
บทที่ 1 รับแต่ตัวเลขที่คั่นด้วยช่องว่าง แต่ข้อมูลเข้าของโจทย์จริงมีหลายหน้าตา ตัวเลขอยู่คนละบรรทัด มีตัวอักษรปน มีชื่อคน มีเวลาแบบ 12:30 หรือวันที่แบบ 21/10/2569 หน้านี้ทำให้รับได้ทุกแบบที่โจทย์ระดับ TOI Zero ให้มา และรู้จักกับดักของ %c ที่ทำให้คนเสียคะแนนกันบ่อย
ตัวกำหนดของ scanf
| ชนิด | เขียน | หมายเหตุ |
|---|---|---|
int | scanf("%d", &x); | เหมือน printf |
long long | scanf("%lld", &x); | เหมือน printf |
double | scanf("%lf", &x); | ต้องเป็น %lf ใช้ %f ไม่ได้ |
char | scanf(" %c", &x); | มีช่องว่างหน้า %c เหตุผลอยู่ด้านล่าง |
| คำหนึ่งคำ | scanf("%s", name); | ไม่มี & |
ทุกตัวต้องมี & หน้าตัวแปรตามที่เห็นในหน้า 1.1 ยกเว้น %s ตัวเดียว
ช่องว่างกับการขึ้นบรรทัด
ก่อนอ่านตัวเลข %d %lld %lf จะข้ามช่องว่างทุกแบบไปก่อน ทั้งเว้นวรรค tab และการขึ้นบรรทัดใหม่ ข้ามกี่ตัวก็ได้ ข้อมูลเข้าจะแบ่งบรรทัดยังไง โปรแกรมก็ได้ตัวเลขชุดเดิม
โจทย์ให้รหัสนักเรียนในบรรทัดแรก และคะแนนสามวิชาในบรรทัดที่สอง
#include <stdio.h>
int main()
{
int student_id, math, science, english;
scanf("%d", &student_id);
scanf("%d %d %d", &math, &science, &english);
printf("student %d total %d\n", student_id, math + science + english);
return 0;
}
6701
70 85 90
student 6701 total 245
ลองป้อนข้อมูลเข้าที่จัดบรรทัดเละๆ แบบนี้ โปรแกรมเดิมยังพิมพ์ student 6701 total 245 เหมือนเดิม
6701 70
85
90
สรุปได้สองข้อ
- จะเขียน
scanfบรรทัดละครั้งตามข้อมูลเข้า หรือรวมเป็นscanfเดียวที่มี%dสี่ตัวก็ได้ผลเท่ากัน เลือกแบบที่อ่านง่าย - ช่องว่างระหว่าง
%dในข้อความแม่แบบ แปลว่า "ข้ามช่องว่างกี่ตัวก็ได้" เขียน"%d%d%d"ก็ได้ผลเหมือน"%d %d %d"เพราะ%dข้ามเองอยู่แล้ว เอกสารนี้ใส่ช่องว่างไว้ให้อ่านง่าย
รับตัวอักษรด้วย %c
%c ต่างจากตัวอื่น มันไม่ข้ามช่องว่าง อ่านตัวถัดไปทันที ไม่ว่าจะเป็นตัวอะไร ลองรับคะแนนในบรรทัดแรก และเกรดในบรรทัดที่สอง
#include <stdio.h>
int main()
{
int score;
char grade;
scanf("%d", &score);
scanf("%c", &grade);
printf("score = %d\n", score);
printf("grade = [%c]\n", grade);
printf("code = %d\n", grade);
return 0;
}
85
A
score = 85
grade = [
]
code = 10
เกรดไม่ใช่ A แต่เป็นการขึ้นบรรทัด วงเล็บปิดจึงไปอยู่บรรทัดถัดไป และรหัสที่ได้คือ 10 ซึ่งเป็นรหัส ASCII ของการขึ้นบรรทัด ดูว่าเกิดอะไรขึ้นทีละตัวอักษร
| ตัวอักษรในข้อมูลเข้า | 8 | 5 | ขึ้นบรรทัด | A | ขึ้นบรรทัด |
|---|---|---|---|---|---|
| ถูกอ่านโดย | %d | %d | %c | ไม่มีใครอ่าน | ไม่มีใครอ่าน |
%d อ่าน 8 กับ 5 แล้วหยุดตรงที่ไม่ใช่ตัวเลข การขึ้นบรรทัดหลัง 85 จึงยังค้างอยู่ %c มาเจอเป็นตัวแรก ก็หยิบไปเลย แก้ด้วยการใส่ ช่องว่างหน้า %c ช่องว่างในข้อความแม่แบบแปลว่าข้ามช่องว่างทุกแบบที่มีก่อน แล้ว %c ค่อยอ่าน
#include <stdio.h>
int main()
{
int score;
char grade;
scanf("%d", &score);
scanf(" %c", &grade);
printf("score = %d\n", score);
printf("grade = [%c]\n", grade);
return 0;
}
85
A
score = 85
grade = [A]
ตัวอักษรสองตัวที่คั่นด้วยช่องว่างในบรรทัดเดียว เช่น A B ก็เจอปัญหาเดียวกัน
#include <stdio.h>
int main()
{
char first, second;
scanf("%c%c", &first, &second);
printf("[%c] [%c]\n", first, second);
return 0;
}
A B
[A] [ ]
ตัวที่สองได้ช่องว่าง เพราะช่องว่างก็เป็นตัวอักษรหนึ่งตัว แก้เป็น "%c %c" จะได้ [A] [B] จำง่ายๆ ว่า รับ char ให้ใส่ช่องว่างหน้า %c เสมอ ยกเว้นโจทย์ที่ต้องการอ่านช่องว่างจริงๆ ซึ่งแทบไม่มีในระดับนี้
รับคำด้วย %s
ชื่อคนหรือชื่อสินค้าเป็นตัวอักษรหลายตัว เก็บใน char ตัวเดียวไม่ได้ ต้องใช้กล่องที่เก็บตัวอักษรได้หลายตัว
#include <stdio.h>
int main()
{
char name[21]; // (1)!
scanf("%s", name); // (2)!
printf("Hello, %s!\n", name);
return 0;
}
- ขอกล่องเก็บตัวอักษร 21 ช่องชื่อ
nameใช้เก็บคำยาวได้ไม่เกิน 20 ตัว อีกหนึ่งช่องเก็บเครื่องหมายบอกจุดจบของข้อความ วงเล็บเหลี่ยมแบบนี้คือ array ซึ่งจะเรียนในบทที่ 5 และเรื่องข้อความจะลงลึกในบทที่ 6 %sไม่ต้องมี&เพราะชื่อnameบอกที่อยู่ของกล่องอยู่แล้ว เหตุผลเต็มๆ อยู่ในบทที่ 10
Somchai Jaidee
Hello, Somchai!
%s ข้ามช่องว่างข้างหน้าเหมือน %d แล้วอ่านไปจนเจอช่องว่าง จึงได้แค่ Somchai ส่วน Jaidee ยังค้างอยู่ในข้อมูลเข้า ถ้าโจทย์ให้ชื่อกับนามสกุล ให้รับเป็นสองคำ
#include <stdio.h>
int main()
{
char first[21], last[21];
scanf("%s %s", first, last);
printf("Hello, %s!\n", first);
printf("%s, %s\n", last, first);
return 0;
}
Somchai Jaidee
Hello, Somchai!
Jaidee, Somchai
ตอนนี้ใช้ได้แค่รับคำแล้วพิมพ์ออกไปทั้งคำ การนับตัวอักษร การรับข้อความทั้งบรรทัดที่มีช่องว่าง และการแก้ตัวอักษรข้างใน รอบทที่ 6 และต้องดูขอบเขตในโจทย์เสมอว่าคำยาวได้กี่ตัว แล้วขอกล่องให้มากกว่านั้นหนึ่งช่อง
ตัวคั่นพิเศษ
ตัวอักษรอื่นในข้อความแม่แบบที่ไม่ใช่ % และไม่ใช่ช่องว่าง scanf จะจับคู่ตรงตัว เวลา 07:45 จึงรับด้วย "%d:%d" อ่านตัวเลข เจอ : ข้ามไป แล้วอ่านตัวเลขอีกตัว
#include <stdio.h>
int main()
{
int hour, minute;
scanf("%d:%d", &hour, &minute);
printf("%d minutes after midnight\n", hour * 60 + minute);
return 0;
}
07:45
465 minutes after midnight
%d อ่าน 07 เป็น 7 ได้ตามปกติ เลข 0 ข้างหน้าไม่มีผล วันที่ก็ใช้วิธีเดียวกัน ตัวอย่างนี้แปลงวันที่แบบไทยที่ใช้ปี พ.ศ. เป็นแบบสากลที่ขึ้นต้นด้วยปี ค.ศ. และใช้ %02d จากหน้า 2.2
#include <stdio.h>
int main()
{
int day, month, year;
scanf("%d/%d/%d", &day, &month, &year);
printf("%d-%02d-%02d\n", year - 543, month, day);
return 0;
}
21/10/2569
2026-10-21
ป้อน 5/3/2569 ที่ไม่มีเลข 0 นำหน้า ก็ได้ 2026-03-05 เพราะ %d อ่านได้ทั้งเลขหลักเดียวและสองหลัก
ลองทำให้พัง
แบบที่ 1 ลืมตัวคั่น
รับเวลาด้วย "%d %d" แทน "%d:%d" แล้วป้อน 12:30
int hour, minute;
scanf("%d %d", &hour, &minute);
printf("%d hours %d minutes\n", hour, minute);
คอมไพล์ผ่านโดยไม่มีคำเตือน เพราะ compiler ไม่รู้ว่าข้อมูลเข้าจะหน้าตาแบบไหน ตอนรัน %d ตัวแรกได้ 12 แล้ว %d ตัวที่สองเจอ : ซึ่งไม่ใช่ตัวเลข scanf เลยหยุดอ่านทันที minute ไม่เคยถูกใส่ค่า จึงเป็นค่าขยะ บนเครื่องที่เขียนเอกสารนี้ได้ 12 hours 0 minutes ซึ่งดูน่าเชื่อจนอาจไม่ทันสังเกต scanf ไม่ขึ้นข้อความเตือนอะไรเลยตอนอ่านไม่สำเร็จ ถ้าไม่ลองพิมพ์ค่าที่รับมาดู จะไม่รู้เลย
แบบที่ 2 ใส่ & หน้า %s
ในโปรแกรม who เขียน scanf("%s", &name);
who.c:6:13: warning: format '%s' expects argument of type 'char *', but argument 2 has type 'char (*)[21]' [-Wformat=]
6 | scanf("%s", &name);
| ~^ ~~~~~
| | |
| | char (*)[21]
| char *
บนเครื่องที่เขียนเอกสารนี้ยังพิมพ์ Hello, Somchai! ได้ถูก แต่ compiler บอกว่าชนิดไม่ตรง char (*)[21] กับ char * เป็นเรื่องของบทที่ 10 ตอนนี้จำแค่ว่า %s ไม่ต้องมี & และทำตามคำเตือนทุกครั้ง
แบบที่ 3 รับ double ด้วย %f
ใน printf ใช้ %f กับ double ได้ หลายคนเลยเขียน scanf แบบเดียวกัน
double height;
scanf("%f", &height);
printf("%f\n", height);
height.c:6:13: warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double *' [-Wformat=]
6 | scanf("%f", &height);
| ~^ ~~~~~~~
| | |
| | double *
| float *
| %lf
ใน scanf %f คือ float ชนิดทศนิยมที่เล็กกว่า double ครึ่งหนึ่ง scanf จึงเขียนค่าลงกล่องแค่ครึ่งเดียว บนเครื่องที่เขียนเอกสารนี้ ป้อน 1.75 แล้วได้ 0.000000 ทางแก้อยู่ในบรรทัดสุดท้ายของคำเตือนแล้ว คือ %lf
สรุป
%d%lld%lf%sข้ามช่องว่างและการขึ้นบรรทัดให้เอง ข้อมูลเข้าแบ่งบรรทัดยังไงก็อ่านได้%cไม่ข้ามอะไรเลย รับcharด้วย" %c"ที่มีช่องว่างข้างหน้า%sอ่านหนึ่งคำจนเจอช่องว่าง ไม่มี&และกล่องต้องใหญ่กว่าคำที่ยาวที่สุดหนึ่งช่อง- ตัวคั่นอย่าง
:หรือ/ใส่ในข้อความแม่แบบให้ตรงกับข้อมูลเข้า เช่น"%d:%d" doubleในscanfต้องเป็น%lfscanfอ่านไม่สำเร็จจะไม่มีข้อความเตือน ถ้าผลแปลก ให้พิมพ์ค่าที่รับมาดูก่อน