Sign in

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

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

บทที่ 7 อาร์เรย์

ShareFacebookXLinkedInLINE

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

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

ผลลัพธ์ข้อความหลักฐานCLO
LLO7.1อธิบายและใช้อาร์เรย์หนึ่งมิติ (ประกาศ กำหนดค่าเริ่มต้น เข้าถึงสมาชิกด้วยดัชนี)งานข้อ 1 (ปฏิบัติการที่ 7 Lab A)CLO1
LLO7.2ใช้ลูปประมวลผลอาร์เรย์ (ผลรวม ค่าเฉลี่ย ค้นหา) โดยระวังขอบเขตดัชนีงานข้อ 2 (ปฏิบัติการที่ 7 Lab A)CLO2
LLO7.3ใช้อาร์เรย์สองมิติ และส่งอาร์เรย์เข้าฟังก์ชันงานข้อ 3 (ปฏิบัติการที่ 7 Lab B)CLO2
LLO7.4ปฏิบัติตามมาตรฐานการเขียนโค้ด (การจัดย่อหน้า การตั้งชื่อสื่อความหมาย การใส่คอมเมนต์) ในงานโค้ดของบทนี้โค้ดทุกข้อที่ส่งในบทนี้CLO5

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

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

บทที่ 7 เป็นจุดที่นำการวนซ้ำจากบทที่ 5 และฟังก์ชันจากบทที่ 6 มาใช้กับข้อมูลชุดที่เก็บในอาร์เรย์ จึงเป็นสะพานเชื่อมไปสู่การจัดการสตริงในบทที่ 8 (สตริงคืออาร์เรย์ของอักขระ) โครงสร้างข้อมูลแบบระเบียนในบทที่ 9 และการเรียงลำดับและการค้นหาในบทที่ 10 งานทั้งสามข้อในบทนี้สะสมหลักฐานของ CLO1 และ CLO2 ต่อเนื่องจากบทก่อน งานของบทนี้อยู่ในหมวดงานท้ายบทตามแผนคะแนนที่ผู้สอนยืนยันไว้ นอกจากนี้บทนี้ยังทำหน้าที่เป็น จุดตรวจความซื่อสัตย์ทางวิชาการ จุดที่ 2 (CLO4 Checkpoint 2: ทบทวนความซื่อสัตย์ก่อนสอบกลางภาค) เพื่อให้ผู้เรียนสำรวจประวัติการเขียนโค้ดและแหล่งอ้างอิงในใบงานที่ 1–7 ก่อนเข้าสู่การสอบกลางภาค (เป็นจุดตรวจให้ข้อเสนอแนะ ไม่คิดสัดส่วนคะแนน) (ดูบันทึกสำหรับผู้สอนท้ายบท)

วิธีสอน

  1. ทบทวนการวนซ้ำด้วยลูป for จากบทที่ 5 และการส่งค่าเข้าฟังก์ชันจากบทที่ 6 แล้วตั้งโจทย์ที่ต้องเก็บข้อมูลหลายค่า เช่น คะแนนนักศึกษาหลายคน เพื่อชี้ให้เห็นความจำเป็นของอาร์เรย์แทนการประกาศตัวแปรแยกหลายตัว
  2. แนะนำการประกาศอาร์เรย์หนึ่งมิติและการเข้าถึงสมาชิกด้วยดัชนีที่เริ่มจาก 0 โดยสาธิตบนเครื่องจริง และย้ำว่าอาร์เรย์ขนาด n มีดัชนีตั้งแต่ 0 ถึง n-1
  3. สาธิตการวนประมวลผลอาร์เรย์ด้วยลูป for เพื่อหาผลรวมและค่าเฉลี่ย พร้อมชี้กับดักการวนเกินขอบเขตดัชนี
  4. อธิบายการส่งอาร์เรย์เข้าฟังก์ชันพร้อมขนาด และชี้ว่าฟังก์ชันทำงานกับอาร์เรย์เดิม ต่างจากการส่งตัวแปรเดี่ยวแบบสำเนาค่า (by value) ในบทที่ 6
  5. สาธิตอาร์เรย์สองมิติและลูปซ้อน แล้วให้ลงมือปฏิบัติตามใบงาน โดยกำหนดจุดตรวจระหว่างทางเพื่อให้ข้อมูลป้อนกลับทันที

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

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

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

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

ข้องานหลักฐานผลลัพธ์CLO
1ประกาศอาร์เรย์คะแนน กำหนดค่าเริ่มต้น และแสดงสมาชิกทุกตัวด้วยลูปไฟล์ซอร์สโค้ด + ผลการรันLLO7.1CLO1
2คำนวณผลรวมและค่าเฉลี่ยของอาร์เรย์ด้วยลูป โดยไม่เกินขอบเขตดัชนีไฟล์ซอร์สโค้ด + ผลการรันLLO7.2CLO2
3ใช้อาร์เรย์สองมิติแสดงตาราง และเขียนฟังก์ชันที่รับอาร์เรย์หนึ่งมิติไฟล์ซอร์สโค้ด + ผลการรันLLO7.3CLO2

เงื่อนไขผ่าน: งานข้อ 2 ประเมินจากไฟล์เดียวกับงานข้อ 1 ที่ต่อยอดการวนประมวลผลอาร์เรย์ ดังนั้นงานข้อ 1 ต้องประกาศอาร์เรย์และแสดงสมาชิกครบถูกต้องและคอมไพล์ผ่านก่อนจึงจะประเมินข้อ 2 ได้

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

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

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

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

งานข้อ 1 ใช้เกณฑ์ผ่าน/ไม่ผ่าน:

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

งานข้อ 2 ใช้เกณฑ์ผ่าน/ไม่ผ่าน ร่วมกับรายการตรวจ (checklist): ผลรวมและค่าเฉลี่ยถูกต้อง, เงื่อนไขลูปอยู่ในช่วงดัชนี 0 ถึง n-1 ไม่เกินขอบเขต, ใช้ %.2f กับค่าเฉลี่ยที่เป็นทศนิยม, จัดย่อหน้า 4 ช่องว่างและมีคอมเมนต์ส่วนหัว

งานข้อ 3 ใช้เกณฑ์ผ่าน/ไม่ผ่าน ร่วมกับรายการตรวจ: อาร์เรย์สองมิติแสดงตารางถูกต้อง, ส่งอาร์เรย์เข้าฟังก์ชันพร้อมขนาดถูกต้อง, คอมไพล์ผ่านไม่มีการแจ้งเตือน, เป็นไปตามมาตรฐานโค้ด

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

บทนำ

โปรแกรมจำนวนมากต้องจัดเก็บข้อมูลชนิดเดียวกันหลายค่า เช่น คะแนนของนักศึกษาทั้งห้อง หรือค่าที่วัดได้หลายครั้ง การประกาศตัวแปรแยกทีละตัวย่อมไม่สะดวกและขยายไม่ได้เมื่อจำนวนข้อมูลเพิ่มขึ้น อาร์เรย์ (array) จึงเข้ามาแก้ปัญหานี้ด้วยการเก็บข้อมูลชนิดเดียวกันหลายค่าไว้ในชื่อเดียว แล้วเข้าถึงแต่ละค่าด้วยดัชนี (index) บทที่ 5 ได้แนะนำการวนซ้ำด้วยลูป และบทที่ 6 ได้แนะนำฟังก์ชันไปแล้ว บทนี้นำทั้งสองเรื่องมาใช้กับข้อมูลชุดที่เก็บในอาร์เรย์ ทั้งอาร์เรย์หนึ่งมิติและสองมิติ รวมถึงการส่งอาร์เรย์เข้าฟังก์ชัน

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

