[0]zero-index clytie0x/zero-index

2.1 แสดงผลด้วย printf

ในการแข่งขัน grader ที่รู้จักในหน้า 0.3 เทียบผลลัพธ์ของเรากับคำตอบทีละตัวอักษร ทศนิยมเกินมาหนึ่งหลัก หรือเว้นวรรคผิดหนึ่งช่อง ก็ได้ศูนย์คะแนนในชุดนั้นทันที printf คือเครื่องมือที่ทำให้เราคุมได้ทุกตัวอักษร บทที่ 1 ใช้ไปแล้วสามสี่แบบ หน้านี้รวมทุกแบบที่โจทย์ระดับ TOI Zero ต้องใช้ ทั้งชนิดข้อมูล จำนวนทศนิยม และตัวอักษรพิเศษอย่างเครื่องหมายคำพูด

ตัวกำหนดรูปแบบทั้งหมด

ข้อความแรกใน printf เรียกว่า format string (ข้อความแม่แบบ) ตัวอักษรธรรมดาในนั้นพิมพ์ออกมาตรงตัว ส่วนที่ขึ้นต้นด้วย % เรียกว่า format specifier (ตัวกำหนดรูปแบบ) เป็นช่องที่รอค่ามาเติม ค่าที่อยู่หลังข้อความจะเติมลงช่องตามลำดับ อย่างที่เห็นในหน้า 1.1

ตัวกำหนดใช้พิมพ์เจอครั้งแรก
%dint1.1
%lldlong long1.3
%fdouble1.4
%cchar ตัวอักษรหนึ่งตัว1.4
%sข้อความ (string)หน้านี้
%%เครื่องหมาย % หนึ่งตัว1.2
#include <stdio.h>

int main()
{
    int score = 45;
    long long views = 3500000000;
    double height = 158.5;
    char grade = 'B';
    printf("%s got %d points, grade %c\n", "Mali", score, grade);
    printf("height %f cm\n", height);
    printf("views %lld\n", views);
    printf("progress 75%%\n");
    return 0;
}
Mali got 45 points, grade B
height 158.500000 cm
views 3500000000
progress 75%
  • บรรทัดที่ 9 มีช่องสามช่อง %s %d %c ค่าสามค่าเติมตามลำดับ ชนิดของค่าต้องตรงกับช่อง
  • %s ใช้กับข้อความในเครื่องหมายคำพูดคู่ ตอนนี้ยังไม่มีตัวแปรที่เก็บข้อความ จะได้เริ่มใช้ในหน้า 2.3 และเรียนเต็มๆ ในบทที่ 6
  • บรรทัดที่ 12 ไม่มีค่าให้เติมเลย %% ไม่ใช่ช่อง แค่บอกให้พิมพ์ % หนึ่งตัว

กำหนดจำนวนทศนิยมด้วย %.Nf

%f พิมพ์ทศนิยม 6 ตำแหน่งเสมอ แต่โจทย์มักสั่งว่า "ตอบเป็นทศนิยม 2 ตำแหน่ง" ถ้าพิมพ์ 6 ตำแหน่งไป ผลไม่ตรงกับคำตอบ ก็ไม่ได้คะแนน วิธีกำหนดคือใส่จุดกับตัวเลขไว้ระหว่าง % กับ f เช่น %.2f แปลว่าทศนิยม 2 ตำแหน่ง

ใช้โปรแกรม BMI จากหน้า 1.4 พิมพ์ค่าเดียวกันหลายแบบ

#include <stdio.h>

int main()
{
    double weight, height;
    scanf("%lf %lf", &weight, &height);
    double bmi = weight / (height * height);
    printf("%f\n", bmi);
    printf("%.3f\n", bmi);
    printf("%.2f\n", bmi);
    printf("%.1f\n", bmi);
    printf("%.0f\n", bmi);
    return 0;
}
60 1.70
20.761246
20.761
20.76
20.8
21
  • printf ปัดเศษ ไม่ได้ตัดทิ้ง ค่าจริงคือ 20.7612456... พอเหลือ 1 ตำแหน่ง หลักถัดไปคือ 6 จึงปัดขึ้นเป็น 20.8 พอเหลือ 0 ตำแหน่ง ก็ปัดขึ้นเป็น 21
  • %.0f ไม่พิมพ์จุดทศนิยมเลย
  • การปัดเกิดตอนพิมพ์เท่านั้น ค่าใน bmi ยังเป็น 20.7612456... เหมือนเดิม

printf ปัด แต่ cast ตัด

%.0f กับ 20.76 ได้ 21 แต่ (int)20.76 ได้ 20 เพราะ cast ตัดทศนิยมทิ้งตามที่เห็นในหน้า 1.4 โจทย์ที่สั่งให้ "ปัดเศษ" ใช้ %.0f ได้ ส่วนโจทย์ที่สั่งให้ "ตัดเศษทิ้ง" ใช้ cast

printf ไม่มีแบบ %lf ที่ต่างจาก %f เขียน %lf ใน printf ก็ได้ผลเหมือนกัน แต่ใน scanf ต้องเป็น %lf เท่านั้น ดูเหตุผลในหน้า 2.3

ปัดเศษไม่ได้ตรงอย่างที่คิดทุกครั้ง

2.675 ปัดเป็นทศนิยม 2 ตำแหน่ง คิดในใจได้ 2.68 แต่ลองรันดู

#include <stdio.h>

int main()
{
    printf("%.2f\n", 2.675);
    printf("%.20f\n", 2.675);
    return 0;
}
2.67
2.67499999999999982236

บรรทัดที่สองเฉลยว่าทำไม 2.675 เก็บในฐานสองแบบลงตัวไม่ได้ เรื่องเดียวกับ 0.1 + 0.2 ในหน้า 1.4 ค่าที่เก็บจริงน้อยกว่า 2.675 นิดเดียว หลักที่สามจึงเป็น 4 และปัดลง

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

