Sign in

Part of 10306103 หลักการเขียนโปรแกรมเบื้องต้น (Fundamentals of Programming)

2026-10-22 · updated 2026-10-30 · 24 min read

บทที่ 2 ตัวแปร ชนิดข้อมูล ค่าคงที่ และนิพจน์

ShareFacebookXLinkedInLINE

แผนบริหารการสอนประจำบทที่ 2

บทนี้ต่อยอดจากบทที่ 1 ที่ได้ฝึกเขียน คอมไพล์ และรันโปรแกรมภาษา C โปรแกรมแรก โดยเจาะลึกหน่วยพื้นฐานที่ใช้สร้างโปรแกรมทุกตัว คือ ตัวแปร ชนิดข้อมูล ค่าคงที่ และนิพจน์ มุ่งให้ประกาศตัวแปรและเลือกชนิดข้อมูลได้เหมาะสม เขียนนิพจน์ด้วยตัวดำเนินการเลขคณิตอย่างถูกต้อง เข้าใจผลของการหารจำนวนเต็ม และแปลงชนิดข้อมูลเพื่อให้ผลลัพธ์ถูกต้อง การแสดงผลในบทนี้ใช้ printf ที่รู้จักแล้วจากบทที่ 1 ส่วนการรับข้อมูลเข้าด้วย scanf เป็นเนื้อหาของบทที่ 3

ผลลัพธ์ข้อความหลักฐานCLO
LLO2.1อธิบายและเลือกใช้ชนิดข้อมูลพื้นฐาน (int, float, double, char) และประกาศตัวแปร/ค่าคงที่ได้เหมาะสมงานข้อ 1 (ปฏิบัติการที่ 2 Lab A)CLO1
LLO2.2เขียนนิพจน์ด้วยตัวดำเนินการเลขคณิต (+ - * / %) และอธิบายลำดับความสำคัญและการหารจำนวนเต็มงานข้อ 2 (ปฏิบัติการที่ 2 Lab B)CLO1
LLO2.3เขียนโปรแกรมคำนวณและแสดงผลค่าตัวแปรได้ถูกต้องตามไวยากรณ์ รวมถึงการแปลงชนิดข้อมูล (type cast)งานข้อ 3 (ปฏิบัติการที่ 2 Lab B)CLO3
LLO2.4ปฏิบัติตามมาตรฐานการเขียนโค้ด (การจัดย่อหน้า การตั้งชื่อสื่อความหมาย การใส่คอมเมนต์) ในงานโค้ดของบทนี้โค้ดทุกข้อที่ส่งในบทนี้CLO5

ผลลัพธ์การเรียนรู้ของบทนี้ผู้สอนยืนยันแล้ว (มติผู้สอน 21 ต.ค. 2569) และเพิ่ม LLO2.4 มาตรฐานการเขียนโค้ดตามมติผู้สอน 28 ต.ค. 2569 (ดูบันทึกสำหรับผู้สอนท้ายบท)

CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):

  • CLO1 อธิบายและใช้ตัวแปร ชนิดข้อมูล การรับ-แสดงผล และโครงสร้างควบคุม (if/else, switch, loop) เพื่อแก้ปัญหาและได้ผลลัพธ์ที่ถูกต้อง
  • CLO3 สามารถเขียนโค้ดได้ถูกต้องตามหลักไวยากรณ์ (Syntax) และมีประสิทธิภาพ (Efficiency)

บทที่ 2 เป็นจุดตรวจต่อเนื่องของ CLO1 และ CLO3 ต่อจากบทที่ 1 งานทั้งสามข้อในบทนี้สะสมหลักฐานด้านการเลือกชนิดข้อมูลและการเขียนนิพจน์ที่ถูกต้อง ซึ่งเป็นพื้นฐานที่ส่งต่อไปยังการรับ-แสดงผลในบทที่ 3 และโครงสร้างควบคุมในบทที่ 4–5 ใบงานบทนี้คิด 10 คะแนนตามตารางผลลัพธ์ด้านล่าง และเป็นหนึ่งใน 13 ชิ้นที่มีน้ำหนักเท่ากันในหมวดใบงานบทที่ 1–13 รวม 15% ของคะแนนรายวิชา ตามแผนคะแนนฉบับที่ 4

วิธีสอน

  1. ทบทวนโปรแกรมภาษา C โปรแกรมแรกจากบทที่ 1 แล้วนำเข้าสู่คำถามว่าจะเก็บและคำนวณค่าต่าง ๆ ในโปรแกรมได้อย่างไร
  2. อธิบายแนวคิดตัวแปรและการประกาศด้วยรูปแบบ ชนิด ชื่อ = ค่า; พร้อมเชื่อมโยงกับมาตรฐานการตั้งชื่อแบบ snake_case จากบทที่ 1
  3. เปรียบเทียบชนิดข้อมูลพื้นฐานและค่าคงที่ด้วยตารางและตัวอย่างสั้น ๆ โดยชี้ให้เห็นการเลือกชนิดให้เหมาะกับข้อมูล
  4. สาธิตการเขียนนิพจน์และการรันโปรแกรม ch2_demo.c บนเครื่องจริง เน้นผลของการหารจำนวนเต็มและการแปลงชนิดข้อมูล
  5. ให้ลงมือปฏิบัติตามใบงาน โดยกำหนดจุดตรวจระหว่างทางเพื่อให้ข้อมูลป้อนกลับทันที

สื่อการเรียนการสอน

  • เครื่องคอมพิวเตอร์ที่เข้าถึงแพลตฟอร์มการเรียนของรายวิชาสำหรับเขียน คอมไพล์ และรันโปรแกรมภาษา C (มาตรฐาน C11) บนเว็บ
  • เอกสารประกอบบทที่ 2 และสไลด์สรุปชนิดข้อมูลและลำดับความสำคัญของตัวดำเนินการ
  • ไฟล์โปรแกรมตัวอย่าง ch2_demo.c ที่ทดสอบแล้ว

การวัดผลและการประเมินผล

กำหนดส่ง: งานข้อ 1–3 ภายใน 23:59 น. ของวันก่อนวันปฏิบัติการในสัปดาห์ที่ 3 ส่วนชุดฝึกท้ายบทและแบบฝึกหัดวัดความเข้าใจของบทนี้ ภายใน 23:59 น. ของวันก่อนวันบรรยายในสัปดาห์ที่ 3 ดูวันที่จริงของตอนเรียนได้ในห้องเรียนออนไลน์ (มติผู้สอน 28 ต.ค. 2569)

ข้องานหลักฐานผลลัพธ์CLO
1ประกาศตัวแปรชนิด int, double, char และค่าคงที่ const แล้วแสดงค่าด้วย printfไฟล์ซอร์สโค้ด + ผลการรันLLO2.1CLO1
2ทำนายและตรวจผลของนิพจน์ ได้แก่ การหารจำนวนเต็ม เศษเหลือ และลำดับความสำคัญตารางทำนาย + ผลการรันLLO2.2CLO1
3เขียนโปรแกรมคำนวณที่มีการแปลงชนิดข้อมูล (double cast) ให้คอมไพล์ผ่านและผลถูกต้องไฟล์ซอร์สโค้ด + ผลการรันLLO2.3CLO3

เงื่อนไขผ่าน: งานข้อ 3 ต้องคอมไพล์ผ่านโดยไม่มีข้อผิดพลาดก่อน จึงจะประเมินความถูกต้องของผลลัพธ์และมาตรฐานโค้ดได้

คะแนนใบงาน (10 คะแนน)

ผลลัพธ์สิ่งที่ตรวจคะแนน
LLO2.1งานข้อที่ระบุผลลัพธ์นี้3
LLO2.2งานข้อที่ระบุผลลัพธ์นี้3
LLO2.3งานข้อที่ระบุผลลัพธ์นี้3
LLO2.4มาตรฐานโค้ด ตรวจจากโค้ดทุกข้อที่ส่ง1

ใบงานบทที่ 1–13 รวมกันคิดเป็น 15% ของคะแนนรายวิชา ชิ้นละเท่ากัน ส่งช้าหัก 10% ของคะแนนงานต่อสัปดาห์ที่ช้า สูงสุด 50% และรับงานถึงสิ้นสัปดาห์ที่ 15 งานที่ผู้สอนส่งกลับให้แก้นับเวลาจากการส่งครั้งแรก ดูคะแนนของตนเองได้ที่หน้า “คะแนนของฉัน” ในห้องเรียนออนไลน์ของรายวิชา

เกณฑ์ให้คะแนน

งานข้อ 2 และงานข้อ 3 ใช้เกณฑ์ผ่าน/ไม่ผ่าน:

เกณฑ์ผ่านยังไม่ผ่าน
งานข้อ 2ทำนายผลนิพจน์ถูกต้องและผลการรันตรงกับที่ทำนายทำนายผิดหรือผลการรันไม่ตรง
งานข้อ 3คอมไพล์ผ่านไม่มีข้อผิดพลาด และผลลัพธ์เป็นทศนิยมที่ถูกต้องคอมไพล์ไม่ผ่านหรือผลถูกตัดเศษ

งานข้อ 1 ใช้รายการตรวจ (checklist): เลือกชนิดข้อมูลเหมาะกับค่าที่กำหนด, ประกาศตัวแปรและค่าคงที่ถูกไวยากรณ์, แสดงค่าครบทุกตัวด้วย printf, ตั้งชื่อสื่อความหมายแบบ snake_case

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

บทนำ

ทุกโปรแกรมต้องเก็บข้อมูลไว้ประมวลผล ไม่ว่าจะเป็นจำนวนเต็ม จำนวนที่มีทศนิยม หรืออักขระ ที่เก็บข้อมูลเหล่านี้เรียกว่า ตัวแปร (variable) และข้อมูลแต่ละอย่างมี ชนิดข้อมูล (data type) ที่กำหนดว่าจะเก็บค่าแบบใดและทำอะไรกับค่านั้นได้ บทนี้อธิบายการประกาศตัวแปร การเลือกชนิดข้อมูลพื้นฐาน การกำหนดค่าคงที่ และการเขียนนิพจน์ด้วยตัวดำเนินการเลขคณิต ซึ่งเป็นหน่วยพื้นฐานที่ใช้สร้างโปรแกรมที่ซับซ้อนขึ้นในบทถัดไป