สิ่งที่ได้จากบทนี้ใช้ต่อในบท
อาร์เรย์หนึ่งมิติ ประกาศ กำหนดค่าเริ่มต้น และเข้าถึงด้วยดัชนีเริ่ม 0บท 8 (สตริง), บท 9 (struct), บท 10 (เรียง/ค้นหา)
การวนประมวลผลอาร์เรย์ด้วยลูปอย่างระวังขอบเขตทุกบทที่ประมวลผลข้อมูลเป็นชุด
การส่งอาร์เรย์เข้าฟังก์ชัน (arr[], ขนาด) และลูปซ้อนสำหรับอาร์เรย์สองมิติบท 10 (เรียง/ค้นหา) เป็นต้นไป

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

บทนี้ต้องใช้คอมไพเลอร์ภาษา C และโปรแกรมแก้ไขข้อความหรือ IDE และต้องมีความรู้เรื่องการวนซ้ำด้วยลูป for จากบทที่ 5 การออกแบบฟังก์ชันและการส่งค่าเข้าฟังก์ชันจากบทที่ 6 และการแปลงชนิดข้อมูล (cast) จากบทที่ 2 ตารางด้านล่างเป็นรุ่นเครื่องมือที่ใช้ทดสอบโค้ดตัวอย่างในบทนี้จริง

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

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

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

ทบทวน บทที่ 6 ฟังก์ชันและการออกแบบแบบโมดูล โดยอธิบายข้อมูลเข้าและผลคืนของฟังก์ชัน จากนั้นเพิ่มคำถามว่า “ข้อมูลมีกี่สมาชิก และสมาชิกใดถูกตั้งค่าแล้ว”

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

ทุกชุดคะแนนและความพึงพอใจเป็นข้อมูลสมมติ ไม่ใช่ข้อมูลนักศึกษา เกณฑ์งานส่งและ LLO7.1–7.3 ยังคงเดิม กิจกรรมเพิ่มใช้ฝึกและให้ข้อมูลป้อนกลับ ส่วนเวลาและแนวคำตอบอยู่ในส่วนผู้สอน

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

7.1 อาร์เรย์หนึ่งมิติ: การประกาศและการเข้าถึง

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

ระดับที่ 2: การประกาศอาร์เรย์ระบุชนิดข้อมูล ชื่อ และจำนวนสมาชิกในวงเล็บเหลี่ยม เช่น int scores[5]; ประกาศอาร์เรย์ชื่อ scores ที่เก็บจำนวนเต็มได้ 5 ค่า และกำหนดค่าเริ่มต้นพร้อมประกาศได้ด้วยวงเล็บปีกกา เช่น int scores[5] = {80, 75, 90, 65, 100}; การเข้าถึงสมาชิกใช้ชื่ออาร์เรย์ตามด้วยดัชนีในวงเล็บเหลี่ยม โดยดัชนีเริ่มจาก 0 ดังนั้น scores[0] คือสมาชิกตัวแรก และ scores[4] คือสมาชิกตัวที่ห้า

C
int scores[5] = {80, 75, 90, 65, 100};
printf("%d\n", scores[0]);

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

คำถามตรวจความเข้าใจ 7.1: อาร์เรย์ขนาด 5 มีดัชนีที่ใช้ได้ตั้งแต่เท่าใดถึงเท่าใด และสมาชิกตัวแรกเข้าถึงด้วยดัชนีใด

ใบงาน 1: แยกความจุ จำนวนข้อมูล และดัชนี

วาดช่องเก็บข้อมูลสิบช่องสำหรับ int scores[10]; แล้วใส่เพียง 40, 60, 50, 80 และ 70 กำกับดัชนีไว้เหนือช่อง อย่าเติมศูนย์ในช่องที่ยังไม่ได้ตั้งค่าแล้วนำมาคิดเป็นข้อมูลจริง

สถานะความจุcountดัชนีตัวแรกที่มีข้อมูลดัชนีตัวสุดท้ายที่มีข้อมูลตำแหน่งที่จะเก็บถัดไปถ้ายังมีที่
ยังไม่รับข้อมูล100
รับแล้วหนึ่งค่า101
รับแล้วห้าค่า105
รับครบสิบค่า1010

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

อภิปรายว่า int values[5] = {0}; ตั้งค่าเริ่มต้นครบเป็นศูนย์ต่างจาก int values[5]; ภายในบล็อกอย่างไร แม้ตั้งศูนย์ครบก็ยังต้องแยกว่า “มีพื้นที่พร้อมใช้” กับ “ผู้ใช้ส่งข้อมูลจริงกี่ค่า”

7.2 การประมวลผลอาร์เรย์ด้วยลูป

ระดับที่ 1: การประมวลผลอาร์เรย์มักทำโดยวนทีละสมาชิกตั้งแต่ตัวแรกถึงตัวสุดท้ายด้วยลูป for จากบทที่ 5 โดยใช้ตัวแปรนับเป็นดัชนีของอาร์เรย์

ระดับที่ 2: รูปแบบที่ใช้บ่อยคือให้ตัวนับ i เริ่มที่ 0 และวนขณะที่ i ยังน้อยกว่าขนาดอาร์เรย์ ดังเงื่อนไข i < size แล้วเข้าถึงสมาชิกด้วย arr[i] การหาผลรวมทำด้วยตัวแปรสะสมจากบทที่ 5 ด้วย sum += arr[i]; และค่าเฉลี่ยคำนวณจากผลรวมหารด้วยจำนวนสมาชิก โดยแปลงชนิดเป็นทศนิยมด้วยการ cast จากบทที่ 2 เป็น (double)total / size เพื่อไม่ให้ปัดเศษเป็นจำนวนเต็ม

C
int sum = 0;
for (int i = 0; i < 5; i++) {
    sum += scores[i];
}

การค้นหาสมาชิกทำได้โดยวนเทียบทีละตัวกับค่าที่ต้องการ และหยุดด้วย break เมื่อพบ ตามที่ฝึกในบทที่ 5

ระดับที่ 3: กับดักที่พบบ่อยคือความคลาดเคลื่อนที่ขอบเขต (off-by-one) เมื่อตั้งเงื่อนไขลูปไม่พอดีกับขนาดอาร์เรย์ เช่น การใช้ i <= size แทน i < size ทำให้วนไปถึงดัชนี size ซึ่งเกินสมาชิกตัวสุดท้าย (ดัชนีสูงสุดคือ n-1) และเข้าถึงนอกขอบเขต การตั้งเงื่อนไขให้ตัวนับน้อยกว่าขนาดอาร์เรย์เสมอ และตรวจค่าดัชนีในรอบแรกและรอบสุดท้าย ช่วยลดข้อผิดพลาดนี้ได้

คำถามตรวจความเข้าใจ 7.2: การเข้าถึง scores[5] ในอาร์เรย์ขนาด 5 เกิดปัญหาอะไร และควรตั้งเงื่อนไขลูปอย่างไรจึงจะไม่เกินขอบเขต

ใบงาน 2: นับการเข้าถึงให้ถึงสมาชิกสุดท้ายพอดี

ใช้ข้อมูล Lab A แล้วเติมค่าที่ถูกอ่านและ sum หลังแต่ละรอบ โดยเริ่ม sum ที่ 0 การตรวจครั้งที่ i เป็น 5 มีไว้จบลูป ไม่ใช่ให้อ่าน scores[5]

i ที่ตรวจi < 5สมาชิกที่อ่านได้ในรอบนี้sum หลังบวก
0
1
2
3
4
5

