Part of 10306103 หลักการเขียนโปรแกรมเบื้องต้น (Fundamentals of Programming)
บทที่ 3 การรับเข้าและการแสดงผล
แผนบริหารการสอนประจำบทที่ 3
บทนี้ว่าด้วยการรับเข้าและการแสดงผลของโปรแกรมภาษา C ต่อยอดจากการแสดงข้อความด้วย printf ในบทที่ 1 และการใช้ตัวแปรกับชนิดข้อมูลในบทที่ 2 โดยอธิบายตัวระบุรูปแบบ (format specifier) และการจัดรูปแบบผลลัพธ์อย่างละเอียด พร้อมแนะนำคำสั่ง scanf สำหรับรับข้อมูลจากแป้นพิมพ์เป็นครั้งแรก เพื่อให้เขียนโปรแกรมที่มีวงจรรับเข้า–ประมวลผล–แสดงผลได้ครบถ้วน
| ผลลัพธ์ | ข้อความ | หลักฐาน | CLO |
|---|---|---|---|
| LLO3.1 | อธิบายและใช้ตัวระบุรูปแบบของ printf (%d %f %c %s) และการจัดรูปแบบ (ความกว้าง/จำนวนทศนิยม/การจัดชิด) | งานข้อ 1 (ปฏิบัติการที่ 3 Lab A) | CLO1 |
| LLO3.2 | ใช้ scanf รับข้อมูลหลายชนิดเข้าโปรแกรม พร้อมใช้ & อย่างถูกต้อง | งานข้อ 2 (ปฏิบัติการที่ 3 Lab B) | CLO1 |
| LLO3.3 | เขียนโปรแกรมรับเข้า–ประมวลผล–แสดงผล ที่คอมไพล์และทำงานถูกต้องตามไวยากรณ์ | งานข้อ 3 (ปฏิบัติการที่ 3 Lab B) | CLO3 |
| LLO3.4 | ปฏิบัติตามมาตรฐานการเขียนโค้ด (การจัดย่อหน้า การตั้งชื่อสื่อความหมาย การใส่คอมเมนต์) ในงานโค้ดของบทนี้ | โค้ดทุกข้อที่ส่งในบทนี้ | CLO5 |
CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):
- CLO1 อธิบายและใช้ตัวแปร ชนิดข้อมูล การรับ-แสดงผล และโครงสร้างควบคุม (if/else, switch, loop) เพื่อแก้ปัญหาและได้ผลลัพธ์ที่ถูกต้อง
- CLO3 สามารถเขียนโค้ดได้ถูกต้องตามหลักไวยากรณ์ (Syntax) และมีประสิทธิภาพ (Efficiency)
บทที่ 3 เป็นจุดที่โปรแกรมเริ่มรับข้อมูลจากภายนอกได้ จึงเป็นสะพานเชื่อมความรู้เรื่องตัวแปรในบทที่ 2 ไปสู่โครงสร้างควบคุมในบทที่ 4–5 งานทั้งสามข้อในบทนี้สะสมหลักฐานของ CLO1 และ CLO3 ต่อเนื่องจากบทก่อน ใบงานบทนี้คิด 10 คะแนนตามตารางผลลัพธ์ด้านล่าง และเป็นหนึ่งใน 13 ชิ้นที่มีน้ำหนักเท่ากันในหมวดใบงานบทที่ 1–13 รวม 15% ของคะแนนรายวิชา ตามแผนคะแนนฉบับที่ 4 (ดูบันทึกสำหรับผู้สอนท้ายบท)
วิธีสอน
- ทบทวนการแสดงผลด้วย
printfจากบทที่ 1 แล้วเชื่อมโยงเข้าสู่การควบคุมรูปแบบการแสดงผลด้วยตัวระบุรูปแบบ - สาธิตการจัดรูปแบบผลลัพธ์ด้วยความกว้าง การจัดชิด และจำนวนทศนิยมบนเครื่องจริง พร้อมชี้ให้เห็นผลลัพธ์ที่เปลี่ยนไป
- แนะนำคำสั่ง
scanfและบทบาทของเครื่องหมาย&โดยเทียบกับการแสดงผลที่คุ้นเคยอยู่แล้ว - ให้ลงมือปฏิบัติตามใบงาน โดยกำหนดจุดตรวจระหว่างทางเพื่อให้ข้อมูลป้อนกลับทันที
- สรุปข้อผิดพลาดที่พบบ่อยเมื่อรับข้อมูล เช่น การลืม
&และการใช้ตัวระบุรูปแบบไม่ตรงชนิดข้อมูล เพื่อป้องกันความเข้าใจคลาดเคลื่อน
สื่อการเรียนการสอน
- เครื่องคอมพิวเตอร์ที่เข้าถึงแพลตฟอร์มการเรียนของรายวิชาสำหรับเขียน คอมไพล์ และรันโปรแกรมภาษา C (มาตรฐาน C11) บนเว็บ
- เอกสารประกอบบทที่ 3 และสไลด์สรุปตัวระบุรูปแบบและการจัดรูปแบบ
- ไฟล์โปรแกรมตัวอย่าง
ch3_fmt.cและch3_io.c
การวัดผลและการประเมินผล
กำหนดส่ง: งานข้อ 1–3 ภายใน 23:59 น. ของวันก่อนวันปฏิบัติการในสัปดาห์ที่ 4 ส่วนชุดฝึกท้ายบทและแบบฝึกหัดวัดความเข้าใจของบทนี้ ภายใน 23:59 น. ของวันก่อนวันบรรยายในสัปดาห์ที่ 4 ดูวันที่จริงของตอนเรียนได้ในห้องเรียนออนไลน์ (มติผู้สอน 28 ต.ค. 2569)
| ข้อ | งาน | หลักฐาน | ผลลัพธ์ | CLO |
|---|---|---|---|---|
| 1 | เขียนโปรแกรมแสดงผลด้วยตัวระบุรูปแบบและการจัดรูปแบบ (ความกว้าง/ทศนิยม/การจัดชิด) ตามที่กำหนด | ไฟล์ซอร์สโค้ด + ผลการรัน | LLO3.1 | CLO1 |
| 2 | เขียนโปรแกรมรับข้อมูลหลายชนิดด้วย scanf โดยใช้ & ถูกต้อง | ไฟล์ซอร์สโค้ด + ผลการรัน | LLO3.2 | CLO1 |
| 3 | โปรแกรมรับเข้า–ประมวลผล–แสดงผล คอมไพล์ผ่านและให้ผลถูกต้อง | ไฟล์ซอร์สโค้ด + ผลการรัน | LLO3.3 | CLO3 |
เงื่อนไขผ่าน: งานข้อ 3 ประเมินจากไฟล์เดียวกับงานข้อ 2 ที่ต่อยอดเพิ่มการประมวลผล ดังนั้นงานข้อ 2 ต้องรับค่าได้ครบและคอมไพล์ผ่านก่อนจึงจะประเมินข้อ 3 ได้
คะแนนใบงาน (10 คะแนน)
| ผลลัพธ์ | สิ่งที่ตรวจ | คะแนน |
|---|---|---|
| LLO3.1 | งานข้อที่ระบุผลลัพธ์นี้ | 3 |
| LLO3.2 | งานข้อที่ระบุผลลัพธ์นี้ | 3 |
| LLO3.3 | งานข้อที่ระบุผลลัพธ์นี้ | 3 |
| LLO3.4 | มาตรฐานโค้ด ตรวจจากโค้ดทุกข้อที่ส่ง | 1 |
ใบงานบทที่ 1–13 รวมกันคิดเป็น 15% ของคะแนนรายวิชา ชิ้นละเท่ากัน ส่งช้าหัก 10% ของคะแนนงานต่อสัปดาห์ที่ช้า สูงสุด 50% และรับงานถึงสิ้นสัปดาห์ที่ 15 งานที่ผู้สอนส่งกลับให้แก้นับเวลาจากการส่งครั้งแรก ดูคะแนนของตนเองได้ที่หน้า “คะแนนของฉัน” ในห้องเรียนออนไลน์ของรายวิชา
เกณฑ์ให้คะแนน
งานข้อ 2 และงานข้อ 3 ใช้เกณฑ์ผ่าน/ไม่ผ่าน:
| เกณฑ์ | ผ่าน | ยังไม่ผ่าน |
|---|---|---|
| งานข้อ 2 | รับค่าครบทุกชนิดและใช้ & ถูกต้องทุกตัวแปรที่ไม่ใช่สตริง | รับค่าไม่ครบหรือใช้ & ไม่ถูกต้อง |
| งานข้อ 3 | คอมไพล์ผ่านไม่มีการแจ้งเตือน และผลลัพธ์ตรงโจทย์ | คอมไพล์ไม่ผ่านหรือผลลัพธ์ไม่ตรง |
งานข้อ 1 ใช้รายการตรวจ (checklist): เลือกตัวระบุรูปแบบตรงชนิดข้อมูล, จัดความกว้างและการจัดชิดตรงโจทย์, กำหนดจำนวนทศนิยมถูกต้อง, จัดย่อหน้า 4 ช่องว่างและมีคอมเมนต์ส่วนหัว
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
บทนำ
โปรแกรมที่มีประโยชน์ส่วนใหญ่ต้องรับข้อมูลจากภายนอกและแสดงผลกลับออกมาในรูปแบบที่อ่านเข้าใจง่าย บทที่ 1 ได้แนะนำการแสดงข้อความด้วย printf และบทที่ 2 ได้แนะนำตัวแปรและชนิดข้อมูล บทนี้จึงนำสองสิ่งนั้นมาประกอบกันให้สมบูรณ์ โดยอธิบายตัวระบุรูปแบบและการจัดรูปแบบผลลัพธ์ของ printf อย่างละเอียด แล้วแนะนำคำสั่ง scanf สำหรับรับข้อมูลจากแป้นพิมพ์ เพื่อให้เขียนโปรแกรมที่มีวงจรรับเข้า–ประมวลผล–แสดงผลได้ครบถ้วน
ทักษะการรับเข้าและการแสดงผลในบทนี้เป็นเครื่องมือพื้นฐานของทุกบทที่ตามมา การรับข้อมูลด้วย scanf จะถูกใช้ร่วมกับโครงสร้างควบคุมในบทที่ 4–5 ฟังก์ชันในบทที่ 6 และการประมวลผลข้อมูลในบทถัดไป ส่วนการจัดรูปแบบผลลัพธ์ช่วยให้การแสดงผลเป็นระเบียบและตรวจสอบง่ายตลอดรายวิชา
| สิ่งที่ได้จากบทนี้ | ใช้ต่อในบท |
|---|---|
| ตัวระบุรูปแบบและการจัดรูปแบบผลลัพธ์ของ printf | ทุกบทที่มีการแสดงผล |
| การรับข้อมูลด้วย scanf และการใช้ & | บท 4–5 (โครงสร้างควบคุม), บท 6 (ฟังก์ชัน) เป็นต้นไป |
| การอ่าน double ด้วย %lf และการจำกัดความกว้างสตริง | ทุกปฏิบัติการที่รับข้อมูลหลายชนิด |
ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์
บทนี้ต้องใช้คอมไพเลอร์ภาษา C และโปรแกรมแก้ไขข้อความหรือ IDE และต้องมีความรู้เรื่องตัวแปรและชนิดข้อมูลจากบทที่ 2 ตารางด้านล่างเป็นรุ่นเครื่องมือที่ใช้ทดสอบโค้ดตัวอย่างในบทนี้จริง
| เครื่องมือ | รุ่นที่ทดสอบ | หน้าที่ |
|---|---|---|
| คอมไพเลอร์ gcc | 13.3.0 (Ubuntu) | แปลซอร์สโค้ด .c เป็นไฟล์ปฏิบัติการ |
| มาตรฐานภาษา | C11 (-std=c11) | กำหนดไวยากรณ์ภาษา C ที่ใช้อ้างอิง |
โค้ดตัวอย่างในบทนี้ทดสอบด้วย gcc 13.3.0 บนระบบปฏิบัติการลินุกซ์ โดยการรับข้อมูลทดสอบด้วยการป้อนค่าผ่านทางเข้ามาตรฐาน (standard input) หากใช้คอมไพเลอร์หรือระบบปฏิบัติการอื่น ผลการแสดงข้อความภาษาไทยและการจัดรูปแบบอาจต่างกันเล็กน้อย แต่ซอร์สโค้ดภาษา C มาตรฐานใช้ได้เหมือนกัน
วิธีใช้บทเรียนและใบงานในหน้านี้
นำชนิดข้อมูลและนิพจน์จากบทที่ 2 มาใช้กับข้อมูลที่ป้อนเข้าจริง เริ่มจากตกลงลำดับข้อมูลและหน่วย ทำนายผลก่อนรัน แล้วเปรียบเทียบกับผลจริง ในแต่ละรอบให้แยกสามคำถาม: อ่านได้ครบหรือไม่ ค่าใช้กับโจทย์ได้หรือไม่ และผลแสดงตรงรูปแบบที่ต้องการหรือไม่
คัดตารางใบงานลงสมุดหรือไฟล์ของตนเอง ตารางในหน้าเว็บไม่ได้บันทึกคำตอบอัตโนมัติ ใช้ข้อมูลสมมติและแพลตฟอร์ม C ของรายวิชา ไม่ต้องติดตั้งเครื่องมือใหม่หรือเปิดบัญชีเพิ่ม งานส่งและคะแนนยังยึดแผนเดิม
บางตัวอย่างใช้ if แบบสั้นเพื่อหยุดเมื่ออ่านไม่ครบ ให้เข้าใจว่า “ตรวจแล้วจบก่อนใช้ค่า” ส่วนการออกแบบเงื่อนไขอย่างละเอียดจะเรียนในบทที่ 4 ไม่ต้องเพิ่มลูปหรือกลไกอ่านใหม่หลายรอบเพื่อทำใบงานนี้
3.1 การแสดงผลด้วย printf และตัวระบุรูปแบบ
ระดับที่ 1: คำสั่ง printf ใช้แสดงข้อความและค่าของตัวแปรออกทางจอภาพ ข้อความที่อยู่ในเครื่องหมายอัญประกาศจะถูกแสดงตามที่เขียน ส่วนตำแหน่งที่ต้องการแทรกค่าตัวแปรจะใช้ตัวระบุรูปแบบ (format specifier) ซึ่งขึ้นต้นด้วยเครื่องหมาย %
ระดับที่ 2: ตัวระบุรูปแบบที่ใช้บ่อยจับคู่กับชนิดข้อมูลจากบทที่ 2 ดังตาราง
| ตัวระบุรูปแบบ | ใช้กับชนิดข้อมูล | ตัวอย่างค่า |
|---|---|---|
%d | จำนวนเต็ม (int) | 42 |
%f | จำนวนจริง (float/double) | 3.140000 |
%c | อักขระหนึ่งตัว (char) | A |
%s | สตริง (ชุดอักขระ) | Somchai |
นอกจากนี้ยังมีอักขระพิเศษที่ใช้คู่กับ printf เช่น \n ขึ้นบรรทัดใหม่ และ \t เว้นระยะแท็บ ส่วนการแสดงเครื่องหมายเปอร์เซ็นต์เองในข้อความใช้ %% เพื่อไม่ให้สับสนกับตัวระบุรูปแบบ
เพื่อให้เห็นว่าตัวระบุรูปแบบแต่ละตัวให้ผลลัพธ์จริงอย่างไร ตารางต่อไปนี้จับคู่ตัวระบุรูปแบบกับนิพจน์ตัวอย่างและผลลัพธ์ที่ได้จากการรันจริงด้วย gcc 13.3.0 โดยใส่เครื่องหมาย [ และ ] กำกับเพื่อให้เห็นขอบเขตของค่าที่แสดง
| ตัวระบุรูปแบบ | ความหมาย | นิพจน์ตัวอย่าง | ผลลัพธ์จริง |
|---|---|---|---|
%d | จำนวนเต็มฐานสิบ | printf("[%d]", 42) | [42] |
%f | จำนวนจริง (ทศนิยม 6 ตำแหน่งโดยปริยาย) | printf("[%f]", 3.14) | [3.140000] |
%c | อักขระหนึ่งตัว | printf("[%c]", 'A') | [A] |
%s | สตริง | printf("[%s]", "Somchai") | [Somchai] |
%% | เครื่องหมายเปอร์เซ็นต์ (ไม่ใช้อาร์กิวเมนต์) | printf("50%% done") | 50% done |
ข้อสังเกตสำคัญคือ %f แสดงจำนวนทศนิยม 6 ตำแหน่งโดยปริยาย (3.14 แสดงเป็น 3.140000) หากต้องการควบคุมจำนวนทศนิยมให้สั้นลง ต้องใช้การกำหนดความละเอียดที่อธิบายในหัวข้อ 3.2
ใบงาน 1: จับคู่ข้อมูลกับทิศทางการทำงาน
ก่อนรันตัวอย่าง ให้แยกว่าเป็นการส่งค่าไปแสดงหรือส่งตำแหน่งให้ฟังก์ชันเขียนข้อมูล การใช้รูปแบบคล้ายกันไม่ได้ทำให้ printf กับ scanf ใช้แทนกันทุกส่วน
| งานที่ต้องการ | ชนิดข้อมูล | รูปแบบที่เลือก | ส่งค่าหรือส่งตำแหน่ง | เหตุผล |
|---|---|---|---|---|
| แสดงจำนวนชุด | int | บันทึกเอง | บันทึกเอง | อธิบายทิศทางข้อมูล |
| แสดงราคาเฉลี่ย | double | บันทึกเอง | บันทึกเอง | ระบุจำนวนทศนิยม |
| อ่านจำนวนสาย | int | บันทึกเอง | บันทึกเอง | ระบุตำแหน่งเก็บ |
| อ่านค่าทศนิยม | double | บันทึกเอง | บันทึกเอง | แยกจากการอ่าน float |
| แสดงอักขระกลุ่ม | char | บันทึกเอง | บันทึกเอง | แยกอักขระจากรหัสจำนวนเต็ม |
| แสดงเครื่องหมายเปอร์เซ็นต์ | ไม่มีค่าตัวแปรเพิ่มเติม | บันทึกเอง | อธิบายว่าใช้ค่าตัวแปรหรือไม่ | ไม่สับสนกับจุดเริ่มรูปแบบ |
ถ้าเห็นการใช้ %d กับ double หรือใช้ %f อ่านเข้า double ให้ชี้ว่าผิดสัญญาตรงไหนและแก้ก่อนรัน ไม่ใช้ผลจากโค้ดผิดชนิดครั้งหนึ่งเป็นคำตอบที่คาดหวัง
ตัวอย่างที่ทำทีละขั้น โปรแกรมด้านล่างแสดงตัวระบุรูปแบบทั้งห้าตัวในโปรแกรมเดียว
/* แสดงตัวระบุรูปแบบพื้นฐาน */
#include <stdio.h>
int main(void) {
int n = 42;
double x = 3.14;
char c = 'A';
char s[] = "Somchai";
printf("d: [%d]\n", n);
printf("f: [%f]\n", x);
printf("c: [%c]\n", c);
printf("s: [%s]\n", s);
printf("pct: 50%% done\n");
printf("tab:\tend\n");
return 0;
}ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)
d: [42]
f: [3.140000]
c: [A]
s: [Somchai]
pct: 50% done
tab: endไล่อ่านสายอักขระรูปแบบทีละตำแหน่ง ข้อความทั่วไปเช่น d: [ ถูกพิมพ์ตามที่เขียน พบ %d จึงแทนด้วยค่าของ n คือ 42 พบ \n จึงขึ้นบรรทัดใหม่ บรรทัดถัดมา %f แทนด้วยค่าของ x เป็น 3.140000 (หกทศนิยม) และบรรทัดสุดท้าย \t แทรกระยะแท็บระหว่าง tab: กับ end
นอกจากตัวระบุรูปแบบแล้ว รูปแบบที่สมบูรณ์ของ % ยังประกอบด้วยส่วนย่อยหลายส่วนตามลำดับ ซึ่งจะได้ใช้ในหัวข้อ 3.2
| ส่วนประกอบ | สัญลักษณ์ | หน้าที่ | จำเป็นหรือไม่ |
|---|---|---|---|
| เครื่องหมายเริ่ม | % | บอกจุดเริ่มของตัวระบุรูปแบบ | จำเป็น |
| แฟล็ก (flags) | - 0 + | ควบคุมการจัดชิดและการเติม | ไม่จำเป็น |
| ความกว้าง (width) | ตัวเลข เช่น 6 | ความกว้างต่ำสุดของช่องแสดงผล | ไม่จำเป็น |
| ความละเอียด (precision) | . ตามด้วยตัวเลข เช่น .2 | จำนวนทศนิยม (สำหรับ %f) | ไม่จำเป็น |
| ชนิด | d f c s | ชนิดข้อมูลที่จะแสดง | จำเป็น |
ระดับที่ 3: ต้องจับคู่ตัวระบุรูปแบบกับชนิดข้อมูลของค่าที่ส่งเข้าไปให้ถูกต้อง เพราะหากไม่ตรงกัน เช่น ใช้ %d กับค่าชนิด double พฤติกรรมอาจไม่ถูกกำหนด (undefined behavior) จึงไม่สามารถรับประกันผลลัพธ์ได้ โดยคอมไพเลอร์อาจไม่แจ้งข้อผิดพลาดเสมอไป การเปิดการแจ้งเตือนด้วย -Wall ตั้งแต่บทที่ 1 ช่วยให้ตรวจพบการจับคู่ที่ไม่ตรงได้ตั้งแต่ขั้นคอมไพล์ อนึ่ง ในการแสดงผลด้วย printf ค่าชนิด float จะถูกเลื่อนขั้น (promote) เป็น double โดยอัตโนมัติเมื่อส่งเป็นอาร์กิวเมนต์ จึงใช้ %f แสดงได้ทั้ง float และ double แต่ในการรับข้อมูลด้วย scanf สองชนิดนี้ต้องใช้ตัวระบุรูปแบบต่างกัน ซึ่งจะอธิบายในหัวข้อ 3.3
คำถามตรวจความเข้าใจ 3.1: ตัวระบุรูปแบบใดใช้แสดงค่าจำนวนจริง และหากต้องการแสดงเครื่องหมายเปอร์เซ็นต์ในข้อความต้องเขียนอย่างไร
3.2 การจัดรูปแบบผลลัพธ์
ระดับที่ 1: นอกจากเลือกตัวระบุรูปแบบให้ตรงชนิดข้อมูลแล้ว ยังกำหนดความกว้างของช่องแสดงผล การจัดชิด และจำนวนตำแหน่งทศนิยมได้ เพื่อให้ผลลัพธ์เป็นระเบียบและอ่านง่าย
ระดับที่ 2: รูปแบบการจัดที่ใช้บ่อยสรุปได้ดังตาราง โดยผลลัพธ์ในตารางมาจากการรันโปรแกรม ch3_fmt.c จริง (ดูปฏิบัติการที่ 3 Lab A)
| รูปแบบ | ความหมาย | ตัวอย่างผลลัพธ์ |
|---|---|---|
%6d | จำนวนเต็มในช่องกว้าง 6 ชิดขวา | | 42| |
%-6d | จำนวนเต็มในช่องกว้าง 6 ชิดซ้าย | |42 | |
%06d | จำนวนเต็มในช่องกว้าง 6 เติมศูนย์หน้า | |000042| |
%.2f | จำนวนจริง ทศนิยม 2 ตำแหน่ง | 3.14 |
%8.3f | จำนวนจริงในช่องกว้าง 8 ทศนิยม 3 ตำแหน่ง | | 3.142| |
เครื่องหมาย | ในตัวอย่างเป็นเพียงขอบที่พิมพ์ไว้เพื่อให้เห็นขอบเขตของช่อง ไม่ใช่ส่วนหนึ่งของการจัดรูปแบบ ตัวเลขหลังจุดใน %.2f กำหนดจำนวนทศนิยม ส่วนตัวเลขหน้าจุดใน %8.3f กำหนดความกว้างรวมของช่อง
หน้าที่ของแต่ละส่วนย่อยในการจัดรูปแบบสรุปแยกได้ดังตาราง
| ส่วน | สัญลักษณ์ | ผลต่อการแสดงผล |
|---|---|---|
| ความกว้าง | ตัวเลขหน้าจุด เช่น 6 | กำหนดความกว้างต่ำสุด เติมช่องว่างให้เต็มถ้าค่าแคบกว่า ค่าที่กว้างกว่าจะไม่ถูกตัด |
| ความละเอียด | . ตามด้วยตัวเลข เช่น .2 | จำนวนทศนิยมของ %f (ปัดค่าเพื่อแสดง) |
แฟล็ก - | - | จัดชิดซ้าย เติมช่องว่างด้านขวา |
แฟล็ก 0 | 0 | เติมศูนย์แทนช่องว่างด้านหน้า (ใช้กับตัวเลข) |
ใบงาน 2: นับช่องว่างและตรวจความกว้างขั้นต่ำ
เขียนข้อความที่คาดระหว่างเครื่องหมายขอบโดยแทนช่องว่างด้วยจุดบนกระดาษของคุณก่อน จากนั้นรันรูปแบบที่ถูกต้องแล้วนับจำนวนช่องจริง เครื่องหมายขอบเป็นเพียงข้อความช่วยสังเกต ไม่ได้จำกัดค่าที่อยู่ด้านใน
| คำสั่งที่ทดลอง | ข้อความระหว่างขอบที่คาด | จำนวนช่องว่าง | ผลจริงและเหตุผล |
|---|---|---|---|
printf("[%6d]", 42); | ก่อนรัน | ก่อนรัน | หลังรัน |
printf("[%-6d]", 42); | ก่อนรัน | ก่อนรัน | หลังรัน |
printf("[%06d]", 42); | ก่อนรัน | ก่อนรัน | หลังรัน |
printf("[%3d]", 12345); | ก่อนรัน | ก่อนรัน | ค่าจะถูกตัดหรือช่องจะขยาย |
printf("[%6.2f]", 3.14159); | ก่อนรัน | ก่อนรัน | นับจุดและหลักทศนิยมด้วย |
เปรียบเทียบการแสดง 1.75 ด้วย %.1f และ %.2f แล้วอธิบายว่าค่าที่เห็นบนจอกับค่าที่เก็บไว้เป็นคนละขั้น หากนำตัวแปรเดิมไปคำนวณต่อ จะไม่ได้เปลี่ยนเป็นค่าที่ปัดบนจอเอง
ตัวอย่างที่ทำทีละขั้น: %6.2f จัดรูปแบบ 3.14159 อย่างไร
พิจารณาคำสั่ง printf("|%6.2f|\n", 3.14159);
- ส่วนความละเอียด
.2ทำงานก่อน โดยปัดค่า 3.14159 ให้เหลือทศนิยม 2 ตำแหน่ง ได้ข้อความ3.14ซึ่งยาว 4 อักขระ (นับรวมจุดทศนิยม) - ส่วนความกว้าง
6กำหนดความกว้างต่ำสุดของช่องเป็น 6 อักขระ เมื่อข้อความยาว 4 จึงเติมช่องว่างด้านซ้าย 2 ช่อง (ชิดขวาเป็นค่าโดยปริยาย) - ผลลัพธ์สุดท้ายคือสองช่องว่างตามด้วย
3.14รวมกว้าง 6 ตำแหน่งพอดี
| 3.14|ตัวอย่างที่ทำทีละขั้น: %-6d ต่างจาก %06d อย่างไร
ใช้ค่าเดียวกันคือ 42 (ยาว 2 อักขระ) ในช่องกว้าง 6 ตำแหน่ง
%6dไม่มีแฟล็ก จึงชิดขวา เติมช่องว่าง 4 ช่องด้านซ้าย%-6dแฟล็ก-สั่งให้ชิดซ้าย เติมช่องว่าง 4 ช่องด้านขวา%06dแฟล็ก0สั่งให้เติมศูนย์ 4 ตัวด้านซ้าย (ยังคงชิดขวา)
|%6d| -> | 42|
|%-6d| -> |42 |
|%06d| -> |000042|โปรแกรมสั้นต่อไปนี้รวมการจัดรูปแบบที่พบบ่อยไว้ในที่เดียว เพื่อให้เห็นผลลัพธ์เทียบกันชัดเจน
/* รวมการจัดรูปแบบที่ใช้บ่อย */
#include <stdio.h>
int main(void) {
int n = 42;
double pi = 3.14159;
printf("|%6d|\n", n);
printf("|%-6d|\n", n);
printf("|%06d|\n", n);
printf("|%6.2f|\n", pi);
printf("|%8.3f|\n", pi);
printf("|%-8.3f|\n", pi);
return 0;
}ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)
| 42|
|42 |
|000042|
| 3.14|
| 3.142|
|3.142 |ระดับที่ 3: การจัดรูปแบบมีประโยชน์มากเมื่อต้องแสดงผลหลายบรรทัดให้เรียงเป็นตาราง เช่น การใช้ความกว้างคงที่ทำให้ตัวเลขเรียงตรงหลักกัน และการกำหนดทศนิยมคงที่ทำให้ค่าเงินหรือค่าวัดอ่านเทียบกันได้ง่าย ข้อควรระวังคือ %.1f และ %.2f เป็นการปัดค่าเพื่อแสดงผล ไม่ใช่การตัดทิ้ง เช่น ค่า 1.75 เมื่อแสดงด้วย %.1f จะได้ 1.8 อีกข้อหนึ่งคือความกว้างกำหนดเพียงความกว้าง "ต่ำสุด" เท่านั้น หากค่าที่แสดงกว้างกว่าช่องที่กำหนด ผลลัพธ์จะไม่ถูกตัดทิ้งแต่จะล้นออกมาเต็มความยาวของค่า
คำถามตรวจความเข้าใจ 3.2: %-6d ต่างจาก %6d อย่างไร
3.3 การรับข้อมูลด้วย scanf
ระดับที่ 1: คำสั่ง scanf ใช้รับข้อมูลจากแป้นพิมพ์เข้ามาเก็บในตัวแปร เป็นคู่ตรงข้ามกับ printf ที่แสดงผลออก รูปแบบการเขียนคล้ายกันคือระบุตัวระบุรูปแบบของชนิดข้อมูลที่ต้องการรับ
ความแตกต่างหลักระหว่างสองคำสั่งสรุปได้ดังตาราง
| ประเด็น | printf | scanf |
|---|---|---|
| ทิศทางข้อมูล | แสดงผลออกทางจอภาพ | รับข้อมูลเข้าเก็บในตัวแปร |
เครื่องหมาย & | ไม่ใช้ | ใช้หน้าตัวแปรเดี่ยว (ไม่ใช่สตริง) |
| อ่าน/แสดงค่า double | %f | %lf |
| อ่าน/แสดงค่า float | %f | %f |
ระดับที่ 2: เมื่อรับค่าเข้าตัวแปรที่ไม่ใช่สตริง ต้องใส่เครื่องหมาย & หน้าชื่อตัวแปรเพื่อบอกตำแหน่งที่อยู่ของตัวแปรนั้น เช่น
int age;
scanf("%d", &age);การอ่านค่าชนิด double ด้วย scanf ต้องใช้ตัวระบุรูปแบบ %lf ซึ่งต่างจาก printf ที่ใช้ %f ส่วนการอ่านสตริงใช้ %s และไม่ต้องใส่ & หน้าชื่อตัวแปรสตริง ตารางต่อไปนี้สรุปตัวระบุรูปแบบของ scanf ตามชนิดข้อมูล พร้อมระบุว่าต้องใส่ & หรือไม่
| ชนิดข้อมูล | ตัวระบุรูปแบบใน scanf | ต้องมี & |
|---|---|---|
| int | %d | ใช่ |
| float | %f | ใช่ |
| double | %lf | ใช่ |
| char | %c | ใช่ |
| สตริง (char[]) | %s | ไม่ (ชื่ออาร์เรย์เป็นที่อยู่อยู่แล้ว) |
ตัวอย่างที่ทำทีละขั้น: รับจำนวนเต็มและจำนวนจริง
/* รับค่าสองชนิดด้วย scanf */
#include <stdio.h>
int main(void) {
int age;
double score;
printf("ป้อนอายุและคะแนน: ");
if (scanf("%d %lf", &age, &score) != 2) {
puts("Input error");
return 1;
}
printf("อายุ = %d\n", age);
printf("คะแนน = %.2f\n", score);
return 0;
}ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0 ป้อนค่า 25 87.5)
ป้อนอายุและคะแนน: อายุ = 25
คะแนน = 87.50ไล่การทำงานทีละขั้น scanf อ่านข้อความที่ป้อน 25 87.5 โดยตัวระบุ %d รับ 25 ไปเก็บที่ตำแหน่งของ age ข้ามช่องว่าง แล้ว %lf รับ 87.5 ไปเก็บที่ตำแหน่งของ score เพราะ score เป็น double จึงต้องใช้ %lf ไม่ใช่ %f จากนั้น printf แสดงค่ากลับ โดย %.2f แสดง 87.5 เป็น 87.50 (ทศนิยม 2 ตำแหน่ง)
ระดับที่ 3: เหตุผลที่สตริงไม่ต้องใส่ & คือชื่อของอาร์เรย์อักขระเป็นตัวแทนตำแหน่งที่อยู่ของข้อมูลอยู่แล้ว การใส่ & จึงไม่จำเป็น ขณะที่ตัวแปรเดี่ยว เช่น int หรือ double ต้องใช้ & เพื่อให้ scanf ทราบว่าจะนำค่าที่รับไปเก็บไว้ที่ใด เรื่องอาร์เรย์จะอธิบายอย่างละเอียดในบทที่ 7 และ 8 บทนี้ใช้เพียงเพื่อรับข้อความสั้น ทั้งนี้หากใช้ %f อ่านค่าที่เก็บในตัวแปร double หรือใช้ %lf อ่านค่าที่เก็บในตัวแปร float จะเป็นพฤติกรรมไม่พึงประสงค์ (undefined behavior) ตามมาตรฐานภาษา เพราะชนิดของปลายทางไม่ตรงกับที่ตัวระบุรูปแบบคาดไว้และอาจเขียนข้อมูลผิดตำแหน่งหรือผิดขนาด
คำถามตรวจความเข้าใจ 3.3: เหตุใด scanf("%d", &age) จึงต้องมี & แต่การอ่านสตริงด้วย %s ไม่ต้อง
ใบงาน 3: ตรวจจำนวนรายการที่อ่านได้ก่อนคำนวณ
ตัวอย่างสั้นที่แสดงรูปคำสั่ง scanf ช่วยให้เห็นวิธีส่งตำแหน่ง แต่โปรแกรมที่นำค่าไปใช้ต้องตรวจผลการอ่านด้วย โปรแกรมต่อไปนี้แยกการอ่านไม่ครบออกจากจำนวนที่อ่านได้แต่ผิดช่วง ใช้ข้อมูลจำนวนเต็มที่แทนด้วย int ได้ และจำกัดค่าที่นำมาบวกอยู่ใน -10000 ถึง 10000
/* scan_status.c: Check conversions before using the input values. */
#include <stdio.h>
int main(void)
{
int first = 0;
int second = 0;
int assigned = scanf("%d %d", &first, &second);
if (assigned == EOF) {
puts("assigned=EOF");
puts("Input error");
return 1;
}
printf("assigned=%d\n", assigned);
if (assigned != 2) {
puts("Input error");
return 1;
}
if (first < -10000 || first > 10000 ||
second < -10000 || second > 10000) {
puts("Range error");
return 1;
}
printf("sum=%d\n", first + second);
return 0;
}| ข้อมูลทดสอบ | ค่าคืนที่คาด | ควรนำไปบวกหรือไม่ | เหตุผลที่เลือกกรณี |
|---|---|---|---|
12 30 | ก่อนรัน | ก่อนรัน | ข้อมูลปกติสองค่า |
12 x | ก่อนรัน | ก่อนรัน | อ่านสำเร็จเพียงบางส่วน |
x 30 | ก่อนรัน | ก่อนรัน | ตัวแรกไม่ตรงชนิด |
12 แล้วจบข้อมูล | ก่อนรัน | ก่อนรัน | มีข้อมูลไม่ครบ |
| จบข้อมูลโดยไม่มีค่า | ก่อนรัน | ก่อนรัน | ไม่มีการกำหนดค่าสำเร็จ |
10001 1 | ก่อนรัน | ก่อนรัน | อ่านได้แต่เกินช่วงใช้งาน |
12 30 extra | ก่อนรัน | ก่อนรัน | ตรวจว่าการอ่านครบเท่ากับตรวจหมดทั้งบรรทัดหรือไม่ |
EOF เป็นชื่อค่าที่ใช้ตรวจเงื่อนไขจบข้อมูลหรือ input failure ก่อนมีการกำหนดค่า ไม่ควรท่องว่าทุกระบบต้องใช้เลขค่าหนึ่งแทนชื่อนี้ การอ่านสำเร็จสองค่าไม่ได้รับรองว่าไม่มีข้อความตามหลัง และการเช็กช่วงหลัง scanf ไม่ทำให้รับเลขที่ใหญ่เกินช่วง int ได้อย่างปลอดภัย ตัวอย่างนี้ไม่รับภาระตรวจข้อความจำนวนใด ๆ แบบทั่วไป
3.4 การรับหลายค่าและข้อควรระวัง
ระดับที่ 1: scanf รับข้อมูลหลายค่าในคำสั่งเดียวได้ โดยเขียนตัวระบุรูปแบบเรียงต่อกันตามลำดับค่าที่จะป้อน และคั่นด้วยช่องว่างในสายอักขระรูปแบบ
ระดับที่ 2: เมื่อรับสตริงด้วย %s ควรจำกัดความกว้างให้น้อยกว่าขนาดอาร์เรย์หนึ่งตำแหน่ง เพื่อเว้นที่ให้อักขระปิดท้ายสตริง เช่น อาร์เรย์ขนาด 20 ควรใช้ %19s เพื่อป้องกันข้อมูลล้น นอกจากนี้ %s อ่านข้อความถึงช่องว่างตัวแรกเท่านั้น จึงเหมาะกับการรับคำเดียว ไม่เหมาะกับข้อความที่มีช่องว่าง
เหตุที่ %19s รองรับอาร์เรย์ขนาด 20 เพราะตัวเลขความกว้างจำกัดจำนวนอักขระที่ scanf รับเข้าได้สูงสุด 19 ตัว เมื่ออ่านสตริงสำเร็จ scanf จะเติมอักขระปิดท้ายสตริงอีกหนึ่งตำแหน่ง รวมเป็น 20 ตำแหน่งพอดีกับขนาดอาร์เรย์ ความสัมพันธ์ระหว่างความกว้างกับขนาดอาร์เรย์สรุปได้ดังตาราง
| ขนาดอาร์เรย์ | ความกว้างที่ควรใช้ | จำนวนอักขระสูงสุดที่รับ |
|---|---|---|
| 10 | %9s | 9 |
| 20 | %19s | 19 |
| 32 | %31s | 31 |
ใบงาน 4: ข้อความที่เก็บกับข้อความที่ยังไม่ถูกอ่าน
กำหนด char code[6]; และอ่านด้วย %5s ให้แยก “ความจุปลายทาง” “จำนวนไบต์ข้อความที่รับ” และ “ตัวปิดสตริง” ก่อนทดลอง การจำกัดความกว้างป้องกันการเขียนเกินบัฟเฟอร์ตามรูปแบบนี้ แต่ไม่ได้ทิ้งส่วนที่เหลือของคำยาวให้อัตโนมัติ
| ข้อมูลรหัสหลังอ่านจำนวนสองค่าครบ | ข้อความที่คาดว่าเก็บใน code | ส่วนที่ยังไม่ถูกอ่าน | เหตุผล |
|---|---|---|---|
AB | บันทึกก่อนรัน | บันทึกแนวคิด | คำสั้นกว่าขีดจำกัด |
ABCDE | บันทึกก่อนรัน | บันทึกแนวคิด | ยาวเท่าขีดจำกัด |
ABCDEFGH | บันทึกก่อนรัน | บันทึกแนวคิด | มีส่วนท้ายเกินห้าไบต์ |
AB CD | บันทึกก่อนรัน | บันทึกแนวคิด | มีช่องว่างแบ่งคำ |
ใช้ข้อความ ASCII ในการทดลองนี้เพื่อให้หนึ่งตัวตรงกับหนึ่งไบต์ ไม่สรุปว่าขนาดบัฟเฟอร์เดียวกันรองรับตัวอักษรไทยได้จำนวนเท่ากัน บทสตริงจะอธิบายเรื่องไบต์และข้อความเพิ่มเติม ชื่อเต็มที่มีช่องว่างต้องเลือกวิธีอ่านตามข้อกำหนด ไม่ย้ายไปใช้ %s โดยหวังว่าจะเก็บทั้งบรรทัด
ตัวอย่างที่ทำทีละขั้น: รับหลายค่าและจำกัดความกว้างด้วย %5s
/* รับหลายค่าและจำกัดความกว้างสตริง */
#include <stdio.h>
int main(void) {
int a, b;
char code[6];
printf("ป้อนจำนวนเต็มสองค่า: ");
if (scanf("%d %d", &a, &b) != 2) {
puts("Input error");
return 1;
}
if (a < -10000 || a > 10000 || b < -10000 || b > 10000) {
puts("Range error");
return 1;
}
printf("ผลรวม = %d\n", a + b);
printf("ป้อนรหัส: ");
if (scanf("%5s", code) != 1) {
puts("Input error");
return 1;
}
printf("รหัสที่เก็บได้ = %s\n", code);
return 0;
}ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0 ป้อน 12 30 แล้วป้อน ABCDEFGH)
ป้อนจำนวนเต็มสองค่า: ผลรวม = 42
ป้อนรหัส: รหัสที่เก็บได้ = ABCDEสังเกตสองจุด จุดแรก scanf("%d %d", &a, &b) รับ 12 และ 30 ตามลำดับแล้วบวกกันได้ 42 จุดที่สอง แม้ป้อนข้อความยาวแปดอักขระ ABCDEFGH แต่ %5s จำกัดให้รับได้เพียง 5 อักขระแรกคือ ABCDE อาร์เรย์ code ขนาด 6 จึงมีที่พอสำหรับ 5 อักขระกับอักขระปิดท้ายอีกหนึ่งตำแหน่ง ไม่เกิดข้อมูลล้น
ระดับที่ 3: ข้อผิดพลาดที่พบบ่อยในการรับข้อมูลมี 3 อย่าง ได้แก่ การลืมใส่ & หน้าตัวแปรที่ไม่ใช่สตริง การใช้ %f แทน %lf ขณะอ่านค่า double และการกำหนดอาร์เรย์เล็กเกินกว่าข้อความที่รับ การตรวจลำดับตัวระบุรูปแบบให้ตรงกับลำดับตัวแปรและชนิดข้อมูลทุกครั้งช่วยลดข้อผิดพลาดเหล่านี้ได้ ตารางต่อไปนี้เทียบข้อผิดพลาดกับผลที่ตามมาเพื่อให้จดจำได้ง่าย
| ข้อผิดพลาด | ผลที่ตามมา | วิธีป้องกัน |
|---|---|---|
ลืม & หน้าตัวแปรเดี่ยว | ค่าไปเก็บผิดตำแหน่ง โปรแกรมทำงานผิดหรือค้าง | ใส่ & หน้าตัวแปรที่ไม่ใช่สตริงทุกตัว |
ใช้ %f อ่าน double | ค่าที่รับเพี้ยน | ใช้ %lf อ่าน double ใน scanf |
| อาร์เรย์เล็กเกินข้อความ | ข้อมูลล้น (buffer overflow) | จำกัดความกว้าง เช่น %19s สำหรับอาร์เรย์ขนาด 20 |
คำถามตรวจความเข้าใจ 3.4: การอ่านค่า double ด้วย scanf ใช้ตัวระบุรูปแบบใด และต่างจากการแสดงผลด้วย printf อย่างไร
3.5 กรณีศึกษาต่อเนื่อง: ใบสรุปการจัดชุดอุปกรณ์
บทก่อนใช้ค่าคงที่ในโปรแกรม บทนี้เปลี่ยนให้ผู้ใช้ส่งจำนวนสายและจำนวนสายต่อชุดเข้ามา สิ่งที่ต้องเพิ่มไม่ใช่เพียง scanf แต่เป็นข้อตกลงของข้อมูลเข้า ลำดับการอ่าน การตรวจผลการอ่าน และข้อความผลลัพธ์ที่ผู้ใช้ตีความได้ โปรแกรมที่คำนวณสูตรถูกแต่ใช้ค่าจากการอ่านที่ล้มเหลวก็ยังไม่น่าเชื่อถือ
3.5.1 แยกความสำเร็จของการอ่านออกจากความถูกต้องของค่า
#include <stdio.h>
int main(void)
{
int cables;
int per_kit;
if (scanf("%d %d", &cables, &per_kit) != 2) {
puts("Input error: enter two integers");
return 1;
}
if (cables < 0 || cables > 10000 ||
per_kit < 1 || per_kit > 10000) {
puts("Range error");
return 1;
}
printf("Complete kits: %d\n", cables / per_kit);
printf("Leftover cables: %d\n", cables % per_kit);
return 0;
}ส่วน if เป็นโครงสร้างทางเลือกที่จะเรียนละเอียดในบทที่ 4 ในที่นี้อ่านเป็น “ถ้าอ่านไม่ครบให้จบก่อน” และ “ถ้าค่านอกช่วงให้จบก่อน” เพื่อไม่สาธิตรูปแบบรับข้อมูลที่ใช้ค่าไม่ได้ตรวจ ผู้เรียนยังไม่ต้องออกแบบเงื่อนไขซับซ้อนด้วยตนเอง
เมื่อป้อน 17 3 โปรแกรมรายงาน 5 ชุดและเหลือ 2 เส้น เมื่อป้อน 17 x อ่านสำเร็จเพียงค่าตัวแรก จึงไม่เข้าคำนวณ ส่วน 17 0 อ่านครบสองค่าแต่ไม่ผ่านขอบเขต สองกรณีหลังเป็นความผิดพลาดคนละประเภท ข้อความที่แยกสาเหตุช่วยให้ผู้ใช้แก้ข้อมูลได้ตรงจุด
scanf คืนจำนวนรายการที่แปลงและกำหนดค่าได้สำเร็จ ไม่ใช่ผลรวมของค่าที่อ่าน และไม่ใช่จำนวนอักขระที่พิมพ์ ส่วน & หน้าตัวแปรจำนวนในตัวอย่างบอกตำแหน่งที่จะให้ฟังก์ชันเขียนค่า ยังไม่จำเป็นต้องเรียนพอยน์เตอร์เต็มรูปแบบเพื่อใช้แบบแผนนี้อย่างระมัดระวัง glibc: Formatted Input Basics อธิบายทั้งค่าคืนและปัญหาการฟื้นตัวหลังอ่านไม่ตรงรูปแบบ
3.5.2 ลองข้อมูลที่คล้ายกันแต่ผลต่างกัน
| ข้อมูลเข้า | สิ่งที่การอ่านสองจำนวนเต็มทำได้ | สิ่งที่โปรแกรมตัวอย่างทำต่อ |
|---|---|---|
17 3 | อ่านได้ 2 ค่า | คำนวณและรายงาน |
17 แล้วจบข้อมูล | อ่านได้เพียง 1 ค่า | รายงาน input error |
x 3 | อ่านตัวแรกไม่ได้ | รายงาน input error |
17 0 | อ่านได้ 2 ค่า | รายงาน range error |
17 3 extra | อ่านได้ 2 ค่า โดยยังมีข้อความเหลือ | ตัวอย่างนี้ยังคำนวณได้ |
แถวสุดท้ายแสดงข้อจำกัดสำคัญ การตรวจว่าคืน 2 ไม่ได้ตรวจว่าทั้งบรรทัดมีเพียงสองค่า หากข้อกำหนดต้องปฏิเสธข้อความเกิน ต้องออกแบบการอ่านทั้งบรรทัดแล้วแยกวิเคราะห์เพิ่มเติม ตัวอย่างนี้จึงไม่ควรถูกโฆษณาว่าเป็นตัวตรวจข้อมูลเข้าที่สมบูรณ์สำหรับทุกกรณี
อีกข้อจำกัดคือการแปลงเลขที่ใหญ่มากเกินช่วงชนิดด้วย scanf ไม่ใช่วิธีตรวจช่วงที่เชื่อถือได้ ตัวอย่างกำหนดให้ข้อมูลทดสอบเป็นจำนวนที่แทนด้วย int ได้ก่อน แล้วตรวจช่วงใช้งาน 0–10000 ภายในโปรแกรม ระบบที่ต้องรับข้อความใด ๆ อย่างทนทานควรอ่านเป็นข้อความและใช้ตัวแปลงที่ตรวจข้อผิดพลาดได้ ซึ่งเป็นหัวข้อขยายหลังมีพื้นฐานสตริง ไม่ต้องเพิ่มเครื่องมือใหม่ในบทนี้
3.6 อ่าน format string เป็นสัญญาระหว่างข้อมูลกับฟังก์ชัน
3.6.1 เหตุใด printf กับ scanf จึงใช้รูปแบบไม่เหมือนกันทุกจุด
การแสดง double ด้วย printf ใช้ %f แต่การอ่านเข้า double ด้วย scanf ใช้ %lf อย่าสร้างกฎว่า “double ต้องใช้ lf ทุกครั้ง” เพราะฟังก์ชันทั้งสองมีสัญญาคนละแบบ สำหรับ printf ค่า float ที่ส่งผ่านส่วนอาร์กิวเมนต์แปรผันจะถูกเลื่อนชนิดเป็น double ส่วน scanf ต้องรู้ขนาดและชนิดของปลายทางที่เขียนลงไป
ให้เขียนตารางคู่ชนิดของตัวแปรและรูปแบบที่ใช้จริง เช่น int กับ %d, float กับ %f สำหรับการอ่าน และ double กับ %lf สำหรับการอ่าน การใช้รูปแบบผิดไม่ได้เป็นเพียงการจัดหน้าผิด แต่อาจทำให้พฤติกรรมโปรแกรมไม่ถูกกำหนด จึงไม่ควรทดลองรันแบบผิดแล้วนำผลที่ได้ครั้งหนึ่งไปอธิบายเป็นกฎภาษา
3.6.2 ความกว้างเป็นขั้นต่ำ ไม่ใช่กรรไกรตัดค่า
ถ้าแสดง printf("[%5d]", 17); จะมีช่องว่างนำหน้าสามช่อง แต่ถ้าค่ามีหกหลัก ความกว้าง 5 ไม่ได้ตัดให้เหลือห้าหลัก แถวตารางอาจขยายและเสียแนวได้ การออกแบบตารางจึงต้องคิดช่วงข้อมูล ไม่ใช่ทดสอบเพียงค่าตัวอย่างสั้นที่สุด
#include <stdio.h>
int main(void)
{
printf("|%6s|%6s|\n", "kits", "left");
printf("|%6d|%6d|\n", 5, 2);
printf("|%6d|%6d|\n", 120, 0);
printf("price=%.2f\n", 12.5);
return 0;
}ผลแถวข้อมูลมีเลขชิดขวาในพื้นที่อย่างน้อยหกช่อง และราคาเป็น 12.50 ให้เปรียบเทียบ %6d กับ %-6d โดยทำนายตำแหน่งช่องว่างก่อนรัน สังเกตว่าสัญลักษณ์ - ใน format นี้ควบคุมการจัดชิด ไม่ได้เปลี่ยนเลขให้ติดลบ
ใบงาน 5: เดินตามตำแหน่งอ่านช่องว่างและอักขระ
%c อ่านอักขระถัดไปโดยไม่ข้าม whitespace ก่อน แต่ช่องว่างใน format string สั่งให้ข้าม whitespace ก่อนส่วนถัดไป โปรแกรมนี้อ่านจำนวนเต็ม ตามด้วยอักขระดิบหนึ่งตัว แล้วจึงข้าม whitespace เพื่ออ่านอักขระที่เลือก
/* whitespace_trace.c: Compare a raw character with a spaced conversion. */
#include <stdio.h>
int main(void)
{
int value = 0;
char raw = '?';
char selected = '?';
int assigned = scanf("%d%c %c", &value, &raw, &selected);
if (assigned != 3) {
puts("Input error");
return 1;
}
printf("value=%d\n", value);
printf("raw=[%c]\n", raw);
printf("selected=[%c]\n", selected);
return 0;
}ทดลองข้อมูล 42 ตามด้วย Enter แล้ว Z ในบรรทัดถัดไป เปรียบเทียบกับการป้อน 42 Z บรรทัดเดียว ให้เขียนลำดับอักขระที่ถูกอ่านก่อนดูผล ตัวแปร raw อาจเก็บ newline หรือช่องว่างซึ่งทำให้ข้อความระหว่างขอบดูต่างกัน
| ข้อมูลเข้า | สิ่งที่ %d อ่าน | อักขระที่ %c แรกอ่าน | สิ่งที่ช่องว่างในรูปแบบข้าม | อักขระตัวสุดท้าย |
|---|---|---|---|---|
42\nZ\n | บันทึกก่อนรัน | บันทึกก่อนรัน | บันทึกก่อนรัน | บันทึกก่อนรัน |
42 Z\n | บันทึกก่อนรัน | บันทึกก่อนรัน | บันทึกก่อนรัน | บันทึกก่อนรัน |
42\t Z\n | บันทึกก่อนรัน | บันทึกก่อนรัน | บันทึกก่อนรัน | บันทึกก่อนรัน |
กรณี tab ให้เพิ่มช่องว่างสองตัวก่อน Z เพื่อสังเกตส่วนที่รูปแบบข้าม ในตาราง \n และ \t เป็นสัญลักษณ์แทน newline และ tab ไม่ใช่การพิมพ์เครื่องหมาย backslash ลงใน input จริง หากแพลตฟอร์มให้กรอกข้อมูลหลายบรรทัด ให้ป้อนตามโครงสร้างนั้น ไม่ใส่ newline ท้ายรูปแบบ scanf โดยเดาว่าต้องทำเหมือน printf
3.7 เวิร์กช็อปขยาย: ออกแบบบทสนทนาของโปรแกรมและแฟ้มกรณีทดสอบ
ออกแบบใบสรุปการซื้ออุปกรณ์สมมติ รับจำนวนชิ้นและราคาต่อชิ้นในหน่วยสตางค์ โดยใช้ค่าขนาดเล็กที่แทนผลคูณได้แน่นอน ระบุข้อความแจ้งให้ทราบว่าราคาที่ต้องป้อนเป็นสตางค์ เพราะการขอ “ราคา” โดยไม่บอกหน่วยอาจทำให้ผู้ใช้ป้อน 12.50 ในช่องที่คาดจำนวนเต็ม
แยกขั้นทำงานเป็นสามรอบ รอบแรกพิมพ์ผลจากค่าคงที่เพื่อจัดรูปแบบให้ถูก รอบสองเพิ่มการรับข้อมูลพร้อมตรวจจำนวนค่าที่อ่าน รอบสามเพิ่มการตรวจขอบเขตและหน่วย วิธีนี้ช่วยระบุได้ว่าความผิดเกิดจากสูตร รูปแบบแสดง หรือการรับข้อมูล แทนการแก้ทั้งโปรแกรมพร้อมกัน
เตรียมข้อมูลทดสอบอย่างน้อยกรณีจำนวนเป็นศูนย์ จำนวนปกติ ราคาที่มีสตางค์ ข้อมูลขาดหนึ่งค่า และข้อความแทนตัวเลข เขียนผลที่คาดหวังก่อนรัน แล้วบันทึกทั้ง input และ output จริง หากแพลตฟอร์มตรวจ output อัตโนมัติ ให้ใช้ข้อความตามโจทย์บนแพลตฟอร์ม ไม่เพิ่ม prompt ที่ระบบไม่ได้กำหนด
ในการอธิบายงาน ให้ชี้ว่าค่าใดอยู่ในตัวแปรหลังอ่านแต่ละขั้นและเมื่ออ่านไม่ครบ โปรแกรมหยุดตรงไหน จากนั้นให้เพื่อนเปลี่ยนข้อมูลโดยไม่แก้โค้ด หากต้องแก้ตัวเลขในซอร์สทุกครั้ง แสดงว่ายังไม่ได้เปลี่ยนจากโปรแกรมค่าคงที่เป็นโปรแกรมรับข้อมูลอย่างสมบูรณ์ งานนี้จะเป็นฐานให้เพิ่มเงื่อนไขหลายกรณีในบทต่อไป
3.8 ตัวอย่างเปรียบเทียบ: รายงานเดียวกันสำหรับคนอ่านและระบบตรวจ
สมมติผลคำนวณจำนวนชุดเป็น 5 และของเหลือเป็น 2 สำหรับผู้ใช้ทั่วไปข้อความ Complete kits: 5 กับ Leftover cables: 2 ช่วยตีความ แต่ถ้าโจทย์ระบบตรวจระบุให้พิมพ์ 5 2 เท่านั้น การเพิ่มคำอธิบายอาจทำให้ผลไม่ตรงรูปแบบที่กำหนด ทั้งสองรูปแบบใช้ค่าเดียวกันแต่มีผู้รับผลต่างกัน
ก่อนเขียน printf ให้อ่านข้อกำหนดว่าใช้กี่บรรทัด คั่นด้วยอะไร ต้องแสดงทศนิยมกี่ตำแหน่ง และต้องมีข้อความกำกับหรือไม่ อย่าใช้ภาพผลลัพธ์หนึ่งภาพแทนข้อกำหนดทั้งหมด เพราะช่องว่างท้ายบรรทัดหรือ newline อาจมองไม่เห็นชัด
ลองทำตารางรายงานรหัสอุปกรณ์ จำนวนชุด และราคา แต่ละคอลัมน์มีความหมายต่างกัน รหัสอาจต้องมีศูนย์นำหน้า จำนวนชุดเป็นจำนวนเต็ม และราคาแสดงสองตำแหน่ง การใช้รูปแบบเดียวกันทุกคอลัมน์ทำให้ข้อมูลบางประเภทเสียความหมาย แม้ทุกแถวจะตรงแนว
สำหรับการรับข้อมูล ให้ซ้อมอ่านจากกระแสข้อมูลทีละขั้น สมมติ input เป็น 12 8\n โดย \n แทน newline การอ่าน %d ครั้งแรกรับ 12 และหยุดก่อนช่องว่าง การอ่านจำนวนถัดไปข้าม whitespace แล้วรับ 8 แต่การอ่าน %c ถัดมาไม่ได้มีพฤติกรรมข้าม whitespace เหมือนการอ่านจำนวน หากต้องอ่านตัวอักษรที่ไม่ใช่ช่องว่างตามโจทย์ การมีช่องว่างนำในรูปแบบ " %c" จึงมีความหมาย
อย่างไรก็ตาม ช่องว่างท้ายรูปแบบ เช่น "%d\n" ไม่ได้แปลว่า “อ่านเลขและกด Enter แล้วจบทันที” เสมอไป มันสั่งให้ข้าม whitespace ต่อจนพบสิ่งอื่นหรือจบข้อมูล จึงอาจทำให้โปรแกรมโต้ตอบดูเหมือนรอเพิ่ม อย่าเติม newline ใน scanf ตามรูปแบบของ printf โดยไม่ได้อ่านสัญญาของฟังก์ชัน
เวิร์กช็อปส่วนนี้ให้เขียนสิ่งที่ถูกอ่านกับสิ่งที่เหลือหลังแต่ละคำสั่ง ไม่ต้องแสดงตัวอย่างการใช้ format ผิดชนิดที่ทำให้เกิด undefined behavior การทดลองที่มีประโยชน์คือเปลี่ยนรูปแบบอย่างถูกชนิดและทำนายพฤติกรรมจากกติกา ไม่ใช่รันโค้ดที่ผิดแล้วจำอาการเฉพาะเครื่อง
3.9 อ่านโค้ดที่ผิดและทำความเข้าใจข้อความวินิจฉัย
ตัวอย่างต่อไปนี้เป็นโค้ดฝึกแยกจากงานที่ต้องส่ง แสดงข้อผิดพลาดที่สัมพันธ์กับเนื้อหาบท พร้อมข้อความวินิจฉัยจริง วิธีอ่าน และฉบับแก้ คำว่า diagnostic ใช้รวมข้อความ error, warning และ note จึงไม่ควรเรียกทุกบรรทัดว่า error หรือถือว่าการไม่มี error แปลว่าโปรแกรมถูกตามโจทย์
ที่มาของ log: ผู้เขียนทดสอบด้วย GCC 11.4.0 บน Ubuntu ผ่าน Linux environment เมื่อ 24 ตุลาคม 2569 ใช้ C11 และตัวเลือกดังนี้ ชื่อไฟล์ เลขบรรทัด คอลัมน์ ถ้อยคำ และคำเตือนที่เปิดใช้อาจต่างจากแพลตฟอร์มการเรียน นักศึกษาไม่จำเป็นต้องเปลี่ยนเครื่องมือหรือการติดตั้งตามชุดทดสอบของผู้เขียน
-std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0ข้อความรูปแบบ bad.c:5:17 อ่านว่าไฟล์ bad.c บรรทัด 5 คอลัมน์ 17 ส่วน ^ และ ~ ช่วยชี้บริเวณที่ตรวจพบ จุดนี้อาจเป็นปลายเหตุจากบรรทัดก่อนหน้า error ทำให้การสร้างโปรแกรมล้มในกรณีนั้น warning เตือนความเสี่ยงแม้คำสั่งสร้างอาจจบสำเร็จ และ note ให้บริบท เช่น declaration เดิม ต้องอ่านคู่กัน (GCC: Diagnostics)
ตัวอย่างที่ผิด 1: printf ใช้รูปแบบไม่ตรงชนิดข้อมูล
ประเภท: คำเตือนเกี่ยวกับโค้ดที่ไม่ควรรัน
%d ต้องการ int แต่ argument เป็น double การเรียกแบบนี้ไม่ใช่การแปลง double เป็น int อย่างถูกต้อง เป็นการส่งชนิดไม่ตรงกับสัญญาของ variadic function และมี undefined behavior จึงไม่รันโปรแกรมที่ผิดและไม่รับรองตัวเลขที่จะพิมพ์
โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้
#include <stdio.h>
int main(void)
{
double price = 12.5;
printf("price=%d\n", price);
return 0;
}คำสั่งสร้างและข้อความที่ได้จริง
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o badbad.c: In function 'main':
bad.c:5:20: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
5 | printf("price=%d\n", price);
| ~^ ~~~~~
| | |
| int double
| %fสถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 0 แม้เป็นศูนย์ แต่ยังมีคำเตือนชนิด argument ที่สัมพันธ์กับ undefined behavior จึงไม่ได้รันฉบับผิด และไม่กำหนดว่ามันต้องแสดงเลขใดหรือต้อง crash แบบใด
อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร
format ... expects argument of type บอกชนิดที่รูปแบบต้องการ ส่วน argument ... has type บอกชนิดที่ส่งจริง เลข argument นับรวม format string ในการเรียก จึงต้องจับคู่ตัวระบุกับรายการข้อมูลอย่างเป็นลำดับ [-Wformat=] ระบุกลุ่มคำเตือน
โค้ดที่แก้แล้ว
#include <stdio.h>
int main(void)
{
double price = 12.5;
printf("price=%.2f\n", price);
return 0;
}เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ
ใช้ %.2f เพื่อแสดง double สองตำแหน่งทศนิยมแล้วคอมไพล์ให้ไม่มี warning หากโจทย์ต้องการจำนวนเต็มจริงต้องออกแบบการแปลงและการปัดเศษอย่างชัดเจน ไม่เปลี่ยน format เพียงเพราะต้องการซ่อน warning
ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง
price=12.50ตัวอย่างที่ผิด 2: scanf ไม่ส่งที่อยู่ของตัวแปร
ประเภท: คำเตือนเกี่ยวกับโค้ดที่ไม่ควรรัน
scanf ต้องรู้ตำแหน่งที่จะเขียน int จึงต้องส่ง &score ไม่ใช่ค่าของ score กรณีนี้สัมพันธ์กับการใช้เครื่องหมาย & ในบทรับเข้า ยังไม่ต้องขยายไปสู่การจองหน่วยความจำ โค้ดผิดอาจทำให้เกิด undefined behavior เมื่อมี conversion สำเร็จ จึงหยุดที่การอ่าน warning
โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้
#include <stdio.h>
int main(void)
{
int score = 0;
if (scanf("%d", score) != 1) {
puts("Input error");
return 1;
}
printf("score=%d\n", score);
return 0;
}คำสั่งสร้างและข้อความที่ได้จริง
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o badbad.c: In function 'main':
bad.c:5:17: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
5 | if (scanf("%d", score) != 1) {
| ~^ ~~~~~
| | |
| | int
| int *สถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 0 แม้เป็นศูนย์ แต่ยังมีคำเตือนชนิด argument ที่สัมพันธ์กับ undefined behavior จึงไม่ได้รันฉบับผิด และไม่กำหนดว่ามันต้องแสดงเลขใดหรือต้อง crash แบบใด
อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร
อ่านความต่างระหว่าง int * ที่ต้องการกับ int ที่ส่งจริง เครื่องหมาย * ในข้อความชนิดหมายถึง pointer ไม่ใช่การคูณ ตัว compiler กำลังตรวจชนิด argument ไม่ได้แจ้งว่าคะแนนที่ป้อนเกินช่วง
โค้ดที่แก้แล้ว
#include <stdio.h>
int main(void)
{
int score = 0;
if (scanf("%d", &score) != 1) {
puts("Input error");
return 1;
}
printf("score=%d\n", score);
return 0;
}เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ
เพิ่ม & ที่ตัวแปรรับค่าและคงการตรวจค่าคืนของ scanf ทดสอบฉบับแก้ด้วย input 80 แล้วได้ score=80 ส่วน input ที่แปลงเป็นจำนวนไม่ได้ต้องเข้าสาขา Input error ไม่ใช้ค่าที่ยังรับไม่สำเร็จ
ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง
score=80ทดลองฉบับแก้ด้วย not-a-number ตามด้วย newline เพิ่มเติม ได้ Input error และสถานะ 1 จึงแยกข้อมูลที่แปลงไม่ได้ออกจากกรณีรับคะแนนสำเร็จ
ลำดับการอ่าน log เมื่อลองโปรแกรมของบทนี้
- ระบุว่าเกิดในขั้น compile, link, run หรือ test และแยกข้อความจากระบบออกจากข้อความที่โปรแกรมพิมพ์เอง
- อ่าน diagnostic แรกกับบริบทบรรทัดก่อนหน้า แล้วใช้ note เพื่อเทียบ declaration หรือชนิดข้อมูล อย่าแก้ทุกข้อความตามลำดับโดยไม่หาสาเหตุร่วม
- แก้ทีละสาเหตุ สร้างโปรแกรมใหม่ และตรวจว่ากำลังรันผล build ใหม่ การเติมวงเล็บ ปิด warning หรือลบ test อาจทำให้ log เงียบโดยยังผิดโจทย์
- ใช้ข้อมูลเดิมที่ทำให้ผิดทดสอบซ้ำ แล้วเพิ่มกรณีขอบเขตที่สัมพันธ์กับบท การรันจบสถานะศูนย์เพียงครั้งเดียวไม่แทนการตรวจข้อกำหนดครบถ้วน
ความหมายตามภาษาอ้างจากร่างมาตรฐาน C11 โดยข้อความ diagnostic เฉพาะถ้อยคำเป็นของเครื่องมือที่ใช้ ไม่ได้กำหนดให้ compiler ทุกตัวพิมพ์เหมือนกัน (WG14 N1570, GCC: Warning Options)
ปฏิบัติการที่ 3 การรับเข้าและการแสดงผล
ปฏิบัติการนี้แบ่งเป็นสองส่วน ส่วนแรกฝึกใช้ตัวระบุรูปแบบและการจัดรูปแบบผลลัพธ์ ส่วนที่สองฝึกรับข้อมูลหลายชนิดด้วย scanf แล้วต่อยอดเป็นการประมวลผล ทั้งสองส่วนทำแยกกันได้ แต่ควรทำตามลำดับเพื่อให้เห็นวงจรรับเข้า–ประมวลผล–แสดงผลครบถ้วน
ไฟล์ซอร์สโค้ดในปฏิบัติการนี้ให้ตั้งชื่อเป็นภาษาอังกฤษแบบ snake_case และบันทึกไว้ในโฟลเดอร์ของผู้เรียนเอง คำสั่งคอมไพล์ในเอกสารนี้ทดสอบด้วย gcc 13.3.0 โดยการรับข้อมูลทดสอบด้วยการป้อนค่าผ่านทางเข้ามาตรฐาน
Lab A: ตัวระบุรูปแบบและการจัดรูปแบบผลลัพธ์
สถานการณ์ ต้องการแสดงจำนวนเต็มและจำนวนจริงด้วยความกว้าง การจัดชิด และจำนวนทศนิยมแบบต่าง ๆ เพื่อฝึกควบคุมรูปแบบการแสดงผลให้เป็นระเบียบ
เป้าหมาย ฝึก LLO3.1 คือเลือกตัวระบุรูปแบบให้ตรงชนิดข้อมูลและจัดรูปแบบผลลัพธ์ตามที่กำหนด
ขั้นปฏิบัติ
- สร้างไฟล์ชื่อ
ch3_fmt.cด้วยโปรแกรมแก้ไขข้อความหรือ IDE - พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
/* ch3_fmt.c : ตัวระบุรูปแบบและการจัดรูปแบบผลลัพธ์ */
#include <stdio.h>
int main(void) {
int n = 42;
double pi = 3.14159265;
printf("%d in width 6: |%6d|\n", n, n);
printf("left-aligned: |%-6d|\n", n);
printf("zero-padded: |%06d|\n", n);
printf("pi 2 digits: %.2f\n", pi);
printf("pi width8.3: |%8.3f|\n", pi);
printf("percent sign: 100%%\n");
return 0;
}- คอมไพล์และรันด้วยคำสั่ง
gcc -std=c11 -Wall ch3_fmt.c -o ch3_fmt
./ch3_fmtผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)
42 in width 6: | 42|
left-aligned: |42 |
zero-padded: |000042|
pi 2 digits: 3.14
pi width8.3: | 3.142|
percent sign: 100%สังเกตว่าค่าเดียวกันคือ 42 ปรากฏต่างกันตามรูปแบบที่กำหนด ทั้งการชิดขวา ชิดซ้าย และเติมศูนย์ ส่วนค่า pi แสดงด้วยจำนวนทศนิยมและความกว้างที่ต่างกัน
ตัดสินใจเอง A.1 ออกแบบการแสดงใบเสร็จอย่างง่ายที่มีชื่อรายการชิดซ้ายและราคาชิดขวาด้วยทศนิยม 2 ตำแหน่ง โดยเลือกความกว้างของแต่ละช่องเอง แล้วให้เหตุผลว่าเหตุใดจึงเลือกความกว้างนั้น
ผลลัพธ์ที่คาดหวัง A.1 โปรแกรมคอมไพล์ผ่านและแสดงรายการเรียงตรงหลักกัน ชื่อรายการชิดซ้าย ราคาชิดขวา และทศนิยมคงที่ 2 ตำแหน่งทุกบรรทัด
จุดตรวจ A เลือกตัวระบุรูปแบบตรงชนิดข้อมูล ผลลัพธ์จัดรูปแบบตรงตามความกว้าง การจัดชิด และจำนวนทศนิยมที่กำหนด และโค้ดมีการจัดย่อหน้าและคอมเมนต์ส่วนหัว
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| ค่าจำนวนจริงแสดงเพี้ยน | ใช้ %d กับค่าชนิด double | เปลี่ยนเป็น %f หรือ %.2f ให้ตรงชนิดข้อมูล |
| ทศนิยมไม่แสดงตามจำนวนที่ต้องการ | ลืมจุดและตัวเลขใน %.2f | ระบุจำนวนทศนิยมหลังจุด เช่น %.2f |
| คอลัมน์ไม่เรียงตรงหลัก | ไม่กำหนดความกว้างของช่อง | เพิ่มความกว้าง เช่น %8.2f ให้ทุกบรรทัดเท่ากัน |
เปิดหลังผู้เรียนออกแบบด้วยตนเองแล้ว
ใบงานตรวจ Lab A: ออกแบบรูปแบบก่อนเขียนคำสั่ง
ร่างใบเสร็จสมมติสองคอลัมน์ก่อนเขียนโค้ด ใช้ชื่อรายการ ASCII สั้นและราคาที่มีทั้งค่าจำนวนเต็มกับส่วนสตางค์ ระบุความกว้าง การจัดชิด และจำนวนทศนิยม แล้วเลือกค่าหนึ่งที่กว้างเกินช่องเพื่อดูข้อจำกัดของการออกแบบ
| สิ่งที่ออกแบบ | ข้อกำหนดที่คุณเลือก | ผลที่คาด | ผลจริง | สิ่งที่ต้องปรับ |
|---|---|---|---|---|
| ชื่อรายการ | ความกว้างและชิดซ้าย | ก่อนรัน | หลังรัน | เหตุผล |
| ราคา | ความกว้างและทศนิยม | ก่อนรัน | หลังรัน | เหตุผล |
| ค่าที่กว้างเกินช่อง | ระบุข้อมูลทดสอบ | ก่อนรัน | หลังรัน | ขยายช่องหรือกำหนดช่วงข้อมูล |
| ข้อความเปอร์เซ็นต์ | รูปแบบที่เลือก | ก่อนรัน | หลังรัน | ตรวจ %% |
หากระบบตรวจผลอัตโนมัติกำหนดข้อความและช่องว่างไว้ ให้ทำตามสัญญาของโจทย์ ไม่เพิ่ม prompt หรือหัวรายงานที่ระบบไม่ได้ขอ งานออกแบบใบเสร็จในกิจกรรมนี้ให้ยึดข้อกำหนดที่ผู้สอนให้ไว้เดิม
Lab B: รับข้อมูลด้วย scanf และคำนวณค่าดัชนีมวลกาย
สถานการณ์ สร้างโปรแกรมที่รับชื่อ อายุ ส่วนสูง และอักษรย่อ แล้วแสดงผลกลับ จากนั้นต่อยอดให้รับน้ำหนักเพิ่มเพื่อคำนวณค่าดัชนีมวลกาย (BMI) โดยใช้ความรู้นิพจน์จากบทที่ 2
เป้าหมาย ฝึก LLO3.2 และ LLO3.3 คือรับข้อมูลหลายชนิดด้วย scanf โดยใช้ & ถูกต้อง และเขียนโปรแกรมรับเข้า–ประมวลผล–แสดงผลที่คอมไพล์ผ่าน
ขั้นปฏิบัติ
- สร้างไฟล์ชื่อ
ch3_io.cด้วยโปรแกรมแก้ไขข้อความหรือ IDE - พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
/* ch3_io.c : การรับเข้าและการแสดงผล */
#include <stdio.h>
int main(void) {
int age;
double height;
char initial;
char name[20];
printf("ป้อนชื่อ อายุ ส่วนสูง และอักษรย่อ: ");
if (scanf("%19s %d %lf %c", name, &age, &height, &initial) != 4) {
puts("Input error");
return 1;
}
printf("ชื่อ: %s\n", name);
printf("อายุ: %d ปี\n", age);
printf("ส่วนสูง: %.1f เมตร\n", height);
printf("อักษรย่อ: %c\n", initial);
return 0;
}- คอมไพล์และรันด้วยคำสั่ง แล้วป้อนข้อมูลทดสอบ
Somchai 20 1.75 S
gcc -std=c11 -Wall ch3_io.c -o ch3_io
./ch3_ioผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0 ป้อนค่า Somchai 20 1.75 S)
ป้อนชื่อ อายุ ส่วนสูง และอักษรย่อ: ชื่อ: Somchai
อายุ: 20 ปี
ส่วนสูง: 1.8 เมตร
อักษรย่อ: Sค่าส่วนสูง 1.75 แสดงเป็น 1.8 เพราะ %.1f ปัดค่าเป็นทศนิยมหนึ่งตำแหน่งตามที่อธิบายในหัวข้อ 3.2 ส่วนตัวแปร name ไม่มี & เพราะเป็นสตริง ขณะที่ age และ height ต้องมี & เพราะเป็นตัวแปรเดี่ยว และ height อ่านด้วย %lf เพราะเป็นชนิด double
ตัดสินใจเอง B.1 ต่อยอดโปรแกรมโดยเพิ่มการรับน้ำหนัก (กิโลกรัม) แล้วคำนวณค่าดัชนีมวลกายจากนิพจน์ น้ำหนัก หารด้วย ส่วนสูงยกกำลังสอง และแสดงผลด้วยทศนิยม 2 ตำแหน่ง โดยเลือกชื่อตัวแปรและตัวระบุรูปแบบให้เหมาะสม
ผลลัพธ์ที่คาดหวัง B.1 โปรแกรมรับค่าครบทุกชนิดรวมถึงน้ำหนัก ใช้ & และ %lf ถูกต้อง คอมไพล์ผ่านไม่มีการแจ้งเตือน และแสดงค่าดัชนีมวลกายด้วยทศนิยม 2 ตำแหน่งตามนิพจน์ที่กำหนด (ผลที่คาดว่าจะเห็นสำหรับโปรแกรมที่ผู้เรียนออกแบบเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับการออกแบบของแต่ละคน)
จุดตรวจ B รับค่าครบทุกชนิด ใช้ & ถูกต้องกับตัวแปรที่ไม่ใช่สตริง อ่าน double ด้วย %lf คอมไพล์ผ่านไม่มีการแจ้งเตือน และผลการคำนวณถูกต้องตามนิพจน์
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| โปรแกรมรับค่าผิดหรือค้าง | ลืมใส่ & หน้า age หรือ height | เติม & หน้าตัวแปรเดี่ยวทุกตัว |
| ค่า double ที่รับเพี้ยน | ใช้ %f แทน %lf ขณะอ่าน double | เปลี่ยนเป็น %lf ในสายอักขระของ scanf |
| ข้อความที่รับขาดหายหรือล้น | อาร์เรย์เล็กเกินข้อความ หรือไม่จำกัดความกว้าง | กำหนดอาร์เรย์ให้ใหญ่พอและใช้ %19s กันล้น |
เปิดหลังผู้เรียนส่งงานแล้ว
ใบงานตรวจ Lab B: ลำดับข้อมูล หน่วย และผลการอ่าน
กำหนดข้อมูลสมมติหนึ่งชุดในลำดับ ชื่อหนึ่งคำ อายุ ส่วนสูงเป็นเมตร และอักษรย่อ ก่อนรันให้ชี้ format specifier กับตัวแปรปลายทางแต่ละช่อง แล้วทดสอบกรณีอ่านครบและข้อมูลขาดโดยให้โปรแกรมหยุดก่อนใช้ค่าที่อ่านไม่สำเร็จ
| รอบทดลอง | สิ่งที่ต้องบันทึก |
|---|---|
| ข้อมูลปกติ | ข้อความ input ลำดับค่า และผลที่คาด |
| ชนิดไม่ตรง เช่น อายุเป็นข้อความ | จำนวนรายการที่คาดว่าอ่านได้และจุดหยุด |
| ข้อมูลขาดแล้วจบ input | โปรแกรมต้องไม่แสดงค่าตัวแปรที่ยังอ่านไม่สำเร็จ |
| ชื่อยาวเกินหรือมีช่องว่าง | ชี้ว่าส่วนใดถูกอ่านเข้าช่องถัดไปหรือทำให้อ่านต่อไม่สำเร็จ |
| ต่อขยายคำนวณ | ชนิด หน่วย และสูตรจากค่าที่เก็บจริง |
สำหรับงานคำนวณดัชนีมวลกาย ใช้ตัวเลขสมมติเพื่อฝึกนิพจน์เท่านั้น กำหนดน้ำหนักเป็นกิโลกรัมและส่วนสูงเป็นเมตรที่มากกว่าศูนย์ โดยไม่แปลผลสุขภาพ ค่าที่แสดงหนึ่งตำแหน่งด้วย %.1f ไม่ได้ทำให้ตัวแปรส่วนสูงถูกปัดตามหน้าจอเอง ให้คำนวณจากตัวแปรเดิมและระบุขอบเขตข้อมูลที่งานยอมรับ
เก็บกวาด
เมื่อจบปฏิบัติการ ให้ลบไฟล์ปฏิบัติการที่สร้างจากการคอมไพล์ (เช่น ch3_fmt และ ch3_io) และเก็บเฉพาะไฟล์ซอร์สโค้ด .c ไว้ เพื่อให้โฟลเดอร์งานสะอาดและส่งไฟล์ซอร์สโค้ด .c พร้อมบันทึกผลการรันตามที่กำหนด
ตรวจความเข้าใจก่อนเริ่มบทที่ 4
ตอบด้วยข้อมูลและผลจากการทดลองของตนเอง จุดตรวจนี้เป็นกิจกรรมระหว่างเรียน ไม่เพิ่มคะแนนหรือเงื่อนไขผ่านจากแผนเดิม
- เหตุใดการแสดง
doubleกับการอ่านdoubleจึงใช้สัญญาของรูปแบบต่างกัน - ความกว้าง
%3dทำอะไรเมื่อค่ามีห้าหลัก และทำไมไม่ควรถือว่าความกว้างตัดข้อมูล - การอ่านได้สองค่าบอกอะไร และยังไม่รับรองอะไรเกี่ยวกับทั้งบรรทัดหรือช่วงข้อมูล
- เหตุใด
%cหลังอ่านจำนวนจึงอาจได้ newline แทนตัวอักษรที่ตั้งใจ %5sกับบัฟเฟอร์หกช่องช่วยเรื่องใด และส่วนท้ายคำยาวหายไปเองหรือไม่- การปัดค่าบนจอส่งผลกับค่าที่เก็บไว้เพื่อนำไปคำนวณต่ออย่างไร
เก็บคำทำนาย ผลจริง และข้อจำกัดไว้ใช้ในบทที่ 4 โครงสร้างควบคุมแบบทางเลือก ซึ่งจะเรียนออกแบบเงื่อนไขเพื่อจัดการข้อมูลและทางเลือกของโปรแกรมอย่างเป็นระบบ
สรุป
บทนี้ประกอบการแสดงผลจากบทที่ 1 เข้ากับตัวแปรและชนิดข้อมูลจากบทที่ 2 ให้เป็นวงจรรับเข้า–ประมวลผล–แสดงผลที่สมบูรณ์ เริ่มจากตัวระบุรูปแบบของ printf (%d %f %c %s) และการแสดงเครื่องหมายเปอร์เซ็นต์ด้วย %% ต่อด้วยการจัดรูปแบบผลลัพธ์ด้วยความกว้าง การจัดชิด และจำนวนทศนิยม จากนั้นแนะนำคำสั่ง scanf สำหรับรับข้อมูลจากแป้นพิมพ์ บทบาทของเครื่องหมาย & การอ่าน double ด้วย %lf และการจำกัดความกว้างสตริงด้วย %19s เพื่อป้องกันข้อมูลล้น ทักษะเหล่านี้เป็นพื้นฐานที่จะใช้ต่อเนื่องเมื่อโปรแกรมต้องรับข้อมูลและแสดงผลในบทถัดไป
สิ่งที่ส่งต่อไปยังบทถัดไป
| สิ่งที่ส่งต่อ | ค่า/รูปแบบ | ใช้ในบท |
|---|---|---|
| ตัวระบุรูปแบบและการจัดรูปแบบ printf | %d %f %c %s, ความกว้าง/ทศนิยม/การจัดชิด | ทุกบทที่มีการแสดงผล |
| การรับข้อมูลด้วย scanf และการใช้ & | scanf("%d", &age) | บท 4–5 เป็นต้นไป |
| การอ่าน double ด้วย %lf | scanf("%lf", &height) | ทุกปฏิบัติการที่รับจำนวนจริง |
| การรับสตริงและจำกัดความกว้าง | %19s สำหรับอาร์เรย์ขนาด 20 | บท 7–8 (อาร์เรย์และสตริง) |
สรุปคำสั่ง
| คำสั่ง | หน้าที่ |
|---|---|
gcc -std=c11 -Wall ch3_fmt.c -o ch3_fmt | คอมไพล์ซอร์สโค้ด ch3_fmt.c เป็นไฟล์ปฏิบัติการ ch3_fmt พร้อมเปิดการแจ้งเตือน |
gcc -std=c11 -Wall ch3_io.c -o ch3_io | คอมไพล์ซอร์สโค้ด ch3_io.c เป็นไฟล์ปฏิบัติการ ch3_io พร้อมเปิดการแจ้งเตือน |
./ch3_fmt หรือ ./ch3_io | รันไฟล์ปฏิบัติการที่คอมไพล์แล้ว |
แบบฝึกหัดท้ายบท
ตอนที่ 1 ฝึกทบทวน (15 ข้อ ตรวจคำตอบให้ทันที)
ตอนที่ 2 แบบฝึกหัดเขียนตอบ (5 ข้อ)
กลุ่ม ก (เข้าใจ)
- ก1 (LLO3.1) จงอธิบายความหมายของตัวระบุรูปแบบ
%d,%f,%c,%sพร้อมระบุชนิดข้อมูลที่ใช้คู่กัน - ก2 (LLO3.1) จงอธิบายว่า
%6d,%-6dและ%06dต่างกันอย่างไรในการจัดรูปแบบผลลัพธ์
กลุ่ม ข (ประยุกต์)
- ข1 (LLO3.2) จงเขียนโปรแกรมภาษา C ที่รับจำนวนเต็ม 2 จำนวนด้วย
scanfแล้วแสดงผลรวม โดยใช้&ให้ถูกต้อง แล้วคอมไพล์และรันให้ได้ผลจริง - ข2 (LLO3.3) จงเขียนโปรแกรมที่รับส่วนสูง (เมตร) และน้ำหนัก (กิโลกรัม) ด้วย
scanfแล้วคำนวณและแสดงค่าดัชนีมวลกายด้วยทศนิยม 2 ตำแหน่ง
กลุ่ม ค (วิเคราะห์)
- ค1 (LLO3.3) จากโปรแกรมที่อ่านค่า double ด้วย
scanf("%f", &x)และลืม&หน้าตัวแปรจำนวนเต็ม จงวิเคราะห์ว่าเกิดข้อผิดพลาดใดได้บ้าง และเสนอวิธีแก้ให้ถูกต้อง
การศึกษาค้นคว้าเพิ่มเติม
- ศึกษาตัวระบุรูปแบบและตัวเลือกการจัดรูปแบบของ
printfและscanfเพิ่มเติมจาก Kernighan และ Ritchie (1988) บทที่ 7 ว่าด้วยการรับเข้าและการแสดงผล แล้วทดลองปรับความกว้างและทศนิยมในโปรแกรมตัวอย่าง - ทบทวนความแตกต่างระหว่าง
%fและ%lfในบริบทของprintfและscanfจากมาตรฐานภาษา C ตาม ISO/IEC 9899 เพื่อเข้าใจเหตุผลเชิงชนิดข้อมูล - ทดลองออกแบบการแสดงผลแบบตารางด้วยความกว้างคงที่ แล้วเปรียบเทียบความอ่านง่ายกับการแสดงผลที่ไม่จัดรูปแบบ
- เปิดหน้าอ้างอิงตระกูล
printfและscanfของ cppreference.com เพื่อศึกษารายการแฟล็ก ความกว้าง ความละเอียด และตัวปรับความยาว (length modifier) ให้ครบถ้วน แล้วทดลองตัวระบุรูปแบบที่ยังไม่ได้ใช้ในบทนี้ เช่น%xและ%e
เอกสารอ้างอิง
cppreference.com. (2024). fprintf, printf และ fscanf, scanf — C input/output reference. สืบค้นจาก https://en.cppreference.com/w/c/io/fprintf และ https://en.cppreference.com/w/c/io/fscanf
International Organization for Standardization. (2011). ISO/IEC 9899:2011 — Information technology — Programming languages — C. ISO.
Kernighan, B. W., & Ritchie, D. M. (1988). The C programming language (2nd ed.). Prentice Hall.
ตำราหลักภาษาไทยของรายวิชา. (ผู้สอนเติมรายละเอียดบรรณานุกรม — เพิ่มตามมติผู้สอน 21 ต.ค. 2569).
แหล่งปฐมภูมิที่ตรวจประกอบการปรับปรุง 23 ตุลาคม 2569
ใช้เอกสารข้างต้นตรวจหลักการก่อนเรียบเรียงส่วนขยายและสร้างข้อมูลสมมติ ตัวอย่างใหม่ที่เป็นโปรแกรม C ครบไฟล์ตรวจด้วย GCC 12.2.0 (Debian Linux) และ GCC 6.3.0 (MinGW-w64), C11 และคำเตือน -Wall -Wextra -Wpedantic เมื่อ 23 ตุลาคม 2569 แยกจากผลทดสอบ GCC 13.3.0 ของตัวอย่างเดิม ค่ารูปแบบและผลคำนวณตรวจจากการรัน ส่วนเวลา CPU ขึ้นกับสภาพแวดล้อม ไม่ใช่ผลตายตัว
แหล่งปฐมภูมิเพิ่มสำหรับชุดสอนพร้อมใช้บทที่ 3
- glibc: Formatted Input Basics — ค่าคืน การอ่านไม่สำเร็จ และตำแหน่งปลายทาง
- glibc: Input Conversion Syntax — whitespace และความกว้างสูงสุดของการอ่าน
- glibc: String Input Conversions — ความต่างของ %c กับ %s และตัวปิดสตริง
ตรวจเอกสารก่อนเขียนส่วนขยายเมื่อ 23 ตุลาคม 2569 ตัวอย่างที่เพิ่มการตรวจผลอ่านและโปรแกรมใหม่ตรวจด้วย GCC 12.2.0 (Debian Linux) และ GCC 6.3.0 (MinGW-w64) แยกจากผลตัวอย่างเดิมที่ระบุ GCC 13.3.0 ผลสำหรับข้อมูลปกติคงเดิม ส่วนข้อมูลอ่านไม่ครบจบก่อนนำค่าไปใช้ ขอบเขตการทดลองใช้ตัวเลขที่แทนด้วยชนิดปลายทางได้ ไม่อ้างว่า scanf ตรวจข้อความจำนวนใด ๆ ได้ครบทุกกรณี