เนื้อหาบทนี้ต่อยอดโดยตรงจากบทที่ 1 คือใช้โครงร่างโปรแกรมภาษา C ขั้นต่ำ คำสั่งคอมไพล์ และการแสดงผลด้วย printf ที่รู้จักแล้ว ส่วนชนิดข้อมูล ค่าคงที่ ตัวดำเนินการ และการแปลงชนิดข้อมูลที่เริ่มในบทนี้จะถูกใช้ซ้ำในการรับ-แสดงผลบทที่ 3 โครงสร้างควบคุมบทที่ 4–5 และตลอดรายวิชา

สิ่งที่ได้จากบทนี้ใช้ต่อในบท
ชนิดข้อมูลพื้นฐาน (int, float, double, char) และการประกาศตัวแปรบท 3 เป็นต้นไป (ทุกปฏิบัติการ)
ค่าคงที่ด้วย const และตัวดำเนินการเลขคณิตพร้อมลำดับความสำคัญบท 4–5 (เงื่อนไขและการวนซ้ำ)
การแปลงชนิดข้อมูล (type cast)บท 3 (การจัดรูปแบบการแสดงผล) เป็นต้นไป

ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์

บทนี้ต้องใช้คอมไพเลอร์ภาษา C และโปรแกรมแก้ไขข้อความหรือ IDE เช่นเดียวกับบทที่ 1 ตารางด้านล่างเป็นรุ่นเครื่องมือที่ใช้ทดสอบโค้ดตัวอย่างในบทนี้จริง

เครื่องมือรุ่นที่ทดสอบหน้าที่
คอมไพเลอร์ gcc13.3.0 (Ubuntu)แปลซอร์สโค้ด .c เป็นไฟล์ปฏิบัติการ
มาตรฐานภาษาC11 (-std=c11)กำหนดไวยากรณ์ภาษา C ที่ใช้อ้างอิง
⚠️Warning

โค้ดตัวอย่างในบทนี้ทดสอบด้วย gcc 13.3.0 บนระบบปฏิบัติการลินุกซ์ หากใช้คอมไพเลอร์หรือระบบปฏิบัติการอื่น เช่น MinGW บนวินโดวส์ คำสั่งคอมไพล์และชื่อไฟล์ปฏิบัติการอาจแตกต่างเล็กน้อย แต่ซอร์สโค้ดภาษา C มาตรฐานใช้ได้เหมือนกัน

วิธีใช้บทเรียนและใบงานในหน้านี้

บทนี้นำโจทย์จัดชุดอุปกรณ์จากบทที่ 1 มาเขียนเป็นตัวแปรและนิพจน์ ใช้ค่าคงที่ในซอร์สเพื่อฝึกคิดชนิดและผลการคำนวณก่อน การรับข้อมูลด้วย scanf จะเรียนในบทที่ 3 จึงไม่ต้องเพิ่มการรับข้อมูล ลูป หรือเงื่อนไขเพื่อทำใบงานบทนี้

คัดตารางสำหรับบันทึกลงสมุดหรือไฟล์ของตนเอง เขียนคำทำนายก่อนรัน แล้วจดผลจริงโดยไม่แก้คำทำนายย้อนหลัง ตารางในบทความเป็นแบบฝึกสำหรับจด ไม่ใช่ช่องกรอกที่เว็บไซต์บันทึกคำตอบให้ เมื่อผลต่างกันให้อธิบายชนิดของตัวถูกดำเนินการ ลำดับการจัดกลุ่ม และจุดที่ค่าไปเก็บในตัวแปร

ใช้แพลตฟอร์ม C ของรายวิชาและข้อมูลสมมติ ไม่ต้องติดตั้งเครื่องมือหรือเปิดบัญชีเพิ่ม งานส่งและเกณฑ์ประเมินยังใช้ตามประกาศเดิม

2.1 ตัวแปรและการประกาศ

ระดับที่ 1: ตัวแปร (variable) คือที่เก็บค่าในหน่วยความจำซึ่งมีชื่อกำกับและเปลี่ยนค่าได้ระหว่างที่โปรแกรมทำงาน การนำตัวแปรมาใช้ต้อง ประกาศ (declaration) ก่อน เพื่อบอกคอมไพเลอร์ว่าตัวแปรนั้นชื่ออะไรและเก็บข้อมูลชนิดใด

ระดับที่ 2: รูปแบบการประกาศตัวแปรพร้อมกำหนดค่าเริ่มต้นคือ ชนิด ชื่อ = ค่า; เช่น int radius = 5; ประกาศตัวแปรจำนวนเต็มชื่อ radius และกำหนดค่าเริ่มต้นเป็น 5 การตั้งชื่อตัวแปรใช้มาตรฐานเดียวกับบทที่ 1 คือชื่อสื่อความหมายแบบ snake_case ขึ้นต้นด้วยตัวอักษรหรือขีดล่าง ตามด้วยตัวอักษร ตัวเลข หรือขีดล่าง และต้องไม่ซ้ำกับคำสงวนของภาษา เช่น int หรือ return

ตารางต่อไปนี้เปรียบเทียบชื่อตัวแปรที่ใช้ได้กับใช้ไม่ได้ เพื่อให้เห็นกฎการตั้งชื่อชัดเจน

ชื่อที่เขียนใช้ได้หรือไม่เหตุผล
radiusใช้ได้ขึ้นต้นด้วยตัวอักษร สื่อความหมาย
student_idใช้ได้รูปแบบ snake_case ขึ้นต้นด้วยตัวอักษร
_countใช้ได้ขึ้นต้นด้วยขีดล่างได้
avg2ใช้ได้มีตัวเลขได้หากไม่ได้อยู่ตำแหน่งแรก
2nd_scoreใช้ไม่ได้ขึ้นต้นด้วยตัวเลข
avg scoreใช้ไม่ได้มีช่องว่างคั่นกลาง
intใช้ไม่ได้ตรงกับคำสงวนของภาษา

ระดับที่ 3: การเลือกชนิดข้อมูลให้เหมาะกับค่าที่จะเก็บมีผลต่อคุณภาพและความถูกต้องของโปรแกรม เช่น การเก็บส่วนสูง 1.75 เมตรด้วยชนิดจำนวนเต็มจะทำให้ค่าถูกตัดเศษเหลือ 1 ซึ่งผิดจากความเป็นจริง การตั้งชื่อที่สื่อความหมายยังช่วยให้อ่านและบำรุงรักษาโค้ดได้ง่าย สอดคล้องกับ CLO5 ที่เน้นตั้งแต่บทที่ 1 ข้อบกพร่องที่พบบ่อยของผู้เริ่มต้นคือใช้ตัวแปรโดยไม่ประกาศ หรือประกาศผิดชนิดจนค่าที่เก็บคลาดเคลื่อน

คำถามตรวจความเข้าใจ 2.1: เหตุใดการเก็บส่วนสูง 1.75 จึงควรใช้ชนิด double แทน int

2.2 ชนิดข้อมูลพื้นฐานและค่าคงที่

ระดับที่ 1: ภาษา C มีชนิดข้อมูลพื้นฐานที่ใช้บ่อยสี่ชนิด ได้แก่ int สำหรับจำนวนเต็ม float และ double สำหรับจำนวนที่มีทศนิยม และ char สำหรับอักขระหนึ่งตัว

ระดับที่ 2: ชนิดข้อมูลพื้นฐาน ขนาด ช่วงค่าโดยประมาณ และการใช้งานสรุปได้ดังตาราง ขนาดและช่วงค่าในตารางเป็นค่าตัวอย่างบนระบบที่ทดสอบ (64 บิต Linux/gcc) เพราะมาตรฐานภาษา C รับประกันเพียงช่วงต่ำสุดเท่านั้น (ดูหมายเหตุใต้ตาราง)

ชนิดขนาด (ตัวอย่างบนระบบที่ทดสอบ)ช่วงค่าโดยประมาณ (ตัวอย่างบนระบบที่ทดสอบ)ความละเอียดตัวอย่างค่า
int4 ไบต์ประมาณ -2,147,483,648 ถึง 2,147,483,647จำนวนเต็ม5, -12, 0
float4 ไบต์ประมาณ ±3.4 × 10^38ประมาณ 6–7 หลักนัยสำคัญ3.14f
double8 ไบต์ประมาณ ±1.8 × 10^308ประมาณ 15–16 หลักนัยสำคัญ3.14159
char1 ไบต์ประมาณ -128 ถึง 127อักขระหนึ่งตัว (รหัส ASCII)'A', 'z'
ℹ️Note

มาตรฐานภาษา C รับประกันเพียง ช่วงค่าต่ำสุด ไม่ได้บังคับขนาดไบต์ตายตัว เช่น int ต้องกว้างอย่างน้อย 16 บิต (เก็บได้อย่างน้อย -32767 ถึง 32767) และต้องมีลำดับ char ไม่เกิน short ไม่เกิน int ไม่เกิน long ขนาดจริงจึงขึ้นกับระบบและคอมไพเลอร์ ค่าในตารางด้านบนเป็นค่าที่พบบนระบบ 64 บิต (Linux/gcc) ที่ใช้ทดสอบบทนี้ (อ้างอิง cppreference — Arithmetic types)

ใบงาน 1: เลือกชนิดจากความหมาย หน่วย และขอบเขต

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

ข้อมูลสมมติหน่วยหรือหน้าที่ชนิดที่คุณเลือกเปลี่ยนค่าได้หรือควรใช้ constเหตุผล
จำนวนสาย 17เส้นบันทึกเองบันทึกเองพิจารณาว่านับเป็นส่วนเศษได้หรือไม่
จำนวนสายต่อชุด 3เส้นต่อชุดบันทึกเองบันทึกเองกำหนดว่าจะใช้ขนาดชุดเดิมตลอดตัวอย่างหรือไม่
ราคา 1250สตางค์ต่อชิ้นบันทึกเองบันทึกเองแยกหน่วยออกจากบาท
น้ำหนักเฉลี่ย 1.75กิโลกรัมบันทึกเองบันทึกเองต้องรักษาส่วนเศษ
กลุ่มตัวอย่าง 'B'ป้ายอักขระหนึ่งตัวบันทึกเองบันทึกเองไม่ใช่ข้อความหลายตัว
รหัส "0017"ตัวระบุรายการอธิบายแนวคิดบันทึกเหตุผลต้องรักษาศูนย์นำหน้า จึงไม่ใช่ปริมาณที่นำไปบวก