เขียนรายการดัชนีของลูปที่เริ่ม 1 และใช้ i < 5 เปรียบเทียบกับฉบับเริ่ม 0 ทีละอย่าง หากแก้เป็น i <= 5 ให้ระบุตำแหน่งผิดบนกระดาษ ไม่ต้องรันการเข้าถึงนอกอาร์เรย์เพื่อสาธิต

ส่วนตัวอย่าง mean_score ใช้ count เป็นบวกและไม่เกินจำนวนสมาชิกจริง พร้อมผลรวมที่อยู่ใน int สำหรับชุดฝึก 0–100 ไม่เกินสิบค่า ผลรวมสูงสุด 1000 จึงไม่ล้นชนิดและแปลงเป็น double ก่อนหารได้

ตัวอย่างค้นหา: ตำแหน่งศูนย์ไม่ใช่ไม่พบ

สร้างไฟล์ find_first.c ให้ฟังก์ชันคืนดัชนีแรกที่พบ และคืน -1 เมื่อไม่พบ สัญญากำหนด count ไม่ติดลบและไม่เกินจำนวนสมาชิกที่ใช้ได้ ฟังก์ชันไม่แก้ข้อมูลผ่านพารามิเตอร์ const

C
/* find_first.c : first matching index, or -1 */
#include <stdio.h>
 
int find_first(const int values[], int count, int target);
 
int main(void)
{
    const int values[5] = {40, 60, 40, 80, 70};
    int target;
    if (scanf("%d", &target) != 1) {
        puts("Input error");
        return 1;
    }
    int index = find_first(values, 5, target);
    if (index == -1) {
        puts("Not found");
    } else {
        printf("index=%d value=%d\n", index, values[index]);
    }
    return 0;
}
 
/* Read only values[0] through values[count - 1]. */
int find_first(const int values[], int count, int target)
{
    for (int i = 0; i < count; ++i) {
        if (values[i] == target) {
            return i;
        }
    }
    return -1;
}

ก่อนรันให้ทำนายเมื่อค้นหา 40, 70 และ 99 ระบุว่าข้อมูล 40 ที่มีสองตำแหน่งจะเลือกตัวใด การตรวจเพียง if (index) ไม่ตรงสัญญา เพราะดัชนี 0 เป็นคำตอบที่ถูกต้องและ -1 ไม่ใช่ศูนย์

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

7.3 การส่งอาร์เรย์เข้าฟังก์ชัน

ระดับที่ 1: ฟังก์ชันจากบทที่ 6 สามารถรับอาร์เรย์เป็นพารามิเตอร์ได้ เพื่อแยกการประมวลผลอาร์เรย์ออกเป็นโมดูลที่นำกลับมาใช้ซ้ำได้ แต่ต้องส่งขนาดของอาร์เรย์ไปพร้อมกันเสมอ

ระดับที่ 2: การประกาศพารามิเตอร์อาร์เรย์ใช้วงเล็บเหลี่ยมว่างตามชื่อ เช่น int sum_array(int arr[], int size) รับอาร์เรย์จำนวนเต็ม arr และขนาด size เหตุที่ต้องส่งขนาดไปด้วยเพราะฟังก์ชันไม่สามารถทราบจำนวนสมาชิกของอาร์เรย์จากตัวพารามิเตอร์เองได้ การวนประมวลผลภายในฟังก์ชันจึงต้องอาศัย size เป็นขอบเขต

C
int sum_array(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum;
}

ระดับที่ 3: จุดสำคัญที่ต่างจากบทที่ 6 คือ เมื่อส่งอาร์เรย์เข้าฟังก์ชัน ฟังก์ชันจะทำงานกับอาร์เรย์เดิมโดยตรง ไม่ใช่สำเนาของอาร์เรย์ ดังนั้นถ้าฟังก์ชันแก้ค่าสมาชิก ค่าในอาร์เรย์เดิมจะเปลี่ยนตามไปด้วย แต่ภาษา C ยังคงส่งค่าแบบ by value เช่นเดิม พารามิเตอร์อาร์เรย์ถูกปรับเป็นพอยน์เตอร์และรับสำเนาค่าที่ชี้ไปยังสมาชิกชุดเดิม จึงแก้สมาชิกผ่านค่านั้นได้ สาเหตุเชิงลึกของพฤติกรรมนี้เกี่ยวข้องกับทฤษฎีตัวชี้ (pointer) ซึ่งเป็นหัวข้อเสริมที่จะศึกษาต่อภายหลัง ในบทนี้เพียงจำหลักว่าฟังก์ชันที่รับอาร์เรย์ทำงานกับอาร์เรย์เดิมได้ก็เพียงพอ

คำถามตรวจความเข้าใจ 7.3: เหตุใดการส่งอาร์เรย์เข้าฟังก์ชันจึงต้องส่งขนาดไปด้วย และผลของการแก้ค่าสมาชิกภายในฟังก์ชันต่างจากการส่งตัวแปรเดี่ยวแบบส่งสำเนาค่าอย่างไร

7.4 อาร์เรย์สองมิติ

ระดับที่ 1: อาร์เรย์สองมิติใช้เก็บข้อมูลที่จัดเป็นตารางแบบแถวและหลัก เช่น ตารางตัวเลขหรือเมทริกซ์ โดยมองเป็นอาร์เรย์ของแถว ที่แต่ละแถวมีหลายหลัก

ระดับที่ 2: การประกาศอาร์เรย์สองมิติระบุจำนวนแถวและจำนวนหลักในวงเล็บเหลี่ยมสองชุด เช่น int table[2][3] ประกาศตาราง 2 แถว 3 หลัก และกำหนดค่าเริ่มต้นด้วยวงเล็บปีกกาซ้อน เช่น int table[2][3] = { {1, 2, 3}, {4, 5, 6} }; การเข้าถึงสมาชิกใช้สองดัชนี table[r][c] โดย r คือดัชนีแถวและ c คือดัชนีหลัก การวนทุกสมาชิกจึงใช้ลูปซ้อน (nested loop) คือลูปนอกวนแถวและลูปในวนหลัก

C
for (int r = 0; r < 2; r++) {
    for (int c = 0; c < 3; c++) {
        printf("%d ", table[r][c]);
    }
    printf("\n");
}

ระดับที่ 3: ภาษา C จัดเก็บอาร์เรย์สองมิติแบบเรียงตามแถว (row-major) คือสมาชิกของแถวเดียวกันอยู่ติดกันในหน่วยความจำ ลูปนอกจึงควรวนตามแถวและลูปในวนตามหลักเพื่อเข้าถึงตามลำดับที่เป็นธรรมชาติ ดัชนีทั้งสองมิติต่างมีช่วงตั้งแต่ 0 ถึงจำนวนในมิตินั้นลบหนึ่ง การสลับแถวกับหลักหรือการวนลูปซ้อนเกินขอบเขตเป็นข้อผิดพลาดที่พบบ่อย อาร์เรย์สองมิติใช้แทนตารางคะแนน ตารางเวลา หรือเมทริกซ์ในงานคำนวณจริง

คำถามตรวจความเข้าใจ 7.4: อาร์เรย์ int table[2][3] มีกี่แถวและกี่หลัก และการวนแสดงสมาชิกทุกตัวต้องใช้ลูปลักษณะใด

ตัวอย่างพร้อมใช้: ปฏิเสธรายการที่เกินความจุก่อนเขียน

สร้างไฟล์ bounded_store.c รับจำนวนคำขอ 0–11 ครั้ง แล้วรับค่าครั้งละหนึ่งจำนวนช่วง -100 ถึง 100 เก็บได้ไม่เกินสิบค่า รายการที่สิบเอ็ดถูกอ่านและตรวจช่วงก่อนปฏิเสธ แต่ไม่เปลี่ยน count หรือข้อมูลเดิม