เลขที่ตกตรงครึ่งพอดี ต่างกันตามระบบ

เลขอย่าง 0.25 กับ 2.5 เก็บในฐานสองได้ลงตัวพอดี พอปัดตรงครึ่ง ระบบแต่ละแบบเลือกไม่เหมือนกัน วัดบนเครื่องที่เขียนเอกสารนี้ ฝั่ง Windows 11 ใช้ GCC 14.2 ของ Code::Blocks ฝั่ง Linux ใช้ Debian ใน WSL กับ GCC 16.2

คำสั่งWindowsLinux
printf("%.1f\n", 0.25);0.30.2
printf("%.2f\n", 0.125);0.130.12
printf("%.0f\n", 2.5);32

grader อาจไม่ได้ใช้ระบบเดียวกับเครื่องเรา โจทย์ที่ดีจึงเลี่ยงตัวเลขพวกนี้ แบบฝึกหัดในเว็บนี้ก็เลี่ยงเหมือนกัน

ตัวอักษรพิเศษที่ขึ้นต้นด้วย backslash

บางตัวอักษรพิมพ์ลงในข้อความตรงๆ ไม่ได้ การขึ้นบรรทัดใหม่กด Enter กลางข้อความไม่ได้ เครื่องหมาย " ก็ใส่ตรงๆ ไม่ได้เพราะ compiler จะคิดว่าข้อความจบแล้ว ภาษา C จึงให้เขียนเป็นสองตัวอักษรที่ขึ้นต้นด้วย \ (backslash) เรียกว่า escape sequence

เขียนได้
\nขึ้นบรรทัดใหม่ (newline)
\ttab กระโดดไปคอลัมน์ถัดไป
\"เครื่องหมายคำพูด "
\\backslash \ หนึ่งตัว

สองแถวล่างคือวิธีเดียวกับในคำใบ้ของข้อ เครื่องหมายคำพูด ในบทที่ 0 ใครยังไม่ได้ทำข้อนั้น อ่านหน้านี้จบแล้วกลับไปลองได้

#include <stdio.h>

int main()
{
    printf("Teacher said \"save it in C:\\code\"\n");
    printf("Name\tScore\n");
    printf("Mali\t85\n");
    printf("Somchai\t92\n");
    return 0;
}
Teacher said "save it in C:\code"
Name	Score
Mali	85
Somchai	92

\t ไม่ได้แปลว่าเว้นกี่ช่องตายตัว แต่แปลว่าเลื่อนไปจุดหยุดถัดไป ในหน้าต่างดำของ Windows จุดหยุดอยู่ทุก 8 ตัวอักษร (กล่องผลลัพธ์ในเว็บนี้แสดงแคบกว่านั้น) Mali ยาว 4 ตัว กับ Somchai ยาว 7 ตัว จึงไปหยุดที่ตำแหน่งเดียวกัน ตัวเลขเลยตรงกัน ถ้าชื่อยาวตั้งแต่ 8 ตัวขึ้นไปจะเกิดอะไร ดูในหน้า 2.2

ลองทำให้พัง

แบบที่ 1 ใส่ค่าไม่ครบช่อง

มีช่อง %d สองช่อง แต่ส่งค่าไปตัวเดียว

int math = 78, science = 91;
printf("math = %d, science = %d\n", math);
scores.c:6:35: warning: format '%d' expects a matching 'int' argument [-Wformat=]
    6 |     printf("math = %d, science = %d\n", math);
      |                                  ~^
      |                                   |
      |                                   int
scores.c:5:20: warning: unused variable 'science' [-Wunused-variable]
    5 |     int math = 78, science = 91;
      |                    ^~~~~~~

expects a matching 'int' argument แปลว่าช่องนี้รอ int มาเติม แต่ไม่มีให้ คำเตือนที่สองบอกว่า science ประกาศไว้แต่ไม่ได้ใช้ ซึ่งเป็นเบาะแสเดียวกัน รันต่อได้ แต่ช่องที่ไม่มีค่าจะได้ค่าขยะ บนเครื่องที่เขียนเอกสารนี้ได้ math = 78, science = -1910617456

แบบที่ 2 ใช้ backslash ตัวเดียว

พิมพ์ path ของไฟล์โดยลืมว่า \ ต้องเขียนสองตัว

#include <stdio.h>

int main()
{
    printf("C:\code\new.c\n");
    return 0;
}
path.c:5:29: warning: unknown escape sequence: '\c'
    5 |     printf("C:\code\new.c\n");
      |                             ^
C:code
ew.c

compiler เตือนแค่ \c ที่ไม่มีความหมาย แล้วทิ้ง backslash ไปเหลือแค่ c แต่ \n หน้าคำว่า new เป็น escape sequence ที่ถูกต้อง compiler จึงไม่เตือนอะไร และกลายเป็นการขึ้นบรรทัดใหม่ ตัว n หายไปด้วย ถ้าข้อความมี \ ให้เขียน \\ ทุกครั้ง

สรุป

  • %d %lld %f %c %s ใช้กับ int long long double char และข้อความ %% พิมพ์ %
  • %.2f คือทศนิยม 2 ตำแหน่ง printf ปัดเศษ แต่ cast ตัดทิ้ง
  • ทศนิยมที่เก็บไม่ลงตัวอาจปัดผิดจากที่คิดในหลักสุดท้าย
  • \n ขึ้นบรรทัด \t tab \" เครื่องหมายคำพูด \\ backslash
  • จำนวนช่องกับจำนวนค่าต้องเท่ากัน ชนิดต้องตรงกัน ถ้าไม่ตรง -Wall มักเตือน