สำหรับรหัสที่ต้องรักษารูปข้อความ ให้บันทึกเหตุผลไว้ก่อน การเก็บสตริงจะเรียนในบทที่ 8 ไม่ต้องเขียนโค้ดสตริงเพื่อให้ผ่านงานนี้ ส่วนตัวอย่างรหัสจำนวนเต็มใน Lab A ใช้ฝึกชนิดข้อมูลบนสภาพแวดล้อมที่ระบุ ไม่ได้เป็นข้อสรุปว่ารหัสทุกชนิดควรเก็บเป็น int

เมื่อเทียบกับตารางขนาดชนิดข้อมูล ให้แยก “ขนาดที่เครื่องนี้รายงาน” ออกจาก “สิ่งที่มาตรฐานรับประกัน” ใช้ผล sizeof ที่รันได้จริงของตน ไม่คัดตัวเลขจากตัวอย่างมาอ้างว่าเป็นผลทดลองของเครื่องตน

เพื่อยืนยันขนาดจริงบนระบบที่ใช้สอน สามารถใช้ตัวดำเนินการ sizeof ซึ่งคืนขนาดของชนิดข้อมูลเป็นจำนวนไบต์ และแสดงด้วยตัวระบุรูปแบบ %zu

C
/* types_demo.c : แสดงขนาด (sizeof) ของชนิดข้อมูลพื้นฐาน */
#include <stdio.h>
 
int main(void) {
    printf("sizeof(char)   = %zu byte\n", sizeof(char));
    printf("sizeof(int)    = %zu byte\n", sizeof(int));
    printf("sizeof(float)  = %zu byte\n", sizeof(float));
    printf("sizeof(double) = %zu byte\n", sizeof(double));
    return 0;
}

ผลการรันจริง (ตัวอย่างบนระบบที่ทดสอบด้วย gcc 13.3.0)

TEXT
sizeof(char)   = 1 byte
sizeof(int)    = 4 byte
sizeof(float)  = 4 byte
sizeof(double) = 8 byte

ชนิด char เก็บค่าจำนวนเต็มขนาดเล็กที่ใช้แทนไบต์ของข้อความ ระบบที่ใช้ทดสอบในบทนี้เข้ารหัสอักขระพื้นฐานสอดคล้องกับ ASCII แต่ C11 ไม่บังคับให้ทุกระบบใช้ ASCII เช่น อักขระ 'A' มีรหัส 65 (ทดสอบแล้วด้วย gcc 13.3.0 ดูผลการรันในปฏิบัติการที่ 2 Lab B) ค่าที่ไม่ควรเปลี่ยนระหว่างโปรแกรมทำงาน เช่น ค่าพาย ควรประกาศเป็น ค่าคงที่ (constant) ด้วยคำสำคัญ const เช่น const double PI = 3.14159; เมื่อประกาศเป็น const แล้ว หากมีคำสั่งพยายามเปลี่ยนค่าในภายหลัง คอมไพเลอร์จะแจ้งข้อผิดพลาด อีกวิธีหนึ่งที่พบในโค้ดภาษา C คือการนิยามด้วย #define ที่ต้นไฟล์ เช่น #define PI 3.14159 ซึ่งเป็นการแทนที่ข้อความก่อนแปล ตารางต่อไปนี้เปรียบเทียบสองวิธีนี้

ประเด็นconst double PI = 3.14159;#define PI 3.14159
เกิดขึ้นเมื่อตอนคอมไพล์ เป็นตัวแปรที่เปลี่ยนค่าไม่ได้ก่อนแปล (preprocessor) แทนที่ข้อความ PI ด้วย 3.14159
มีชนิดข้อมูลมี (double) คอมไพเลอร์ตรวจชนิดให้ไม่มีชนิดในตัวเอง เป็นการแทนข้อความล้วน
ปิดท้ายด้วย ;ต้องมี ;ไม่ใส่ ; (ไม่เช่นนั้น ; จะถูกแทนเข้าไปด้วย)
ป้องกันการแก้ค่าได้ หากแก้ค่าภายหลังคอมไพเลอร์แจ้งข้อผิดพลาดได้โดยอ้อม เพราะเป็นเพียงข้อความคงที่

ระดับที่ 3: float และ double ต่างกันที่ความละเอียดในการเก็บทศนิยม โดย double เก็บได้ละเอียดกว่า (ประมาณ 15–16 หลักนัยสำคัญ เทียบกับ float ประมาณ 6–7 หลัก) และเป็นชนิดที่นิยมใช้เป็นค่าตั้งต้นสำหรับงานคำนวณทั่วไป ส่วน float ใช้เมื่อต้องการประหยัดหน่วยความจำและยอมรับความละเอียดที่น้อยลงได้ การเลือกชนิดให้เหมาะกับข้อมูลเป็นส่วนหนึ่งของคุณภาพโค้ด ข้อบกพร่องที่พบบ่อยคือใช้ int เก็บค่าที่มีเศษทศนิยม หรือลืมประกาศค่าที่ไม่ควรเปลี่ยนให้เป็น const จนถูกแก้ค่าโดยไม่ตั้งใจ

คำถามตรวจความเข้าใจ 2.2: การประกาศค่าพายเป็น const double PI = 3.14159; ให้ประโยชน์ด้านคุณภาพโค้ดอย่างไรเมื่อเทียบกับการใช้ตัวแปรธรรมดา

2.3 ตัวดำเนินการและนิพจน์

ระดับที่ 1: นิพจน์ (expression) คือการนำค่าหรือตัวแปรมาเชื่อมด้วยตัวดำเนินการเพื่อให้ได้ผลลัพธ์ ตัวดำเนินการเลขคณิตพื้นฐานในภาษา C ได้แก่ + (บวก) - (ลบ) * (คูณ) / (หาร) และ % (หาเศษเหลือ)

ระดับที่ 2: ตารางต่อไปนี้สรุปตัวดำเนินการเลขคณิตพร้อมตัวอย่างและผลลัพธ์ (ทุกค่าทดสอบแล้วด้วย gcc 13.3.0)

ตัวดำเนินการความหมายตัวอย่างผลลัพธ์
+บวก7 + 29
-ลบ7 - 25
*คูณ7 * 214
/หาร7 / 23 (หารจำนวนเต็ม)
%เศษเหลือ7 % 21

เมื่อตัวถูกดำเนินการทั้งสองเป็นจำนวนเต็ม ตัวดำเนินการ / จะเป็นการหารจำนวนเต็มที่ตัดเศษทิ้ง เช่น 7 / 2 ได้ผลเป็น 3 ไม่ใช่ 3.5 ส่วน % ใช้หาเศษเหลือของการหารจำนวนเต็ม เช่น 7 % 2 ได้ 1 และ % ใช้ได้กับจำนวนเต็มเท่านั้น

เมื่อนิพจน์มีตัวดำเนินการหลายตัว ลำดับความสำคัญ (operator precedence) กำหนดว่าตัวใดทำงานก่อน ตารางด้านล่างเรียงจากทำงานก่อน (ผูกแน่นที่สุด) ไปทำงานทีหลัง (อ้างอิง cppreference — C operator precedence)

ลำดับตัวดำเนินการทิศทางการจับคู่
สูงสุด(นิพจน์) วงเล็บจากในวงเล็บออกมา
รองลงมา+ - เอกภาค และการ cast (ชนิด)ขวาไปซ้าย
กลาง* / %ซ้ายไปขวา
ต่ำสุด+ - ทวิภาคซ้ายไปขวา

สรุปคือ *, / และ % ทำงานก่อน + และ - และใช้วงเล็บเพื่อเปลี่ยนลำดับได้ เช่น 2 + 3 * 4 ได้ 14 แต่ (2 + 3) * 4 ได้ 20

ใบงาน 2: แยกการจัดกลุ่มก่อนคำนวณผล

ก่อนอ่านตัวอย่างการคำนวณด้านล่าง ให้เติมวงเล็บเพื่อแสดงการจัดกลุ่มของนิพจน์ แล้วลดทีละส่วนจนเหลือคำตอบ ไม่ใช้คำตอบจากเครื่องคิดเลขแทนคำอธิบาย เพราะเครื่องคิดเลขอาจใช้การหารคนละแบบกับ C

นิพจน์วงเล็บที่แสดงการจัดกลุ่มขั้นย่อยแรกผลที่คาดผลจาก C
2 + 3 * 4บันทึกเองบันทึกเองบันทึกก่อนรันบันทึกหลังรัน
(2 + 3) * 4บันทึกเองบันทึกเองบันทึกก่อนรันบันทึกหลังรัน
20 / 3 * 3บันทึกเองบันทึกเองบันทึกก่อนรันบันทึกหลังรัน
20 / (3 * 3)บันทึกเองบันทึกเองบันทึกก่อนรันบันทึกหลังรัน
2 + 10 % 3 * 4บันทึกเองบันทึกเองบันทึกก่อนรันบันทึกหลังรัน

ตารางลำดับความสำคัญใช้บอกการจัดกลุ่ม ไม่ควรนำไปสรุปลำดับการประเมินอาร์กิวเมนต์หรือผลข้างเคียงทุกแบบ ในบทนี้ใช้นิพจน์ค่าขนาดเล็กที่ไม่มีการแก้ค่าระหว่างนิพจน์ และแยกคำสั่งกำหนดค่าทีละบรรทัดเพื่อให้ติดตามได้ชัด

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

ตัวอย่างคำนวณทีละขั้น (worked example) ประเมินนิพจน์ 2 + 10 % 3 * 4 ตามลำดับความสำคัญ โดย % และ * อยู่ลำดับเดียวกันจึงทำจากซ้ายไปขวา

ขั้นนิพจน์ที่เหลือตัวดำเนินการที่ทำผลย่อย
12 + 10 % 3 * 410 % 32 + 1 * 4
22 + 1 * 41 * 42 + 4
32 + 42 + 46

