[0]zero-index clytie0x/zero-index

2.3 รับข้อมูลด้วย scanf

บทที่ 1 รับแต่ตัวเลขที่คั่นด้วยช่องว่าง แต่ข้อมูลเข้าของโจทย์จริงมีหลายหน้าตา ตัวเลขอยู่คนละบรรทัด มีตัวอักษรปน มีชื่อคน มีเวลาแบบ 12:30 หรือวันที่แบบ 21/10/2569 หน้านี้ทำให้รับได้ทุกแบบที่โจทย์ระดับ TOI Zero ให้มา และรู้จักกับดักของ %c ที่ทำให้คนเสียคะแนนกันบ่อย

ตัวกำหนดของ scanf

ชนิดเขียนหมายเหตุ
intscanf("%d", &x);เหมือน printf
long longscanf("%lld", &x);เหมือน printf
doublescanf("%lf", &x);ต้องเป็น %lf ใช้ %f ไม่ได้
charscanf(" %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 ของการขึ้นบรรทัด ดูว่าเกิดอะไรขึ้นทีละตัวอักษร

ตัวอักษรในข้อมูลเข้า85ขึ้นบรรทัด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;
}
  1. ขอกล่องเก็บตัวอักษร 21 ช่องชื่อ name ใช้เก็บคำยาวได้ไม่เกิน 20 ตัว อีกหนึ่งช่องเก็บเครื่องหมายบอกจุดจบของข้อความ วงเล็บเหลี่ยมแบบนี้คือ array ซึ่งจะเรียนในบทที่ 5 และเรื่องข้อความจะลงลึกในบทที่ 6
  2. %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 ต้องเป็น %lf
  • scanf อ่านไม่สำเร็จจะไม่มีข้อความเตือน ถ้าผลแปลก ให้พิมพ์ค่าที่รับมาดูก่อน