C
/* bounded_store.c : check capacity before storing */
#include <stdio.h>
 
int main(void)
{
    int values[10];
    int count = 0;
    int attempts;
    if (scanf("%d", &attempts) != 1) {
        puts("Input error");
        return 1;
    }
    if (attempts < 0 || attempts > 11) {
        puts("Range error");
        return 1;
    }
 
    for (int attempt = 0; attempt < attempts; ++attempt) {
        int value;
        if (scanf("%d", &value) != 1) {
            puts("Input error");
            return 1;
        }
        if (value < -100 || value > 100) {
            puts("Range error");
            return 1;
        }
        if (count == 10) {
            puts("Full");
            continue;
        }
        values[count] = value;
        ++count;
    }
 
    printf("count=%d values:", count);
    int sum = 0;
    for (int i = 0; i < count; ++i) {
        printf(" %d", values[i]);
        sum += values[i];
    }
    puts("");
    if (count == 0) {
        puts("No data");
    } else {
        printf("sum=%d mean=%.2f\n", sum, (double)sum / count);
    }
    return 0;
}
กรณีทดสอบสิ่งที่ต้องตรวจทำนายก่อนรันผลจริง
attempts เป็น 0ไม่อ่านสมาชิกและไม่หารศูนย์
หนึ่งค่าเป็น -5ใช้ดัชนี 0 และเฉลี่ยได้ค่าลบ
ครบสิบค่าcount เท่ากับความจุ
ขอสิบเอ็ดค่า โดยค่าท้ายเป็น 99มี Full และสิบค่าเดิมยังอยู่
ข้อมูลหมดก่อนครบคำขอจบด้วย Input error

แยก attempt ซึ่งนับจำนวนครั้งที่ลองจาก count ซึ่งนับจำนวนค่าที่เก็บจริง ลูปสรุปใช้ count ไม่ใช่ attempts หรือความจุสิบเสมอ การตรวจ count == 10 เพียงพอในตัวอย่างนี้เพราะเริ่ม 0 และเพิ่มหลังเก็บสำเร็จทีละหนึ่งเท่านั้น

7.5 กรณีศึกษาต่อเนื่อง: เมื่อจำเป็นต้องเก็บคะแนนไว้ใช้หลายครั้ง

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

7.5.1 ความจุ จำนวนที่ใช้ และดัชนีเป็นสามแนวคิด

กำหนด int scores[10]; หมายถึงมีพื้นที่สิบสมาชิก แต่หากรับคะแนนจริงเพียงห้าค่า จำนวนที่ใช้คือห้า ดัชนีที่ใช้เข้าถึงข้อมูลจริงจึงเป็น 0–4 ไม่ใช่ 0–9 ทั้งหมด สมาชิกที่ยังไม่ได้ตั้งค่าไม่ควรถูกนำมาคำนวณเพียงเพราะอยู่ในพื้นที่อาร์เรย์

GNU C: Accessing Array Elements ระบุว่าดัชนีเริ่มศูนย์และ C ไม่ตรวจขอบเขตให้อัตโนมัติ การอ่านหรือเขียนนอกขอบเขตไม่ได้รับประกันว่าจะหยุดพร้อมข้อความเตือน อาจดูเหมือนทำงานได้ในบางครั้ง จึงไม่ควรใช้การ “ไม่แครช” เป็นหลักฐานว่าดัชนีถูก

สิ่งที่กำลังนับตัวอย่างค่าใช้เพื่ออะไร
ความจุที่จัดไว้10จำกัดจำนวนข้อมูลสูงสุด
จำนวนข้อมูลจริง5กำหนดช่วงที่มีความหมาย
ดัชนีของสมาชิกสุดท้ายที่ใช้4เข้าถึงค่าตัวสุดท้าย
จำนวนครั้งที่วนครบข้อมูล5ประมวลผลสมาชิก 0–4

7.5.2 ประมวลผลสองรอบด้วยเหตุผลที่ต่างกัน

C
#include <stdio.h>
 
double mean_score(const int scores[], int count)
{
    int sum = 0;
    for (int i = 0; i < count; ++i) {
        sum += scores[i];
    }
    return (double)sum / count;
}
 
int main(void)
{
    const int scores[] = {40, 60, 50, 80, 70};
    const int count = 5;
    double mean = mean_score(scores, count);
 
    printf("mean=%.2f\n", mean);
    for (int i = 0; i < count; ++i) {
        printf("%d: score=%d difference=%+.2f\n",
               i, scores[i], scores[i] - mean);
    }
    return 0;
}

ผลเฉลี่ยเป็น 60.00 ส่วนต่างตามลำดับเป็น -20.00, +0.00, -10.00, +20.00 และ +10.00 รอบแรกสะสมเพื่อให้ได้ข้อมูลภาพรวม รอบที่สองใช้ภาพรวมนั้นกับสมาชิกแต่ละตัว ฟังก์ชันกำหนดข้อตกลงว่า count ต้องเป็นบวกและไม่เกินจำนวนสมาชิกที่ใช้ได้ ผู้เรียกในตัวอย่างส่งห้าแน่นอน ถ้าออกแบบเป็นฟังก์ชันทั่วไปต้องเพิ่มวิธีรายงานเมื่อไม่มีข้อมูล

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

ใบงานออกแบบ: เลือกเก็บทุกค่าหรือเก็บเฉพาะสรุป

เทียบงานสามแบบ ได้แก่รายงานผลรวมอย่างเดียว รายงานส่วนต่างจากค่าเฉลี่ยของแต่ละค่า และนับความถี่ระดับ 1–5 การมีข้อมูลหลายค่าไม่ได้บังคับให้เก็บรายการต้นฉบับทั้งหมดเสมอ

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

ใช้ข้อมูล 40 60 50 80 70 ไล่สองรอบของโปรแกรม mean_score รอบแรกหาค่าเฉลี่ย รอบที่สองอ่านแต่ละค่าเพื่อหาส่วนต่าง เปรียบเทียบกับแบบเรียก mean_score ใหม่ทุกครั้ง แม้ได้ตัวเลขเดียวกันก็อ่านสมาชิกซ้ำมากกว่า

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

7.6 การแก้สมาชิกผ่านฟังก์ชันและตารางสองมิติ

7.6.1 ส่งความยาวไปด้วย ไม่เดาขนาดจากพารามิเตอร์

เมื่อประกาศพารามิเตอร์เป็น int values[] ฟังก์ชันไม่ได้รับสำเนาอาร์เรย์ทั้งก้อนแบบตัวแปร int ในบทก่อน แต่เข้าถึงสมาชิกชุดเดิมได้ การแก้ values[0] จึงอาจเปลี่ยนข้อมูลที่ผู้เรียกเห็น ความเข้าใจนี้ไม่ขัดกับการส่งค่าใน C เพราะพารามิเตอร์อาร์เรย์ถูกปรับเป็นชนิดพอยน์เตอร์ รายละเอียดพอยน์เตอร์จะเรียนต่อภายหลัง ในระดับนี้ใช้กฎว่าต้องส่งจำนวนสมาชิกและระบุให้ชัดว่าฟังก์ชันอ่านหรือแก้ข้อมูล

การใช้ sizeof values / sizeof values[0] ภายในฟังก์ชันลักษณะนี้ไม่ใช่วิธีหาจำนวนสมาชิกเดิม ส่วน sizeof scores / sizeof scores[0] ใช้ได้ในขอบเขตที่ scores ยังเป็นอาร์เรย์จริง ควรอธิบายตามชนิดของนิพจน์ ไม่ใช้กฎจำว่า sizeof หาความยาวอาร์เรย์ได้ทุกแห่ง