ตัวอย่างนี้ตรวจได้ด้วยโปรแกรมต่อไปนี้ ซึ่งรวมนิพจน์ที่มีหลายตัวดำเนินการไว้ด้วยกัน

C
/* precedence_demo.c : ลำดับความสำคัญของตัวดำเนินการเลขคณิต */
#include <stdio.h>
 
int main(void) {
    printf("2 + 3 * 4       = %d\n", 2 + 3 * 4);
    printf("(2 + 3) * 4     = %d\n", (2 + 3) * 4);
    printf("10 + 20 / 5 - 3 = %d\n", 10 + 20 / 5 - 3);
    printf("2 + 10 %% 3 * 4  = %d\n", 2 + 10 % 3 * 4);
    return 0;
}

ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)

TEXT
2 + 3 * 4       = 14
(2 + 3) * 4     = 20
10 + 20 / 5 - 3 = 11
2 + 10 % 3 * 4  = 6

บรรทัด 10 + 20 / 5 - 3 ได้ 11 เพราะ 20 / 5 ทำก่อน (ได้ 4) เหลือ 10 + 4 - 3 แล้วทำ + และ - จากซ้ายไปขวา ได้ 14 - 3 เท่ากับ 11

ระดับที่ 3: ข้อบกพร่องที่พบบ่อยที่สุดในบทนี้คือการหารจำนวนเต็มโดยไม่ตั้งใจ เช่น ต้องการค่าเฉลี่ยของ 7 กับ 2 แต่เขียน 7 / 2 ได้ 3 เพราะตัวถูกดำเนินการทั้งสองเป็นจำนวนเต็ม ทำให้ผลลัพธ์ถูกตัดเศษก่อนนำไปใช้ ตารางด้านล่างเปรียบเทียบนิพจน์หารที่ชนิดตัวถูกดำเนินการต่างกัน

นิพจน์ชนิดตัวถูกดำเนินการผลลัพธ์หมายเหตุ
7 / 2จำนวนเต็ม หาร จำนวนเต็ม3หารจำนวนเต็ม ตัดเศษทิ้ง
7 % 2จำนวนเต็ม หาร จำนวนเต็ม1เศษเหลือของการหาร
7.0 / 2ทศนิยม หาร จำนวนเต็ม3.5ตัวเลขทศนิยมทำให้เป็นการหารทศนิยม
(double)7 / 2ทศนิยม หาร จำนวนเต็ม3.5cast ทำให้เป็นการหารทศนิยม

การเข้าใจลำดับความสำคัญและชนิดของตัวถูกดำเนินการจึงจำเป็นต่อความถูกต้องของผลลัพธ์ ซึ่งนำไปสู่การแปลงชนิดข้อมูลในหัวข้อถัดไป

คำถามตรวจความเข้าใจ 2.3: นิพจน์ 7 / 2 ในภาษา C ให้ค่าใด และเพราะเหตุใดจึงได้ค่านั้น

2.4 การแปลงชนิดข้อมูล

ระดับที่ 1: การแปลงชนิดข้อมูล (type conversion) คือการเปลี่ยนค่าจากชนิดหนึ่งไปเป็นอีกชนิดหนึ่ง มีทั้งแบบที่เกิดขึ้นอัตโนมัติและแบบที่ผู้เขียนโปรแกรมสั่งเองด้วยการ cast

ระดับที่ 2: การสั่งแปลงชนิดเองทำโดยเขียนชนิดเป้าหมายไว้ในวงเล็บหน้าค่า เช่น (double)a แปลงค่าของ a ให้เป็นทศนิยมก่อนคำนวณ เมื่อ a และ b เป็นจำนวนเต็ม การเขียน (double)a / b จะทำให้การหารเป็นการหารทศนิยม เช่น เมื่อ a เป็น 7 และ b เป็น 2 นิพจน์ (double)a / b ให้ผล 3.500 (ทดสอบแล้วด้วย gcc 13.3.0 ดูผลการรันในปฏิบัติการที่ 2 Lab B) ต่างจาก a / b ที่ให้ 3

ใบงาน 3: ชนิดปลายทางไม่ย้อนกลับไปเปลี่ยนการหาร

กำหนด a = 9 และ b = 4 โดยทั้งคู่เป็น int ก่อนอ่านผลทดลอง ให้ระบุชนิดที่ใช้ ณ จุดหาร แล้วจึงพิจารณาชนิดของตัวแปรที่รับผล การเห็น double ด้านซ้ายเพียงอย่างเดียวยังไม่ตอบว่าการหารเป็นแบบใด

คำสั่งหรือการคำนวณชนิด ณ จุดหารค่าก่อนเก็บปลายทางค่าที่คาดเมื่อแสดงสองตำแหน่ง
double result = a / b;บันทึกเองบันทึกเองบันทึกเอง
double result = (double)a / b;บันทึกเองบันทึกเองบันทึกเอง
double result = (double)(a / b);บันทึกเองบันทึกเองบันทึกเอง
double result = a / 4.0;บันทึกเองบันทึกเองบันทึกเอง

ทดลองเปลี่ยนคู่ข้อมูลเป็น 8 กับ 4 และ 1 กับ 4 แล้วอธิบายว่าคู่ใดช่วยพบความผิดเรื่องตำแหน่ง cast ได้ดีกว่า คู่ที่หารลงตัวอาจทำให้หลายวิธีดูเหมือนให้คำตอบเหมือนกัน จึงไม่พอสำหรับยืนยันว่าตำแหน่งการแปลงถูกแล้ว

ใช้ตัวหารที่ไม่เป็นศูนย์และค่าที่แทนด้วยชนิดที่ใช้ได้เท่านั้น กรณีตัวหารศูนย์ให้ระบุว่าอยู่นอกขอบเขตและไม่รัน ไม่ทดลองแล้วนำอาการครั้งหนึ่งไปสรุปเป็นคำตอบของภาษา C

ตัวอย่างคำนวณทีละขั้น (worked example) ตำแหน่งที่วางการ cast มีผลต่อผลลัพธ์ กำหนด a เป็น 9 และ b เป็น 4 แล้วเปรียบเทียบสามนิพจน์ โดยการ cast ผูกแน่นกว่า / แต่ต้องดูวงเล็บด้วยว่าครอบตัวถูกหารหรือผลหาร (ดูตารางลำดับความสำคัญในหัวข้อ 2.3)

นิพจน์ลำดับการคำนวณผลลัพธ์
a / b9 / 4 หารจำนวนเต็ม ตัดเศษ2
(double)a / b9.0 / 4 หารทศนิยม2.25
(double)(a / b)9 / 4 ได้ 2 ก่อน แล้วค่อยแปลงเป็น 2.02.00

จุดสำคัญคือ (double)(a / b) ยังได้ 2.00 เพราะการหารจำนวนเต็มเกิดขึ้นก่อน แล้วจึงแปลงผลที่ตัดเศษแล้วเป็นทศนิยม ส่วน (double)a / b แปลงตัวตั้งก่อนหาร จึงได้ 2.25 ที่ถูกต้อง ตรวจได้ด้วยโปรแกรมต่อไปนี้

C
/* div_demo.c : การหารจำนวนเต็มเทียบกับการหารทศนิยม */
#include <stdio.h>
 
int main(void) {
    int a = 9, b = 4;
    printf("a / b          = %d\n", a / b);
    printf("a %% b          = %d\n", a % b);
    printf("(double)a / b  = %.4f\n", (double)a / b);
    printf("(double)(a / b)= %.4f\n", (double)(a / b));
    return 0;
}

ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)

TEXT
a / b          = 2
a % b          = 1
(double)a / b  = 2.2500
(double)(a / b)= 2.0000

ระดับที่ 3: การแปลงชนิดมีผลโดยตรงต่อความถูกต้องของผลลัพธ์ โดยเฉพาะเมื่อต้องการค่าเฉลี่ยหรืออัตราส่วนที่เป็นทศนิยมจากข้อมูลจำนวนเต็ม การ cast ตัวถูกดำเนินการอย่างน้อยหนึ่งตัวให้เป็น double ก่อนหาร จะป้องกันการตัดเศษโดยไม่ตั้งใจ และต้องวางการ cast ให้เกิดก่อนการหารดังตัวอย่างข้างต้น ประเด็นการแสดงผลค่าทศนิยมด้วยตัวระบุรูปแบบ เช่น %.2f และ %.3f จะอธิบายอย่างละเอียดในการจัดรูปแบบการแสดงผลบทที่ 3 บทนี้ใช้ตัวระบุรูปแบบ %d สำหรับจำนวนเต็ม %.2f/%.3f สำหรับทศนิยม และ %c สำหรับอักขระ พร้อมหมายเหตุนี้

คำถามตรวจความเข้าใจ 2.4: นิพจน์ (double)a / b ต่างจาก a / b อย่างไรเมื่อ a และ b เป็นจำนวนเต็ม

2.5 กรณีศึกษาต่อเนื่อง: ชุดอุปกรณ์ ราคา และหน่วยข้อมูล

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

2.5.1 คิดชนิดของนิพจน์ทีละขั้น

C
#include <stdio.h>
 
int main(void)
{
    int cables = 17;
    int per_kit = 3;
    int complete_kits = cables / per_kit;
    int leftover = cables % per_kit;
    double average_a = cables / per_kit;
    double average_b = (double)cables / per_kit;
 
    printf("kits=%d leftover=%d\n", complete_kits, leftover);
    printf("integer first=%.2f\n", average_a);
    printf("convert first=%.2f\n", average_b);
    return 0;
}

ผลลัพธ์คือ kits=5 leftover=2 ตามด้วย integer first=5.00 และ convert first=5.67 ความแตกต่างไม่ได้เกิดจากจำนวนตำแหน่งทศนิยมที่แสดง แต่เกิดก่อนนั้น ขณะคำนวณ cables / per_kit ตัวถูกหารทั้งสองเป็น int จึงได้ผลหารจำนวนเต็ม 5 แล้วจึงแปลง 5 ไปเก็บเป็น double ส่วนการแปลงตัวตั้งก่อนหารทำให้การหารใช้ชนิดทศนิยม

การเขียน (double)(cables / per_kit) ก็ยังได้ 5.00 เพราะวงเล็บด้านในคำนวณก่อน การแปลงชนิดไม่สามารถกู้ส่วนเศษที่ถูกตัดทิ้งไปแล้วได้ ให้ลากเส้นแบ่งในกระดาษเป็น “ชนิดของตัวตั้ง → ชนิดของการหาร → ค่าที่ได้ → ชนิดของตัวรับ” จะช่วยหยุดความเข้าใจผิดว่าตัวแปรด้านซ้ายบังคับวิธีคำนวณทั้งหมดด้านขวา

นิพจน์สิ่งที่เกิดก่อนเก็บค่าค่าทางคณิตศาสตร์ที่เก็บโดยประมาณ
17 / 3หารจำนวนเต็ม5
17.0 / 3หารทศนิยม5.666…
(double)17 / 3แปลงตัวตั้งก่อนหาร5.666…
(double)(17 / 3)หารจำนวนเต็มก่อนแปลง5.0

สำหรับจำนวนเต็มติดลบ การหารใน C11 ตัดส่วนทศนิยมเข้าหาศูนย์ ดังนั้น -17 / 3 ได้ -5 และ -17 % 3 ได้ -2 ไม่ควรนำความจำจากเครื่องคิดเลขหรือภาษาอื่นมาแทนกติกาของ C โจทย์จัดชุดในบทนี้กำหนดให้ปริมาณไม่ติดลบ จึงไม่ต้องใช้ผลลบเป็นข้อมูลใช้งาน แต่ตัวอย่างช่วยให้เห็นว่าขอบเขตข้อมูลมีความสำคัญ GNU C: Integer Arithmetic อธิบายบทบาทของชนิดจำนวนเต็มในการคำนวณ

2.5.2 ใช้จำนวนเต็มแทนหน่วยย่อยเมื่อต้องการยอดแน่นอน

สมมติสมุดราคาชิ้นละ 12 บาท 50 สตางค์ ซื้อ 3 ชิ้น การเก็บราคาเป็น 1250 สตางค์แล้วคูณ 3 ได้ 3750 สตางค์ ทำให้การคำนวณนี้อยู่ในจำนวนเต็มและสื่อหน่วยชัดเจน เมื่อแสดงผลจึงแยกบาทด้วยการหาร 100 และสตางค์ด้วยเศษจาก 100 ตัวอย่างเป็นแบบจำลองราคาง่าย ๆ ไม่รวมภาษี การปัดเศษส่วนลด หรือกติกาธุรกิจอื่นที่ต้องกำหนดเพิ่ม

C
#include <stdio.h>
 
int main(void)
{
    const int unit_price_satang = 1250;
    int quantity = 3;
    int total_satang = unit_price_satang * quantity;
 
    printf("Total: %d.%02d baht\n",
           total_satang / 100, total_satang % 100);
    return 0;
}

ผลคือ Total: 37.50 baht ชื่อ total_satang ช่วยเตือนว่าไม่ใช่จำนวนบาท ถ้าเปลี่ยนชื่อเป็น total เฉย ๆ แล้วอีกคนบวกค่าจัดส่ง 20 ที่คิดเป็นบาท ผลจะผิดโดยไม่มีข้อผิดพลาดทางไวยากรณ์ ชนิด int ไม่ได้รู้จักหน่วยสตางค์ จึงต้องใช้ชื่อและข้อกำหนดช่วยป้องกัน

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

2.6 ความผิดพลาดที่ดูเหมือนคำนวณถูก

2.6.1 ความแม่นยำในการเก็บกับรูปแบบแสดงเป็นคนละเรื่อง

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

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

2.6.2 ค่าที่เปลี่ยนกับชื่อที่เหมือนเดิม

พิจารณาคำสั่ง count = count + 1; ด้านขวาใช้ค่าเดิมคำนวณก่อน แล้วจึงนำผลไปเก็บทับด้านซ้าย เครื่องหมาย = ในที่นี้ไม่ใช่การประกาศความเท่ากันแบบสมการคณิตศาสตร์ ถ้า count เริ่มที่ 4 หลังคำสั่งเป็น 5 และถ้าทำซ้ำอีกครั้งเป็น 6

ให้เขียน trace สามคอลัมน์ คือคำสั่ง ค่าเดิม และค่าใหม่ สำหรับการปรับยอด total = total + price; แล้วเปรียบเทียบกับ total = price; แบบหลังแทนค่าทั้งหมด ไม่ได้สะสม เหตุผลนี้จะใช้ซ้ำในบทลูป ไม่ควรจำเพียงสัญลักษณ์ += โดยไม่เข้าใจการอ่านค่าเดิม

ใบงาน 4: การกำหนดค่าเป็นเหตุการณ์ ไม่ใช่สายเชื่อมตัวแปร

เมื่อกำหนด snapshot = current; โปรแกรมนำค่าของ current ในขณะนั้นไปเก็บ ไม่ได้สร้างความสัมพันธ์ว่าตัวแปรทั้งสองต้องเปลี่ยนตามกันตลอดเวลา อ่านโปรแกรมต่อไปนี้แล้วเขียนผลแต่ละบรรทัดก่อนรัน

C
/* assignment_trace.c: Follow assignments one statement at a time. */
#include <stdio.h>
 
int main(void)
{
    int current = 4;
    int snapshot = current;
 
    printf("start: current=%d snapshot=%d\n", current, snapshot);
    current = current + 1;
    printf("increment: current=%d snapshot=%d\n", current, snapshot);
    current = 20;
    printf("replace: current=%d snapshot=%d\n", current, snapshot);
    snapshot = current;
    printf("copy again: current=%d snapshot=%d\n", current, snapshot);
    return 0;
}
จุดที่สังเกตcurrent ที่คาดsnapshot ที่คาดเหตุผล
หลังเริ่มต้นบันทึกเองบันทึกเองตัวแปรใดถูกตั้งค่าจากตัวใด
หลังเพิ่มค่า currentบันทึกเองบันทึกเองคำสั่งนี้แก้ตัวแปรใด
หลังแทน current ด้วย 20บันทึกเองบันทึกเองค่าก่อนหน้าถูกใช้ต่อหรือถูกแทน
หลังคัดลอกค่าอีกครั้งบันทึกเองบันทึกเองจุดใดที่ snapshot เปลี่ยนจริง

อธิบายความต่างระหว่าง current = current + 1; กับ current = 1; โดยแยกค่าเดิม นิพจน์ด้านขวา และค่าใหม่ที่เก็บด้านซ้าย แนวคิดการเก็บค่าตามคำสั่งอ้างอิงได้จาก GNU C: Assignment Expressions และจะใช้ซ้ำเมื่อเรียนตัวนับกับตัวสะสมในบทลูป

2.7 เวิร์กช็อปขยาย: ตารางชนิด หน่วย และขอบเขต

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

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

ขั้นถัดไปเปรียบเทียบค่าเฉลี่ยของคะแนนสมมติ 7, 8 และ 10 ด้วยสามนิพจน์ (7 + 8 + 10) / 3, (7 + 8 + 10) / 3.0 และ (double)((7 + 8 + 10) / 3) เขียนคำทำนายก่อนรัน แล้วอธิบายความแตกต่างด้วยชนิดของตัวถูกดำเนินการ ห้ามใช้เพียงคำตอบว่า “ใส่จุดแล้วถูก” เพราะยังไม่บอกว่าเกิดอะไรขึ้น

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

2.8 ตัวอย่างเปรียบเทียบ: แปลงหน่วยเวลาโดยไม่สูญเสียเศษ

สมมติงานทดลองใช้เวลา 3671 วินาที ต้องรายงานเป็น 1 ชั่วโมง 1 นาที 11 วินาที เริ่มจากจำนวนชั่วโมงเต็มด้วยการหาร 3600 จากนั้นหาเศษที่ยังไม่ครบชั่วโมง แล้วค่อยแยกเป็นนาทีและวินาที ไม่ใช้ผลหารทศนิยมแล้วเดาตัวเลขหลังจุดว่าเป็นนาที เพราะ 1.5 ชั่วโมงหมายถึงหนึ่งชั่วโมงครึ่ง ไม่ใช่หนึ่งชั่วโมงห้านาที

นิพจน์ที่ใช้คือ hours = total_seconds / 3600, remaining = total_seconds % 3600, minutes = remaining / 60 และ seconds = remaining % 60 หลังแยกแล้วตรวจย้อนกลับว่า hours * 3600 + minutes * 60 + seconds เท่ากับค่าเดิมสำหรับค่าทดสอบที่อยู่ในช่วงปลอดภัย

วินาทีทั้งหมดชั่วโมงนาทีวินาทีส่วนท้าย
0000
590059
60010
359905959
3600100
36711111

ถ้าเขียน minutes = total_seconds / 60 แล้วรายงานควบกับชั่วโมง จะได้ 61 นาทีสำหรับ 3671 วินาที นั่นเป็นนาทีทั้งหมด ไม่ใช่นาทีที่เหลือหลังแยกชั่วโมง สูตรไม่ได้ผิดโดยตัวมันเอง แต่ไม่ตรงกับความหมายของช่องผลลัพธ์ การตั้งชื่อ total_minutes กับ remaining_minutes ช่วยแยกสองแนวคิด

ทดลองเปลี่ยนหน่วยจากวินาทีเป็นมิลลิวินาทีแล้วระบุว่าค่าคงที่ใดต้องเปลี่ยน ถ้านำค่าหน่วยใหม่เข้าไปโดยยังหาร 3600 จะได้ตัวเลขที่ดูสมเหตุผลแต่หน่วยผิด ตรรกะการแปลงหน่วยนี้เชื่อมกับการเก็บราคาเป็นสตางค์และการใช้ตัวแปรที่มีชื่อบอกหน่วย

ท้ายตัวอย่างให้ผู้เรียนเขียนรายการ “ชนิดข้อมูลรับประกันอะไร” กับ “ชนิดข้อมูลไม่รู้เรื่องอะไร” ชนิดจำนวนเต็มกำหนดรูปแบบการคำนวณและช่วง แต่ไม่รู้ว่าค่าเป็นวินาที บาท หรือชิ้น ความถูกต้องจึงต้องอาศัยทั้งชนิด ชื่อ ขอบเขต และโจทย์ร่วมกัน