ฟังก์ชันหาผลรวมสามารถรับ const int values[] เพื่อแสดงเจตนาว่าไม่แก้สมาชิกผ่านพารามิเตอร์นี้ แต่ const ไม่ทำหน้าที่ตรวจดัชนี ผู้เขียนยังต้องรักษาขอบเขตและส่งจำนวนที่ถูกต้อง

7.6.2 อาร์เรย์สองมิติต้องนิยามความหมายของแถวและคอลัมน์

สมมติคะแนนสามกลุ่มจากกิจกรรมสองครั้ง เป็นตารางแถวละกลุ่ม คอลัมน์ละกิจกรรม ข้อมูล {{8, 7}, {6, 9}, {10, 5}} ให้ผลรวมรายกลุ่ม 15, 15 และ 15 แต่ผลรวมรายกิจกรรมเป็น 24 และ 21 การใช้ข้อมูลชุดนี้ช่วยให้เห็นว่าผลรวมสองทิศอาจต่างกัน แม้รายกลุ่มดูเหมือนกันทั้งหมด

ก่อนเขียนลูปซ้อน ให้กำกับตารางว่า scores[group][activity] แล้ว trace วงรอบนอกกับวงรอบใน หากต้องการผลรวมรายกลุ่ม ให้รีเซ็ตตัวสะสมเมื่อเริ่มกลุ่มใหม่ หากต้องการผลรวมทั้งตาราง ให้ตั้งตัวสะสมก่อนทั้งสองลูป ตำแหน่งเริ่มค่าจึงสะท้อนขอบเขตของสิ่งที่ต้องการรวม

ใบงาน 3: แก้สมาชิกกับแก้ตัวนับเป็นคนละเรื่อง

สร้างไฟล์ array_update.c รับจำนวนสมาชิกที่จะใช้ช่วง 0–3 อาร์เรย์ตั้งค่าไว้ครบสามค่าเพื่อให้สังเกตได้ว่าการเปลี่ยน count ไม่ใช่การลบสมาชิกออกจากพื้นที่

C
/* array_update.c : shared elements and a copied count */
#include <stdio.h>
 
void update_first(int values[], int count);
 
int main(void)
{
    int values[3] = {10, 20, 30};
    int count;
    if (scanf("%d", &count) != 1) {
        puts("Input error");
        return 1;
    }
    if (count < 0 || count > 3) {
        puts("Range error");
        return 1;
    }
    update_first(values, count);
    printf("caller count=%d first=%d\n", count, values[0]);
    return 0;
}
 
/* Write one element only when the caller has at least one item. */
void update_first(int values[], int count)
{
    if (count == 0) {
        puts("No update");
        return;
    }
    values[0] = 99;
    count = 0;
    printf("inside count=%d\n", count);
}
count ที่ส่งแก้ values[0] หรือไม่count ภายในหลังแก้count ที่ผู้เรียกเห็นvalues[0] ที่ผู้เรียกเห็น
0
1
3

การส่งอาร์เรย์ไม่ได้สร้างสำเนาสมาชิกทั้งก้อนให้ฟังก์ชัน พารามิเตอร์ถูกปรับเป็นพอยน์เตอร์ที่เข้าถึงสมาชิกเดิม ส่วน count ยังเป็นสำเนาจำนวนเต็ม การแก้ count ในฟังก์ชันจึงไม่เปลี่ยน count ของ main

ในกรณี count เป็น 0 main ยังแสดง values[0] ได้ในโปรแกรมนี้เพราะมีพื้นที่และตั้งค่าเป็น 10 แล้ว แต่ค่านั้นไม่ถือเป็นข้อมูลที่รับไว้ใช้งานตาม count อย่านำตัวอย่างนี้ไปอ่านสมาชิกแรกของอาร์เรย์ที่ยังไม่ได้ตั้งค่า

ใบงาน 4: รวมแถวและคอลัมน์จากตารางเดียวกัน

สร้างไฟล์ matrix_totals.c รับข้อมูลหกค่าช่วง 0–100 ใส่ตาราง 3 แถว 2 คอลัมน์ กำหนดให้แถวแทนกลุ่มและคอลัมน์แทนกิจกรรม ต้องระบุความหมายก่อนเลือกทิศที่รวม

C
/* matrix_totals.c : row totals and column totals */
#include <stdio.h>
 
int row_total(int table[][2], int row);
int column_total(int table[][2], int rows, int column);
 
int main(void)
{
    int table[3][2];
    for (int r = 0; r < 3; ++r) {
        for (int c = 0; c < 2; ++c) {
            if (scanf("%d", &table[r][c]) != 1) {
                puts("Input error");
                return 1;
            }
            if (table[r][c] < 0 || table[r][c] > 100) {
                puts("Range error");
                return 1;
            }
        }
    }
    for (int r = 0; r < 3; ++r) {
        printf("row[%d]=%d\n", r, row_total(table, r));
    }
    for (int c = 0; c < 2; ++c) {
        printf("column[%d]=%d\n", c, column_total(table, 3, c));
    }
    return 0;
}
 
/* Read one valid row; the caller supplies row 0 through 2. */
int row_total(int table[][2], int row)
{
    int sum = 0;
    for (int c = 0; c < 2; ++c) {
        sum += table[row][c];
    }
    return sum;
}
 
/* Read a valid column; rows must not exceed the actual table. */
int column_total(int table[][2], int rows, int column)
{
    int sum = 0;
    for (int r = 0; r < rows; ++r) {
        sum += table[r][column];
    }
    return sum;
}

ใช้ข้อมูล 8 7 6 9 10 5 และ 1 2 3 4 5 6 ก่อนรันให้วาดตารางจริง แล้วทำนายผลรวมแถวและคอลัมน์ด้วยมือ ผลรวมทุกแถวควรเท่าผลรวมทุกคอลัมน์ แต่ผลรวมของแต่ละแถวไม่จำเป็นต้องเท่ากัน

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

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

7.7 เวิร์กช็อปขยาย: ตรวจความจุและเปรียบเทียบวิธีเก็บข้อมูล

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

ทดสอบชุดว่าง หนึ่งรายการ เต็มสิบรายการ และคำขอเพิ่มรายการที่สิบเอ็ด สำหรับชุดว่างต้องมีผลรายงานที่ชัดโดยไม่หารด้วยศูนย์ สำหรับรายการที่สิบเอ็ดต้องปฏิเสธโดยไม่เปลี่ยนสิบค่าที่เก็บไว้เดิม บันทึก count ก่อนและหลังการเพิ่มทุกครั้ง

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

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

7.8 ตัวอย่างเปรียบเทียบ: นับความถี่โดยให้ดัชนีมีความหมาย

สมมติกิจกรรมมีระดับความพึงพอใจ 1–5 และข้อมูลสมมติคือ 5, 4, 5, 3, 4, 5 ต้องรายงานว่ามีแต่ละระดับกี่ครั้ง อาร์เรย์ความถี่ห้าช่องสามารถกำหนดให้ดัชนี 0 แทนระดับ 1 ไปจนดัชนี 4 แทนระดับ 5 ต้องเขียนข้อตกลงนี้ก่อนใช้สูตร frequency[rating - 1]

เริ่มทุกความถี่ที่ศูนย์ หลังรับค่า 5 เพิ่มช่องดัชนี 4 หลังค่า 4 เพิ่มช่อง 3 และทำต่อจนครบ จะได้ความถี่ 0, 0, 1, 2, 3 ตามระดับ 1–5 ผลรวมของความถี่ต้องเท่าจำนวนข้อมูลที่ยอมรับคือหก ความสัมพันธ์นี้เป็นจุดตรวจที่ช่วยพบการลืมเพิ่มหรือเพิ่มซ้ำ