ทดลองแปลงหน่วยและตรวจย้อนกลับด้วยโปรแกรม

เมื่อไล่ตารางด้วยมือแล้ว ใช้โปรแกรมต่อไปนี้ตรวจความสัมพันธ์ของหน่วย กำหนดข้อมูลตัวอย่างเป็นจำนวนเต็มไม่ติดลบขนาดเล็กที่ผลคำนวณทั้งหมดอยู่ในช่วง int โปรแกรมไม่มีการรับข้อมูลหรือวนซ้ำ หากต้องการทดลองค่าอื่น ให้แก้ค่าเริ่มต้นแล้วแปลและรันใหม่

C
/* time_units.c: Decompose a small nonnegative duration into units. */
#include <stdio.h>
 
int main(void)
{
    const int seconds_per_hour = 3600;
    const int seconds_per_minute = 60;
    int total_seconds = 3671;
    int hours = total_seconds / seconds_per_hour;
    int remaining_seconds = total_seconds % seconds_per_hour;
    int minutes = remaining_seconds / seconds_per_minute;
    int seconds_left = remaining_seconds % seconds_per_minute;
    int restored = hours * seconds_per_hour
                 + minutes * seconds_per_minute + seconds_left;
 
    printf("seconds=%d hours=%d minutes=%d seconds_left=%d\n",
           total_seconds, hours, minutes, seconds_left);
    printf("restored=%d\n", restored);
    return 0;
}

ทำนายผลสำหรับ 3671 ก่อนรัน แล้วทดลอง 0, 59, 60, 3599 และ 3600 เพิ่ม โดยบันทึกชั่วโมง นาที วินาทีส่วนท้าย และค่าที่ประกอบกลับ ตรวจว่าค่าที่ประกอบกลับเท่าจำนวนวินาทีเดิม และนาทีกับวินาทีส่วนท้ายอยู่ในช่วง 0–59 สำหรับข้อมูลตามข้อตกลงนี้

การที่โปรแกรมแปลผ่านยังไม่รับรองว่าเลือกหน่วยถูก เช่น การใช้ total_seconds / 60 เป็นนาทีที่เหลือจะสับสนกับนาทีทั้งหมด กรณี 3671 ช่วยเปิดเผยความต่างได้ดีกว่ากรณีที่ยังไม่ถึงหนึ่งชั่วโมง หลักการใช้ผลหารและเศษเพื่อตรวจกลับอ่านเพิ่มเติมได้จาก GNU C: Division and Remainder

2.9 อ่านโค้ดที่ผิดและทำความเข้าใจข้อความวินิจฉัย

ตัวอย่างต่อไปนี้เป็นโค้ดฝึกแยกจากงานที่ต้องส่ง แสดงข้อผิดพลาดที่สัมพันธ์กับเนื้อหาบท พร้อมข้อความวินิจฉัยจริง วิธีอ่าน และฉบับแก้ คำว่า diagnostic ใช้รวมข้อความ error, warning และ note จึงไม่ควรเรียกทุกบรรทัดว่า error หรือถือว่าการไม่มี error แปลว่าโปรแกรมถูกตามโจทย์

ที่มาของ log: ผู้เขียนทดสอบด้วย GCC 11.4.0 บน Ubuntu ผ่าน Linux environment เมื่อ 24 ตุลาคม 2569 ใช้ C11 และตัวเลือกดังนี้ ชื่อไฟล์ เลขบรรทัด คอลัมน์ ถ้อยคำ และคำเตือนที่เปิดใช้อาจต่างจากแพลตฟอร์มการเรียน นักศึกษาไม่จำเป็นต้องเปลี่ยนเครื่องมือหรือการติดตั้งตามชุดทดสอบของผู้เขียน

TEXT
-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: ประกาศชื่อเดิมซ้ำด้วยชนิดที่ขัดกัน

ประเภท: ข้อผิดพลาดขณะคอมไพล์

ตัวแปรชื่อ score ถูกประกาศสองครั้งในขอบเขตเดียวกันด้วยชนิด int และ double การเปลี่ยนชนิดไม่ใช่การประกาศชื่อเดิมทับโดยอัตโนมัติ ต้องเลือกชนิดให้ตรงกับข้อมูลก่อนใช้งาน

โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้

C
#include <stdio.h>
int main(void)
{
    int score = 80;
    double score = 80.5;
    printf("%.1f\n", score);
    return 0;
}

คำสั่งสร้างและข้อความที่ได้จริง

TEXT
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o bad
TEXT
bad.c: In function 'main':
bad.c:5:12: error: conflicting types for 'score'; have 'double'
    5 |     double score = 80.5;
      |            ^~~~~
bad.c:4:9: note: previous definition of 'score' with type 'int'
    4 |     int score = 80;
      |         ^~~~~

สถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 1 คำสั่งล้มและไม่ได้รัน executable ของตัวอย่างนี้ การรันไฟล์เก่าที่อาจค้างจาก build ครั้งก่อนจะไม่ใช่หลักฐานของโค้ดปัจจุบัน

อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร

conflicting types ระบุว่าชนิดที่พบขัดกับ declaration เดิม อ่าน note ที่บอก previous definition ควบคู่กับบรรทัด error เพื่อเทียบสองตำแหน่ง ไม่ตรวจเพียงจุดที่ประกาศครั้งหลัง

โค้ดที่แก้แล้ว

C
#include <stdio.h>
int main(void)
{
    double score = 80.5;
    printf("%.1f\n", score);
    return 0;
}

เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ

ใช้ double เพียง declaration เดียวเพื่อเก็บคะแนนที่มีเศษ และคง %.1f สำหรับ printf ซึ่งรับ double การแก้ไม่ได้ใช้การ cast เพื่อกลบการประกาศซ้ำ

ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง

TEXT
80.5

ตัวอย่างที่ผิด 2: นำผลหารจำนวนเต็มไปเก็บใน double

ประเภท: ข้อผิดพลาดเชิงตรรกะที่ compiler อาจไม่เตือน

ข้อมูลสังเคราะห์กำหนดผลรวม 5 และจำนวน 2 ซึ่งต้องได้ค่าเฉลี่ย 2.50 แต่ operands ทั้งสองของการหารเป็น int จึงหารจำนวนเต็มก่อนแล้วค่อยแปลงผลเป็น double เศษที่สูญเสียไปไม่กลับคืนเพราะตัวแปรปลายทางเป็น double

โค้ดที่ผิด — ไม่ใช้แทนฉบับแก้

C
#include <stdio.h>
int main(void)
{
    int total = 5;
    int count = 2;
    double average = total / count;
    printf("average=%.2f\n", average);
    return 0;
}

คำสั่งสร้างและข้อความที่ได้จริง

TEXT
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 bad.c -o bad

ไม่มีข้อความวินิจฉัยจาก compiler ในการทดลองนี้ ทั้ง stdout และ stderr ว่าง การไม่มีข้อความเป็นผลที่สังเกต ไม่ใช่การรับรองความถูกต้องของโปรแกรม

สถานะจบของคำสั่งสร้างที่ตัวเก็บผลอ่านได้: 0 ขั้นสร้างสำเร็จ จากนั้นจึงตรวจพฤติกรรมขณะรันหรือใช้ชุดทดสอบตามประเภทข้างต้น

ผลจากการรันฉบับผิด

stdout:

TEXT
average=2.00

stderr ว่าง ไม่มี runtime diagnostic จากระบบในการทดลองนี้ ต้องใช้การเทียบผลกับข้อกำหนดเป็นหลักฐาน

สถานะจบที่ตัวเก็บผลอ่านได้: 0

ผลตามข้อกำหนดที่ใช้เทียบ:

TEXT
average=2.50

อ่านข้อความว่าแจ้งอะไรและผิดอย่างไร

กรณีนี้ไม่มี compiler error และอาจไม่มี warning ภายใต้ตัวเลือกที่ใช้ log จึงแสดงผลจริงเทียบกับผลคาดหวังแทน ไม่แต่ง error message ให้การคำนวณที่ถูกกติกาภาษาแต่ผิดโจทย์

โค้ดที่แก้แล้ว

C
#include <stdio.h>
int main(void)
{
    int total = 5;
    int count = 2;
    double average = (double)total / count;
    printf("average=%.2f\n", average);
    return 0;
}

เหตุผลของการแก้และสิ่งที่ต้องตรวจซ้ำ

แปลง total เป็น double ก่อนหารเพื่อให้เกิดการหารทศนิยม แล้วทดสอบด้วยข้อมูลที่หารไม่ลงตัว การ cast ผลหลังหาร เช่น (double)(total / count) ยังช้าเกินไป ต้องตรวจตำแหน่ง conversion

ผลตรวจฉบับแก้จริง: คอมไพล์ด้วยตัวเลือกเดิมและเพิ่ม -Werror เพื่อให้ชุดทดสอบผู้เขียนไม่ยอมรับ warning ไม่มีข้อความวินิจฉัยและคำสั่งสร้างจบด้วยสถานะ 0 จากนั้นรันได้ stdout ดังนี้ และ stderr ว่าง

TEXT
average=2.50

ลำดับการอ่าน log เมื่อลองโปรแกรมของบทนี้

  1. ระบุว่าเกิดในขั้น compile, link, run หรือ test และแยกข้อความจากระบบออกจากข้อความที่โปรแกรมพิมพ์เอง
  2. อ่าน diagnostic แรกกับบริบทบรรทัดก่อนหน้า แล้วใช้ note เพื่อเทียบ declaration หรือชนิดข้อมูล อย่าแก้ทุกข้อความตามลำดับโดยไม่หาสาเหตุร่วม
  3. แก้ทีละสาเหตุ สร้างโปรแกรมใหม่ และตรวจว่ากำลังรันผล build ใหม่ การเติมวงเล็บ ปิด warning หรือลบ test อาจทำให้ log เงียบโดยยังผิดโจทย์
  4. ใช้ข้อมูลเดิมที่ทำให้ผิดทดสอบซ้ำ แล้วเพิ่มกรณีขอบเขตที่สัมพันธ์กับบท การรันจบสถานะศูนย์เพียงครั้งเดียวไม่แทนการตรวจข้อกำหนดครบถ้วน

ความหมายตามภาษาอ้างจากร่างมาตรฐาน C11 โดยข้อความ diagnostic เฉพาะถ้อยคำเป็นของเครื่องมือที่ใช้ ไม่ได้กำหนดให้ compiler ทุกตัวพิมพ์เหมือนกัน (WG14 N1570, GCC: Warning Options)

ปฏิบัติการที่ 2 ตัวแปร ชนิดข้อมูล และนิพจน์

ปฏิบัติการนี้แบ่งเป็นสองส่วน ส่วนแรกฝึกประกาศตัวแปรชนิดต่าง ๆ และค่าคงที่ แล้วแสดงค่าด้วย printf ส่วนที่สองฝึกทำนายและตรวจผลของนิพจน์ รวมถึงการแปลงชนิดข้อมูล ทั้งสองส่วนควรทำตามลำดับเพื่อให้เห็นการเชื่อมโยงจากการประกาศตัวแปรสู่การคำนวณ

ℹ️Note

ไฟล์ซอร์สโค้ดในปฏิบัติการนี้ให้ตั้งชื่อเป็นภาษาอังกฤษแบบ snake_case และบันทึกไว้ในโฟลเดอร์ของผู้เรียนเอง คำสั่งคอมไพล์ในเอกสารนี้ทดสอบด้วย gcc 13.3.0 การแสดงผลใช้ printf ที่รู้จักแล้วจากบทที่ 1 โดยยังไม่ใช้การรับข้อมูลเข้าซึ่งเป็นเนื้อหาของบทที่ 3

Lab A: ประกาศและแสดงค่าตัวแปรข้อมูลนักศึกษา

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

เป้าหมาย ฝึก LLO2.1 คือเลือกชนิดข้อมูลให้เหมาะกับค่า ประกาศตัวแปรและค่าคงที่ และแสดงค่าด้วย printf

ขั้นปฏิบัติ

  1. สร้างไฟล์ชื่อ student_info.c ด้วยโปรแกรมแก้ไขข้อความหรือ IDE
  2. พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
C
/* student_info.c : ประกาศและแสดงค่าตัวแปรข้อมูลนักศึกษาหนึ่งคน */
#include <stdio.h>
 
int main(void) {
    const int CREDIT = 3;
    int student_id = 650000001;
    double gpa = 3.75;
    char section = 'B';
 
    printf("student_id = %d\n", student_id);
    printf("gpa = %.2f\n", gpa);
    printf("section = %c\n", section);
    printf("credit = %d\n", CREDIT);
    return 0;
}
  1. คอมไพล์และรันด้วยคำสั่ง
BASH
gcc -std=c11 -Wall student_info.c -o student_info
./student_info

ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)

TEXT
student_id = 650000001
gpa = 3.75
section = B
credit = 3

ในโค้ดข้างต้น %d เป็นตัวระบุรูปแบบสำหรับจำนวนเต็ม %.2f สำหรับทศนิยมสองตำแหน่ง และ %c สำหรับอักขระหนึ่งตัว รายละเอียดของตัวระบุรูปแบบและการจัดรูปแบบการแสดงผลจะอธิบายในบทที่ 3

ตัดสินใจเอง A.1 ออกแบบชุดข้อมูลนักศึกษาสมมติของตนเอง แล้วเลือกชนิดข้อมูลให้เหมาะกับแต่ละค่า พร้อมเขียนเหตุผลประกอบสั้น ๆ ว่าเหตุใดจึงเลือกชนิดนั้น และระบุว่าค่าใดควรเป็นค่าคงที่

ผลลัพธ์ที่คาดหวัง A.1 โปรแกรมคอมไพล์ผ่านโดยไม่มีข้อผิดพลาดและไม่มีการแจ้งเตือน และแสดงค่าทุกตัวครบถ้วนด้วยชนิดข้อมูลที่เหมาะสม

จุดตรวจ A ประกาศตัวแปรครบทุกชนิดที่กำหนด (จำนวนเต็ม ทศนิยม อักขระ) และค่าคงที่ด้วย const แสดงค่าถูกต้องด้วย printf และตั้งชื่อสื่อความหมายแบบ snake_case

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
เกรดเฉลี่ยแสดงเป็นจำนวนเต็มประกาศเกรดเฉลี่ยด้วย int แทน doubleเปลี่ยนชนิดเป็น double ให้เก็บค่าทศนิยมได้
ค่าคงที่ของวิชาถูกแก้โดยไม่ตั้งใจลืมประกาศด้วย constประกาศค่าที่ไม่ควรเปลี่ยนด้วย const
อักขระแสดงเป็นตัวเลขใช้ %d กับตัวแปร char ที่ต้องการแสดงเป็นอักขระใช้ %c เพื่อแสดงอักขระ

เปิดหลังผู้เรียนออกแบบด้วยตนเองแล้ว

ใบงานตรวจ Lab A: ชนิด ค่าที่เก็บ และผลที่แสดง

ใช้ชุดข้อมูลสมมติสำหรับฝึก ไม่ต้องใส่รหัส เกรด หรือข้อมูลส่วนตัวจริงลงในโปรแกรม แยกให้ได้ว่าค่าใดเป็นปริมาณที่นำไปคำนวณ ค่าใดเป็นป้ายระบุ และค่าใดเป็นค่าคงที่ของตัวอย่าง

สิ่งที่ประกาศชนิดและค่าเริ่มต้นที่เลือกเหตุผลเรื่องหน่วย/ส่วนเศษผลที่คาดเมื่อแสดงผลจริง
จำนวนเต็มในตัวอย่างบันทึกเองบันทึกเองก่อนรันหลังรัน
ค่าทศนิยมบันทึกเองบันทึกเองก่อนรันหลังรัน
อักขระหนึ่งตัวบันทึกเองบันทึกเองก่อนรันหลังรัน
ค่าคงที่บันทึกเองเหตุใดไม่ควรเปลี่ยนระหว่างงานก่อนรันหลังรัน

เมื่อให้เพื่อนตรวจ ให้เพื่อนอธิบายความหมายของชื่อตัวแปรก่อนดูผล หากชื่อไม่บอกหน่วยหรือหน้าที่ ให้ปรับชื่อพร้อมเหตุผล จากนั้นแปลและรันใหม่โดยตรวจว่าการปรับชื่อไม่ได้เปลี่ยนค่าที่คำนวณ

Lab B: นิพจน์และการแปลงชนิดข้อมูล

สถานการณ์ คำนวณพื้นที่วงกลมจากค่าคงที่พาย ตรวจผลของการหารจำนวนเต็มและเศษเหลือ และคำนวณการหารแบบทศนิยมด้วยการแปลงชนิด เพื่อฝึกเขียนและทำนายผลของนิพจน์

เป้าหมาย ฝึก LLO2.2 และ LLO2.3 คือเขียนนิพจน์และทำนายผลของการหารจำนวนเต็ม/เศษเหลือ และเขียนโปรแกรมคำนวณที่มีการแปลงชนิดให้คอมไพล์ผ่านและผลถูกต้อง

ขั้นปฏิบัติ

  1. สร้างไฟล์ชื่อ ch2_demo.c และพิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
C
/* ch2_demo.c : ตัวแปร ชนิดข้อมูล ค่าคงที่ และนิพจน์ */
#include <stdio.h>
 
int main(void) {
    const double PI = 3.14159;
    int radius = 5;
    double area = PI * radius * radius;
 
    int a = 7, b = 2;
    int quotient = a / b;
    int remainder = a % b;
    double real_div = (double)a / b;
 
    char grade = 'A';
 
    printf("radius = %d, area = %.2f\n", radius, area);
    printf("%d / %d = %d, %d %% %d = %d\n", a, b, quotient, a, b, remainder);
    printf("(double)a / b = %.3f\n", real_div);
    printf("grade = %c (code %d)\n", grade, grade);
    return 0;
}
  1. ก่อนรัน ให้ทำนายผลของแต่ละบรรทัดลงในตาราง โดยเฉพาะค่าของ a / b, a % b และ (double)a / b
นิพจน์ค่าที่ทำนายค่าที่รันจริง
PI * radius * radius(เติมก่อนรัน)78.54
a / b(เติมก่อนรัน)3
a % b(เติมก่อนรัน)1
(double)a / b(เติมก่อนรัน)3.500
  1. คอมไพล์และรันด้วยคำสั่ง
BASH
gcc -std=c11 -Wall ch2_demo.c -o ch2_demo
./ch2_demo

ผลการรันจริง (ทดสอบแล้วด้วย gcc 13.3.0)

TEXT
radius = 5, area = 78.54
7 / 2 = 3, 7 % 2 = 1
(double)a / b = 3.500
grade = A (code 65)
  1. เทียบผลที่ทำนายกับผลการรันจริง และอธิบายว่าเหตุใด a / b จึงได้ 3 ขณะที่ (double)a / b ได้ 3.500

ตัดสินใจเอง B.1 เพิ่มการคำนวณค่าเฉลี่ยของจำนวนเต็มสามจำนวนที่กำหนดเอง โดยให้ผลเป็นทศนิยมที่ถูกต้อง ต้องใช้การแปลงชนิดเพื่อไม่ให้ผลถูกตัดเศษ แล้วแสดงผลด้วย printf

ผลลัพธ์ที่คาดหวัง B.1 ค่าเฉลี่ยที่แสดงเป็นทศนิยม ไม่ถูกตัดเศษจากการหารจำนวนเต็ม และโปรแกรมคอมไพล์ผ่านโดยไม่มีการแจ้งเตือน

จุดตรวจ B คอมไพล์ผ่านไม่มีข้อผิดพลาดและไม่มีการแจ้งเตือน ผลลัพธ์ตรงกับที่ทำนาย และโค้ดมีคอมเมนต์ส่วนหัวและจัดย่อหน้า 4 ช่องว่าง

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
ค่าเฉลี่ยถูกตัดเศษเป็นจำนวนเต็มหารผลรวมจำนวนเต็มด้วยจำนวนเต็มโดยไม่แปลงชนิดแปลงตัวตั้งเป็น double ก่อนหาร เช่น (double)(s1 + s2 + s3) / 3
คอมไพล์แจ้งเตือนเกี่ยวกับตัวระบุรูปแบบใช้ %d กับค่าชนิด doubleใช้ %.2f หรือ %.3f กับค่าทศนิยม
ผล % ผิดพลาดหรือคอมไพล์ไม่ผ่านใช้ % กับค่าทศนิยมใช้ % กับจำนวนเต็มเท่านั้น