ระดับดัชนีความถี่สุดท้าย
100
210
321
432
543

ต้องตรวจ rating อยู่ใน 1–5 ก่อนลบหนึ่งแล้วใช้เป็นดัชนี หากรับศูนย์ ดัชนีจะเป็น -1 และถ้ารับหกจะเป็น 5 ซึ่งพ้นห้าช่อง ไม่ควรแก้ด้วยการเพิ่มขนาดอาร์เรย์เผื่อโดยไม่มีความหมาย เพราะข้อมูลยังผิดข้อกำหนด แม้บังเอิญไม่พ้นพื้นที่

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

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

ใบงาน 5: แปลงระดับเป็นดัชนีหลังตรวจช่วง

สร้างไฟล์ rating_frequency.c อ่านข้อมูลหกครั้ง รับระดับ 1–5 และข้ามจำนวนเต็มผิดช่วง รายงานทั้งจำนวนที่ยอมรับกับความถี่ จึงแยก “อ่านหกครั้ง” ออกจาก “ได้ข้อมูลถูกหกค่า”

C
/* rating_frequency.c : validate before converting to an index */
#include <stdio.h>
 
int main(void)
{
    int frequency[5] = {0};
    int accepted = 0;
    for (int attempt = 0; attempt < 6; ++attempt) {
        int rating;
        if (scanf("%d", &rating) != 1) {
            puts("Input error");
            return 1;
        }
        if (rating < 1 || rating > 5) {
            puts("Skipped");
            continue;
        }
        ++frequency[rating - 1];
        ++accepted;
    }
    printf("accepted=%d\n", accepted);
    for (int i = 0; i < 5; ++i) {
        printf("rating=%d count=%d\n", i + 1, frequency[i]);
    }
    return 0;
}
ข้อมูลหกครั้งจำนวนที่คาดว่ายอมรับความถี่ระดับ 1–5 ที่ทำนายผลจริง
5 4 5 3 4 5
1 1 1 1 1 1
0 1 2 3 4 6
0 6 0 6 0 6

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

7.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: อ่านสมาชิกเกินขอบเขตอาร์เรย์

ประเภท: การละเมิดที่ sanitizer ตรวจพบขณะรัน

อาร์เรย์สามสมาชิกมีดัชนี 0, 1, 2 แต่ลูปผิดอ่าน scores[3] ด้วย ซึ่งเป็น undefined behavior ไม่รับรองว่าจะได้เลขขยะหรือ crash ทุกครั้ง ตัวอย่างผิดรันเฉพาะสำเนาที่ติดตั้ง UndefinedBehaviorSanitizer และหยุดเมื่อพบปัญหา

การตรวจเพิ่มเติม: โค้ดผิดรันเฉพาะสำเนาที่สร้างด้วย -fsanitize=undefined -fno-sanitize-recover=all จึงหยุดเมื่อพบการละเมิด ข้อความ runtime error ด้านล่างเป็นของ sanitizer ไม่ใช่ error จาก compiler และการไม่มีข้อความจาก build ธรรมดาไม่ยืนยันว่าเข้าถึงหน่วยความจำถูกต้อง (GCC: Instrumentation Options)

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

C
#include <stdio.h>
int main(void)
{
    int scores[3] = {70, 80, 90};
    for (int i = 0; i <= 3; ++i) {
        printf("%d\n", scores[i]);
    }
    return 0;
}

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

TEXT
gcc -std=c11 -O0 -Wall -Wextra -Wpedantic -Wformat=2 -fdiagnostics-color=never -fmessage-length=0 -fsanitize=undefined -fno-sanitize-recover=all bad.c -o bad

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

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

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

stdout ว่างในการทดลองนี้ หากโปรแกรมหยุดก่อน flush ข้อมูล อาจไม่เห็นข้อความที่คาดว่าจะพิมพ์ก่อนหน้า

stderr:

TEXT
bad.c:6:30: runtime error: index 3 out of bounds for type 'int [3]'

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

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

runtime error: index ... out of bounds ... ชี้การเข้าถึงที่เกิดขณะรัน ชนิด int [3] บอกความจุที่ตรวจพบ ต้องเทียบ index กับช่วงที่ถูกต้อง ข้อความนี้มาจาก sanitizer ที่ผู้เขียนเปิด ไม่ใช่สิ่งที่ C รับประกันว่าจะตรวจให้อัตโนมัติทุกแพลตฟอร์ม

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

C
#include <stdio.h>
int main(void)
{
    int scores[3] = {70, 80, 90};
    for (int i = 0; i < 3; ++i) {
        printf("%d\n", scores[i]);
    }
    return 0;
}

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

ใช้เงื่อนไข i < 3 เพื่อไม่สร้างดัชนีเกินขอบเขต แล้วรันฉบับแก้พร้อม sanitizer อีกครั้งเพื่อดูว่าสามค่าครบและไม่มี runtime diagnostic หากฟังก์ชันรับอาร์เรย์ต้องส่งจำนวนข้อมูลที่ถูกต้องด้วย

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

TEXT
70
80
90

ตัวอย่างที่ผิด 2: กำหนดค่าให้อาร์เรย์ทั้งก้อนด้วย assignment

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

scores เป็นชื่ออาร์เรย์ ไม่ใช่ตัวแปร int สมาชิกเดียว จึงกำหนด scores = 0 เพื่อทำให้ทุกสมาชิกเป็นศูนย์ไม่ได้ ต้องแยก array initialization ตอนประกาศจาก assignment หลังประกาศ

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

C
#include <stdio.h>
int main(void)
{
    int scores[3] = {70, 80, 90};
    scores = 0;
    printf("%d\n", scores[0]);
    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: assignment to expression with array type
    5 |     scores = 0;
      |            ^

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

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

assignment to expression with array type บอกว่าด้านซ้ายเป็นอาร์เรย์ซึ่งใช้ assignment แบบนั้นไม่ได้ ไม่ได้หมายความว่าศูนย์มีค่ามากเกินขอบเขต หรือว่าความจุอาร์เรย์เป็นศูนย์

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

C
#include <stdio.h>
int main(void)
{
    int scores[3] = {70, 80, 90};
    for (int i = 0; i < 3; ++i) {
        scores[i] = 0;
    }
    printf("%d\n", scores[0]);
    return 0;
}

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

กำหนดค่าให้สมาชิกแต่ละตัวด้วยลูปตามความจุ การเขียน scores[0] = 0 อย่างเดียวแก้ได้เฉพาะสมาชิกแรก จึงต้องตรวจสมาชิกอื่นด้วยเมื่อข้อกำหนดคือเคลียร์ทั้งอาร์เรย์

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

TEXT
0

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

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

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

ปฏิบัติการที่ 7 อาร์เรย์

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

ℹ️Note

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

Lab A: อาร์เรย์คะแนน ผลรวมและค่าเฉลี่ย

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

เป้าหมาย ฝึก LLO7.1, LLO7.2 และ LLO7.3 คือประกาศและเข้าถึงอาร์เรย์ วนหาผลรวม/ค่าเฉลี่ยโดยไม่เกินขอบเขต และส่งอาร์เรย์เข้าฟังก์ชัน

ขั้นปฏิบัติ

  1. สร้างไฟล์ชื่อ ch7_array.c ด้วยโปรแกรมแก้ไขข้อความหรือ IDE
  2. พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
C
/* ch7_array.c : อาร์เรย์หนึ่งมิติ ผลรวม และการส่งอาร์เรย์เข้าฟังก์ชัน */
#include <stdio.h>
 
int sum_array(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum;
}
 
int main(void) {
    int scores[5] = {80, 75, 90, 65, 100};
 
    printf("สมาชิกอาร์เรย์: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", scores[i]);
    }
    printf("\n");
 
    int total = sum_array(scores, 5);
    printf("ผลรวม = %d, ค่าเฉลี่ย = %.2f\n", total, (double)total / 5);
    return 0;
}
  1. คอมไพล์และรันด้วยคำสั่ง
BASH
gcc -std=c11 -Wall ch7_array.c -o ch7_array
./ch7_array

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

TEXT
สมาชิกอาร์เรย์: 80 75 90 65 100
ผลรวม = 410, ค่าเฉลี่ย = 82.00

ลูปใน main วนตั้งแต่ดัชนี 0 ถึง 4 ด้วยเงื่อนไข i < 5 จึงแสดงสมาชิกครบทั้งห้าตัวโดยไม่เกินขอบเขต ฟังก์ชัน sum_array รับอาร์เรย์และขนาดไปด้วย แล้ววนบวกสะสมด้วย sum += arr[i]; ส่วนค่าเฉลี่ยคำนวณด้วย (double)total / 5 เพื่อให้ได้ทศนิยม หากหารจำนวนเต็มก่อนจะเสียส่วนทศนิยม ส่วนการใช้ %d กับอาร์กิวเมนต์ double ไม่ใช่การปัดเศษที่ถูกต้อง แต่เป็นพฤติกรรมไม่กำหนดเพราะรูปแบบไม่ตรงชนิด

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

ผลลัพธ์ที่คาดหวัง A.1 โปรแกรมคอมไพล์ผ่านไม่มีการแจ้งเตือน และแสดงคะแนนสูงสุดได้ถูกต้อง เช่น จากอาร์เรย์ {80, 75, 90, 65, 100} ควรได้คะแนนสูงสุดเท่ากับ 100 (ผลที่คาดว่าจะเห็นสำหรับโปรแกรมที่ผู้เรียนออกแบบเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับการออกแบบของแต่ละคน)

จุดตรวจ A แสดงสมาชิกครบทุกตัว ผลรวมและค่าเฉลี่ยถูกต้อง ดัชนีอยู่ในช่วง 0 ถึง n-1 ไม่เกินขอบเขต ส่งอาร์เรย์เข้าฟังก์ชันพร้อมขนาด และโค้ดมีการจัดย่อหน้าและคอมเมนต์ส่วนหัว

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

อาการสาเหตุวิธีแก้
โปรแกรมแสดงค่าแปลกหรือพังวนเกินขอบเขตด้วยเงื่อนไข i <= 5 จึงเข้าถึง scores[5]ใช้เงื่อนไข i < 5 ให้ตัวนับน้อยกว่าขนาดอาร์เรย์
ฟังก์ชันคำนวณผลรวมผิดลืมส่งขนาดอาร์เรย์เข้าฟังก์ชันหรือส่งขนาดผิดส่งขนาดที่ถูกต้องเป็นอาร์กิวเมนต์ที่สองเสมอ
ค่าเฉลี่ยไม่มีส่วนทศนิยมที่ต้องการหารจำนวนเต็มก่อนแปลงชนิดใช้ (double)total / size และแสดงด้วย %.2f
รูปแบบแสดงผลไม่ตรงชนิดใช้ %d กับค่า doubleใช้ %.2f ให้ตรงชนิด ไม่อาศัยผลของรูปแบบผิด

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

ใบงานตรวจ Lab A: ข้อมูลที่จับข้อผิดพลาดได้

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

ชุดข้อมูลห้าค่าผลรวมที่ทำนายค่าเฉลี่ยที่ทำนายค่าสูงสุดที่ทำนาย
80 75 90 65 100
0 0 0 0 0
1 1 1 1 2
-8 -3 -10 -5 -7

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

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

Lab B: อาร์เรย์สองมิติ

สถานการณ์ ต้องการเก็บตารางตัวเลข 2 แถว 3 หลักไว้ในอาร์เรย์สองมิติ แล้วแสดงออกเป็นตารางด้วยลูปซ้อน เพื่อฝึกการประกาศและเข้าถึงอาร์เรย์สองมิติ

เป้าหมาย ฝึก LLO7.3 คือใช้อาร์เรย์สองมิติและเข้าถึงสมาชิกด้วยสองดัชนีผ่านลูปซ้อน

ขั้นปฏิบัติ

  1. สร้างไฟล์ชื่อ ch7_matrix.c ด้วยโปรแกรมแก้ไขข้อความหรือ IDE
  2. พิมพ์ซอร์สโค้ดต่อไปนี้ โดยจัดย่อหน้า 4 ช่องว่างและใส่คอมเมนต์ส่วนหัว
C
/* ch7_matrix.c : อาร์เรย์สองมิติ */
#include <stdio.h>
 
int main(void) {
    int table[2][3] = { {1, 2, 3}, {4, 5, 6} };
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 3; c++) {
            printf("%d ", table[r][c]);
        }
        printf("\n");
    }
    return 0;
}
  1. คอมไพล์และรันด้วยคำสั่ง
BASH
gcc -std=c11 -Wall ch7_matrix.c -o ch7_matrix
./ch7_matrix

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

TEXT
1 2 3
4 5 6

ลูปนอกวนตามแถวด้วยตัวนับ r ตั้งแต่ 0 ถึง 1 และลูปในวนตามหลักด้วยตัวนับ c ตั้งแต่ 0 ถึง 2 การเข้าถึงสมาชิกใช้สองดัชนี table[r][c] เมื่อจบแต่ละแถวจึงขึ้นบรรทัดใหม่ด้วย printf("\n"); ลำดับการวนแบบนี้แสดงตารางตามแถวจากบนลงล่างและจากซ้ายไปขวาซึ่งตรงกับการจัดเก็บแบบเรียงตามแถว

ตัดสินใจเอง B.1 ดัดแปลงโปรแกรมให้คำนวณและแสดงผลรวมของแต่ละแถว โดยในลูปนอกตั้งตัวแปรสะสมของแถวเริ่มที่ 0 แล้วบวกสมาชิกในลูปในด้วยตัวแปรสะสมจากบทที่ 5 เมื่อจบลูปในให้แสดงผลรวมของแถวนั้น พร้อมตั้งชื่อตัวแปรให้สื่อความหมาย

ผลลัพธ์ที่คาดหวัง B.1 โปรแกรมคอมไพล์ผ่านไม่มีการแจ้งเตือน และแสดงผลรวมของแต่ละแถวได้ถูกต้อง เช่น แถวแรกรวมได้ 6 และแถวที่สองรวมได้ 15 (ผลที่คาดว่าจะเห็นสำหรับโปรแกรมที่ผู้เรียนออกแบบเอง ยังไม่ได้ทดสอบในเอกสารนี้ เพราะขึ้นกับการออกแบบของแต่ละคน)

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

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

อาการสาเหตุวิธีแก้
ตารางแสดงสลับแถวกับหลักสลับลำดับดัชนีเป็น table[c][r] หรือวนลูปสลับกันให้ลูปนอกวนแถว ลูปในวนหลัก และเข้าถึงด้วย table[r][c]
โปรแกรมแสดงค่าแปลกหรือพังลูปซ้อนวนเกินขอบเขต เช่น r <= 2 หรือ c <= 3ตั้งเงื่อนไขให้ตัวนับน้อยกว่าจำนวนแถว/หลัก คือ r < 2 และ c < 3
ทุกค่าพิมพ์ต่อกันในบรรทัดเดียวลืมขึ้นบรรทัดใหม่เมื่อจบแต่ละแถวใส่ printf("\n"); ท้ายลูปนอกหลังจบลูปใน

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

ใบงานตรวจ Lab B: พิกัดครบก่อนรวมแถว

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