เปิดหลังผู้เรียนส่งงานแล้ว

ใบงานตรวจ Lab B: เปลี่ยนข้อมูลเพื่อแยกวิธีที่ถูกและผิด

ก่อนเทียบตารางผลตัวอย่าง ให้สร้างตารางคำทำนายของตนเองแยกไว้ ทดลองคู่ข้อมูลต่อไปนี้ด้วยสูตรเดิม แล้วจดชนิดที่ใช้ ณ จุดหารทุกครั้ง

ค่า a, ba / b ที่คาดa % b ที่คาด(double)a / b ที่คาดสิ่งที่ตรวจพบหลังรัน
9, 4ก่อนรันก่อนรันก่อนรันอธิบายจุดที่มีเศษ
8, 4ก่อนรันก่อนรันก่อนรันเหตุใดคู่หารลงตัวอาจซ่อนความผิด
1, 4ก่อนรันก่อนรันก่อนรันแยกค่าศูนย์ที่คำนวณได้จากการอ่านข้อมูลผิด

สำหรับงานต่อยอดค่าเฉลี่ย ใช้คะแนนสมมติ 7, 8 และ 10 เปรียบเทียบการหารจำนวนเต็ม การแปลงผลรวมก่อนหาร และการแปลงหลังหาร ให้ระบุว่าความต่างเกิดก่อนการจัดรูปแบบแสดงผล ไม่อธิบายเพียงว่าเปลี่ยนจำนวนหลักทศนิยมแล้วถูก

เก็บกวาด

เมื่อจบปฏิบัติการ ให้ลบไฟล์ปฏิบัติการที่สร้างจากการคอมไพล์ (เช่น student_info และ ch2_demo) และเก็บเฉพาะไฟล์ซอร์สโค้ด .c ไว้ เพื่อให้โฟลเดอร์งานสะอาดและส่งไฟล์ซอร์สโค้ด .c พร้อมบันทึกผลการรันตามที่กำหนด

ตรวจความเข้าใจก่อนเริ่มบทที่ 3

ตอบด้วยภาษาของตนเองโดยยกข้อมูลจากงานที่ทดลองจริง ไม่คัดคำตอบตัวอย่างมาแทนคำอธิบาย

  1. เหตุใดจำนวนสายกับน้ำหนักเฉลี่ยจึงเลือกชนิดต่างกัน แม้ต่างก็เป็นตัวเลข
  2. เมื่อ a และ b เป็น int การเขียน double result = a / b; รับรองว่าจะเก็บส่วนเศษหรือไม่ เพราะอะไร
  3. ยกคู่ข้อมูลที่ช่วยพบตำแหน่ง cast ผิด และอธิบายว่าคู่หารลงตัวอาจพลาดความผิดนี้อย่างไร
  4. หลังคัดลอกค่าจากตัวแปรหนึ่งไปอีกตัว เหตุใดการแก้ตัวแรกจึงไม่เปลี่ยนตัวที่สองเอง
  5. หน่วยของตัวแปรกับชนิดของตัวแปรช่วยป้องกันความผิดคนละแบบอย่างไร
  6. ผล sizeof ที่ได้จากเครื่องหนึ่งบอกอะไรได้ และยังสรุปอะไรกับทุกเครื่องไม่ได้

การตรวจนี้เป็นกิจกรรมระหว่างเรียน ไม่เพิ่มคะแนนหรือเงื่อนไขผ่านจากแผนเดิม เก็บตารางคำทำนาย ผลจริง และเหตุผลที่แก้ไว้ใช้ต่อในบทที่ 3 การรับเข้าและการแสดงผล ซึ่งจะนำค่าที่ผู้ใช้ป้อนมาแทนค่าคงที่ในตัวอย่าง

สรุป

บทนี้อธิบายหน่วยพื้นฐานที่ใช้สร้างทุกโปรแกรม เริ่มจากตัวแปรและการประกาศด้วยรูปแบบ ชนิด ชื่อ = ค่า; ชนิดข้อมูลพื้นฐานสี่ชนิด (int, float, double, char) และการกำหนดค่าคงที่ด้วย const จากนั้นอธิบายตัวดำเนินการเลขคณิตและนิพจน์ โดยเน้นผลของการหารจำนวนเต็มที่ตัดเศษ ตัวดำเนินการหาเศษเหลือ ลำดับความสำคัญของตัวดำเนินการ และการแปลงชนิดข้อมูลด้วยการ cast เพื่อให้ผลลัพธ์เป็นทศนิยมที่ถูกต้อง ทักษะการเลือกชนิดข้อมูลและการเขียนนิพจน์ที่ถูกต้องในบทนี้เป็นพื้นฐานของการรับ-แสดงผลและโครงสร้างควบคุมในบทถัดไป

สิ่งที่ส่งต่อไปยังบทถัดไป

สิ่งที่ส่งต่อค่า/รูปแบบใช้ในบท
ชนิดข้อมูลพื้นฐานint, float, double, charบท 3 เป็นต้นไป
การประกาศตัวแปรและค่าคงที่ชนิด ชื่อ = ค่า; และ constบท 3 เป็นต้นไป
ตัวดำเนินการเลขคณิตและลำดับความสำคัญ+ - * / % และวงเล็บบท 4–5 (เงื่อนไขและการวนซ้ำ)
การแปลงชนิดข้อมูล(double)a / bบท 3 (การจัดรูปแบบการแสดงผล) เป็นต้นไป

สรุปคำสั่ง

คำสั่งหน้าที่
gcc -std=c11 -Wall student_info.c -o student_infoคอมไพล์ซอร์สโค้ด student_info.c เป็นไฟล์ปฏิบัติการ พร้อมเปิดการแจ้งเตือน
gcc -std=c11 -Wall ch2_demo.c -o ch2_demoคอมไพล์ซอร์สโค้ด ch2_demo.c เป็นไฟล์ปฏิบัติการ พร้อมเปิดการแจ้งเตือน
./student_info หรือ ./ch2_demoรันไฟล์ปฏิบัติการที่คอมไพล์แล้ว

แบบฝึกหัดท้ายบท

ตอนที่ 1 ฝึกทบทวน (15 ข้อ ตรวจคำตอบให้ทันที)

ตอนที่ 2 แบบฝึกหัดเขียนตอบ (5 ข้อ)

กลุ่ม ก (เข้าใจ)

  • ก1 (LLO2.1) จงอธิบายความแตกต่างระหว่างชนิด int, double และ char พร้อมยกตัวอย่างข้อมูลที่เหมาะกับแต่ละชนิดอย่างละหนึ่งตัวอย่าง
  • ก2 (LLO2.1) จงอธิบายว่าการประกาศค่าด้วย const ต่างจากตัวแปรธรรมดาอย่างไร และยกตัวอย่างค่าที่ควรประกาศเป็นค่าคงที่

กลุ่ม ข (ประยุกต์)

  • ข1 (LLO2.2) กำหนดให้ a เป็น 17 และ b เป็น 5 จงทำนายผลของ a / b, a % b และ (double)a / b แล้วเขียนโปรแกรมภาษา C ตรวจสอบ คอมไพล์และรันให้ได้ผลจริง
  • ข2 (LLO2.3) จงเขียนโปรแกรมภาษา C คำนวณพื้นที่สี่เหลี่ยมคางหมูจากความยาวด้านคู่ขนานสองด้านและความสูงที่กำหนดเป็นค่าในโปรแกรม โดยให้ผลเป็นทศนิยมที่ถูกต้อง แล้วแสดงผลด้วย printf

กลุ่ม ค (วิเคราะห์)

  • ค1 (LLO2.3) จากโปรแกรมที่คำนวณค่าเฉลี่ยของจำนวนเต็มสามจำนวนด้วย int average = (s1 + s2 + s3) / 3; จงวิเคราะห์ว่าเหตุใดผลจึงอาจผิดพลาด และเสนอการแก้ไขด้วยการแปลงชนิดข้อมูลให้ถูกต้อง

การศึกษาค้นคว้าเพิ่มเติม

  1. ศึกษาชนิดข้อมูลพื้นฐานและตัวดำเนินการของภาษา C จาก Kernighan และ Ritchie (1988) บทที่ 2 แล้วสรุปความแตกต่างระหว่าง float และ double ด้วยถ้อยคำของตนเอง
  2. ทบทวนกฎการแปลงชนิดข้อมูลและลำดับความสำคัญของตัวดำเนินการจาก Kernighan และ Ritchie (1988) แล้วทดลองปรับโปรแกรม ch2_demo.c ให้คำนวณค่าเฉลี่ยของจำนวนเต็มหลายจำนวน
  3. ทบทวนภาพรวมมาตรฐานภาษา C ตาม ISO/IEC 9899 และหน้า Arithmetic types ของ cppreference เพื่อเข้าใจขอบเขตและช่วงค่าต่ำสุดที่มาตรฐานรับประกันของชนิดข้อมูลพื้นฐาน

เอกสารอ้างอิง

cppreference.com. (2024). Arithmetic types — cppreference.com. สืบค้นจาก https://en.cppreference.com/w/c/language/arithmetic_types

cppreference.com. (2024). C operator precedence — cppreference.com. สืบค้นจาก https://en.cppreference.com/w/c/language/operator_precedence

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 ขึ้นกับสภาพแวดล้อม ไม่ใช่ผลตายตัว

แหล่งปฐมภูมิเพิ่มสำหรับชุดสอนพร้อมใช้บทที่ 2

ตรวจเนื้อหาแหล่งข้อมูลเมื่อ 23 ตุลาคม 2569 แล้วเรียบเรียงคำอธิบายและสร้างตัวอย่างข้อมูลสมมติของบทนี้ ไม่ใช้ผลการทดลองเฉพาะเครื่องเป็นข้อกำหนดทั่วไปของ C11

By Watcharin Sarachai