การทดลองสิ่งที่ต้องตรวจ
ตารางเดิม 1–6ครบสองแถว แถวละสามค่า
ทุกช่องเป็น 0ยังคงจำนวนช่องและรูปตาราง
แถวหนึ่งเป็น 1 อีกแถวเป็น 10ผลรวมรายแถวไม่สะสมข้ามแถว
งานท้ายบท 3 แถว 2 หลักจำนวนพิกัดเท่าเดิมแต่แบ่งแถวต่างกัน

งาน B.1 ให้ตั้งผลรวมแถวใหม่ที่ต้นแต่ละรอบนอก ผลรวมแถวแรกกับแถวสองต้องตรวจแยกกัน การได้ผลรวมทั้งตารางถูกไม่ได้ยืนยันว่าผลรวมแต่ละแถวถูก

เก็บกวาด

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

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

ตอบด้วยช่วงดัชนี สถานะตัวแปร หรือเส้นทางการทำงานของโปรแกรมที่ทดลองแล้ว

  1. ความจุ 10 แต่ count เป็น 5 มีดัชนีข้อมูลจริงใดบ้าง และจะเก็บค่าถัดไปที่ใด
  2. เมื่อนับได้ศูนย์รายการ เหตุใดจึงไม่อ่านสมาชิกแรกหรือหารด้วย count
  3. ทำไมค่า 0 ที่ find_first คืนจึงไม่ควรถูกตีความว่าไม่พบ
  4. การแก้ values[0] กับ count ใน array_update ส่งผลต่างกันอย่างไร
  5. เหตุใดการนับความถี่ต้องตรวจระดับก่อนคำนวณ rating - 1
  6. ผลรวมรายแถวและผลรวมรายคอลัมน์เกี่ยวข้องกันอย่างไร

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

สรุป

บทนี้นำการวนซ้ำจากบทที่ 5 และฟังก์ชันจากบทที่ 6 มาใช้กับข้อมูลชุดที่เก็บในอาร์เรย์ เริ่มจากอาร์เรย์หนึ่งมิติที่เก็บข้อมูลชนิดเดียวกันหลายค่าในชื่อเดียว ประกาศด้วย int scores[5]; กำหนดค่าเริ่มต้นด้วยวงเล็บปีกกา และเข้าถึงสมาชิกด้วยดัชนีที่เริ่มจาก 0 โดยอาร์เรย์ขนาด n มีดัชนี 0 ถึง n-1 ต่อด้วยการประมวลผลอาร์เรย์ด้วยลูป for เพื่อหาผลรวมและค่าเฉลี่ย พร้อมระวังความคลาดเคลื่อนที่ขอบเขตด้วยการตั้งเงื่อนไข i < size จากนั้นอธิบายการส่งอาร์เรย์เข้าฟังก์ชันด้วย int sum_array(int arr[], int size) ที่ต้องส่งขนาดไปด้วย และฟังก์ชันทำงานกับอาร์เรย์เดิม ต่างจากการส่งตัวแปรเดี่ยวแบบส่งสำเนาค่าในบทที่ 6 สุดท้ายคืออาร์เรย์สองมิติ int table[2][3] ที่เข้าถึงด้วยสองดัชนี table[r][c] ผ่านลูปซ้อน ทักษะเหล่านี้เป็นพื้นฐานของการจัดการข้อมูลเป็นชุดในบทถัดไป

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

สิ่งที่ส่งต่อค่า/รูปแบบใช้ในบท
อาร์เรย์หนึ่งมิติและดัชนีเริ่ม 0int a[n];, a[i], ดัชนี 0 ถึง n-1บท 8 (สตริง), บท 9 (struct), บท 10 (เรียง/ค้นหา)
การวนประมวลผลอาร์เรย์ด้วยลูปfor (int i = 0; i < size; i++), sum += a[i];บท 10 (เรียง/ค้นหา) เป็นต้นไป
การส่งอาร์เรย์เข้าฟังก์ชันint f(int arr[], int size) ส่งขนาดไปด้วยบท 10 เป็นต้นไป
อาร์เรย์สองมิติและลูปซ้อนint t[r][c], ลูปนอกวนแถว ลูปในวนหลักบท 9 (struct), บท 10 (เรียง/ค้นหา)

สรุปคำสั่ง

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

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

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

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

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

  • ก1 (LLO7.1) จงอธิบายว่าอาร์เรย์ขนาด n มีดัชนีที่ใช้ได้ตั้งแต่เท่าใดถึงเท่าใด และเหตุใดการเข้าถึงดัชนีที่เกินช่วงนี้จึงเป็นข้อผิดพลาด
  • ก2 (LLO7.3) จงอธิบายว่าเหตุใดการส่งอาร์เรย์เข้าฟังก์ชันจึงต้องส่งขนาดของอาร์เรย์ไปพร้อมกันเสมอ

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

  • ข1 (LLO7.2) จงเขียนโปรแกรมภาษา C ที่ประกาศอาร์เรย์จำนวนเต็ม 6 ตัวพร้อมกำหนดค่าเริ่มต้น แล้วใช้ลูปหาผลรวมและค่าเฉลี่ยโดยไม่เกินขอบเขตดัชนี แสดงค่าเฉลี่ยเป็นทศนิยมสองตำแหน่ง แล้วคอมไพล์และรันให้ได้ผลจริง
  • ข2 (LLO7.3) จงเขียนโปรแกรมที่ประกาศอาร์เรย์สองมิติขนาด 3 แถว 2 หลักพร้อมกำหนดค่าเริ่มต้น แล้วใช้ลูปซ้อนแสดงสมาชิกทุกตัวเป็นตาราง

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

  • ค1 (LLO7.2) จากโปรแกรมที่วนประมวลผลอาร์เรย์ขนาด 5 ด้วยเงื่อนไขลูป i <= 5 จงวิเคราะห์ว่าเกิดปัญหาใดได้บ้าง และเสนอวิธีแก้เงื่อนไขให้ถูกต้อง

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

  1. ศึกษาอาร์เรย์และการเข้าถึงด้วยดัชนีเพิ่มเติมจาก Kernighan และ Ritchie (1988) บทที่ 1 และบทที่ 5 ว่าด้วยอาร์เรย์ แล้วทดลองเขียนโปรแกรมที่ค้นหาสมาชิกที่มีค่ามากที่สุดในอาร์เรย์
  2. ทบทวนข้อกำหนดเรื่องอาร์เรย์และขอบเขตดัชนีในมาตรฐานภาษา C ตาม ISO/IEC 9899 เพื่อเข้าใจว่าเหตุใดภาษา C จึงไม่ตรวจขอบเขตดัชนีให้โดยอัตโนมัติ
  3. ทดลองออกแบบโปรแกรมอาร์เรย์สองมิติที่คำนวณผลรวมของแต่ละแถวและแต่ละหลัก แล้วเปรียบเทียบลำดับการวนลูปซ้อนที่เหมาะสมกับการจัดเก็บแบบเรียงตามแถว

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

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

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

  • GNU C: Accessing Array Elements — ดัชนีเริ่มศูนย์และความรับผิดชอบต่อขอบเขต
  • GNU C: Initializers — ตัวกำหนดค่าเริ่มต้นและสมาชิกส่วนที่เหลือเป็นศูนย์
  • GNU C: Declaring an Array — จำนวนสมาชิกและชนิดของแต่ละช่อง
  • GNU C: Array parameters are pointers — การปรับชนิดพารามิเตอร์อาร์เรย์และพื้นที่สมาชิกเดิม
  • GNU C: Multidimensional Arrays — อาร์เรย์ของแถวและลำดับจัดเก็บ

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

By Watcharin Sarachai