การใช้อาร์เรย์สองมิติในภาษา C: คู่มือพื้นฐานพร้อมตัวอย่างโค้ด

目次

1. บทนำ

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

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

ความสำคัญของอาร์เรย์ในภาษา C

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

โดยเฉพาะ “อาร์เรย์สองมิติ” มีประโยชน์ในสถานการณ์ต่อไปนี้:

  • การคำนวณเมทริกซ์ทางคณิตศาสตร์
  • การจัดการกระดานเกม (เช่น หมากรุก หรือโอเทลโล)
  • การประมวลผลข้อมูลที่มีโครงสร้างแบบตารางหรือสเปรดชีต

สิ่งที่จะได้เรียนรู้จากบทความนี้

ในบทความนี้ ผู้อ่านจะได้เรียนรู้เนื้อหาต่อไปนี้แบบทีละขั้นตอน:

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

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

บทความนี้เหมาะกับใคร

บทความนี้เหมาะสำหรับทั้งผู้ที่เพิ่งเริ่มเรียนภาษา C และผู้ที่ต้องการขยายความสามารถในการเขียนโปรแกรม โดยเฉพาะผู้ที่:

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

2. อาร์เรย์สองมิติ คืออะไร

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

ในส่วนนี้ เราจะอธิบายแนวคิดพื้นฐานและโครงสร้างของอาร์เรย์สองมิติ

โครงสร้างพื้นฐานของอาร์เรย์สองมิติ

อาร์เรย์สองมิติประกอบด้วยหลายแถวและหลายคอลัมน์ โดยในความเป็นจริงแล้วแต่ละแถวคืออาร์เรย์หนึ่งมิติ และเมื่อนำมารวมกันจะกลายเป็นอาร์เรย์สองมิติ

ตัวอย่าง: ภาพรวมของอาร์เรย์สองมิติ

ข้อมูลจะถูกจัดเก็บโดยใช้แถวและคอลัมน์ ดังตัวอย่าง:

array[3][4] = {
  {1, 2, 3, 4},
  {5, 6, 7, 8},
  {9, 10, 11, 12}
};

ในกรณีนี้ array คืออาร์เรย์สองมิติที่มี 3 แถว 4 คอลัมน์ และสามารถเข้าถึงองค์ประกอบได้ด้วยการใช้ดัชนี

  • array[0][0] = “1”
  • array[2][3] = “12”

การใช้งานของอาร์เรย์สองมิติ

อาร์เรย์สองมิติสามารถนำมาใช้ในกรณีต่าง ๆ เช่น:

  1. การคำนวณเมทริกซ์
    เช่น การบวกและคูณเมทริกซ์
  2. การจัดการข้อมูลแบบตาราง
    เช่น สเปรดชีตหรือฐานข้อมูล
  3. การพัฒนาเกม
    เช่น จัดการกระดานหมากรุกหรือโอเทลโล
  4. การประมวลผลภาพ
    เช่น เก็บค่าพิกเซลของสีภาพ

ดังนั้นอาร์เรย์สองมิติถือเป็นโครงสร้างพื้นฐานที่สำคัญมากในการจัดการข้อมูลซับซ้อนอย่างมีประสิทธิภาพ

ความแตกต่างระหว่างอาร์เรย์หนึ่งมิติและสองมิติ

ลักษณะของอาร์เรย์หนึ่งมิติ

อาร์เรย์หนึ่งมิติจะเก็บข้อมูลในรูปแบบเชิงเส้น (linear) เพียงแกนเดียว

int array[5] = {1, 2, 3, 4, 5};

ในกรณีนี้สามารถเข้าถึงข้อมูลตามลำดับด้วยดัชนี

  • array[0] = “1”
  • array[4] = “5”

ลักษณะของอาร์เรย์สองมิติ

อาร์เรย์สองมิติจะเก็บข้อมูลโดยใช้ทั้ง “แถว” และ “คอลัมน์”

int array[2][3] = {
  {1, 2, 3},
  {4, 5, 6}
};

ในกรณีนี้จะเข้าถึงข้อมูลด้วยการระบุแถวและคอลัมน์

  • array[0][2] = “3”
  • array[1][0] = “4”

ดังนั้น อาร์เรย์สองมิติจึงมีประโยชน์มากเมื่อจัดการโครงสร้างข้อมูลที่ซับซ้อน

3. การประกาศและการกำหนดค่าเริ่มต้นของอาร์เรย์สองมิติ

ในภาษา C การใช้อาร์เรย์สองมิติต้องเริ่มจากการประกาศ และเมื่อจำเป็นสามารถกำหนดค่าเริ่มต้นได้ ส่วนนี้จะอธิบายวิธีการประกาศและการกำหนดค่าเริ่มต้นของอาร์เรย์สองมิติ

วิธีการประกาศอาร์เรย์สองมิติ

การประกาศอาร์เรย์สองมิติทำได้ดังนี้:

ชนิดข้อมูล ชื่ออาร์เรย์[จำนวนแถว][จำนวนคอลัมน์];
  • ชนิดข้อมูล: ชนิดของข้อมูลที่จะจัดเก็บในอาร์เรย์ (เช่น int, float, char)
  • จำนวนแถวและจำนวนคอลัมน์: ขนาดของอาร์เรย์ที่กำหนดเป็นจำนวนเต็ม

ตัวอย่างการประกาศ

ตัวอย่างด้านล่างนี้คือการประกาศอาร์เรย์สองมิติชนิด int ขนาด 3 แถว 4 คอลัมน์:

int array[3][4];

กรณีนี้ หน่วยความจำที่สามารถเก็บข้อมูล 3 แถว 4 คอลัมน์ จะถูกจองไว้

การกำหนดค่าเริ่มต้นของอาร์เรย์สองมิติ

เมื่อประกาศอาร์เรย์สองมิติ สามารถกำหนดค่าเริ่มต้นไปพร้อมกันได้ โดยมีหลายวิธีในการกำหนดค่าเริ่มต้น

วิธีที่ 1: กำหนดค่าเริ่มต้นทุกองค์ประกอบ

สามารถกำหนดค่าเริ่มต้นให้ทุกตำแหน่งได้ดังนี้:

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

ในกรณีนี้ array จะถูกจัดเก็บในหน่วยความจำดังนี้:

1  2  3
4  5  6
  • array[0][0] → 1
  • array[1][2] → 6

วิธีที่ 2: กำหนดค่าเริ่มต้นบางส่วน

สามารถกำหนดค่าเฉพาะบางตำแหน่งได้ ส่วนที่เหลือจะถูกกำหนดเป็น 0 อัตโนมัติ:

int array[2][3] = {
    {1, 2},
    {4}
};

อาร์เรย์นี้จะถูกจัดเก็บดังนี้:

1  2  0
4  0  0

วิธีที่ 3: ใช้ {0} เพื่อกำหนดค่าเริ่มต้นทั้งหมดเป็นศูนย์

หากต้องการให้ทุกตำแหน่งเริ่มต้นเป็น 0 สามารถใช้ {0} ได้:

int array[3][4] = {0};

ในกรณีนี้ทุกองค์ประกอบของอาร์เรย์จะถูกกำหนดค่าเป็น 0

ข้อควรระวังในการกำหนดค่าเริ่มต้น

  • จำนวนแถวไม่สามารถละเว้นได้
    ต้องระบุจำนวนแถว (มิติแรก) เสมอ
  int array[][4] = {
      {1, 2, 3, 4},
      {5, 6, 7, 8}
  };

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

  • จำนวนคอลัมน์ต้องระบุ
    สำหรับอาร์เรย์สองมิติ ต้องระบุอย่างน้อยจำนวนคอลัมน์ (มิติที่สอง)

โค้ดตัวอย่าง: การประกาศและกำหนดค่าเริ่มต้นอาร์เรย์

ตัวอย่างต่อไปนี้เป็นการประกาศอาร์เรย์สองมิติ กำหนดค่าเริ่มต้น และแสดงผลแต่ละองค์ประกอบ:

#include <stdio.h>

int main() {
    int array[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("array[%d][%d] = %d\n", i, j, array[i][j]);
        }
    }

    return 0;
}

ผลลัพธ์ที่ได้

array[0][0] = 1
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 6

4. วิธีการใช้อาร์เรย์สองมิติ: การเข้าถึงและแก้ไของค์ประกอบ

ในภาษา C สามารถเข้าถึงและแก้ไขค่าในอาร์เรย์สองมิติได้ ส่วนนี้จะอธิบายวิธีเข้าถึงและการใช้งานจริง

การเข้าถึงองค์ประกอบ

การเข้าถึงค่าในอาร์เรย์สองมิติต้องระบุหมายเลขแถวและคอลัมน์

วิธีการเข้าถึงพื้นฐาน

array[แถว][คอลัมน์]

ตัวอย่างเช่น มีอาร์เรย์ดังนี้:

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};
  • array[0][0] → 1 (แถวที่ 1 คอลัมน์ที่ 1)
  • array[1][2] → 6 (แถวที่ 2 คอลัมน์ที่ 3)

ตัวอย่าง: โปรแกรมที่แสดงผลองค์ประกอบ

#include <stdio.h>

int main() {
    int array[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    printf("array[0][0] = %d\n", array[0][0]);
    printf("array[1][2] = %d\n", array[1][2]);

    return 0;
}

ผลลัพธ์ที่ได้

array[0][0] = 1
array[1][2] = 6

การแก้ไของค์ประกอบ

สามารถเปลี่ยนค่าขององค์ประกอบในอาร์เรย์ได้โดยการกำหนดค่าใหม่

วิธีการเปลี่ยนค่า

array[แถว][คอลัมน์] = ค่าใหม่;

ตัวอย่าง: โปรแกรมที่เปลี่ยนค่า

#include <stdio.h>

int main() {
    int array[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    // เปลี่ยนค่า
    array[0][0] = 10;
    array[1][2] = 20;

    // แสดงผลลัพธ์
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("array[%d][%d] = %d\n", i, j, array[i][j]);
        }
    }

    return 0;
}

ผลลัพธ์ที่ได้

array[0][0] = 10
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 20

การทำงานซ้ำ (Loop) กับอาร์เรย์สองมิติ

ในการจัดการอาร์เรย์สองมิติ มักใช้ลูปซ้อนกัน (nested loop) เพื่อเข้าถึงหรือแก้ไขข้อมูลทั้งหมด

ตัวอย่าง: การวนลูปตามแถวและคอลัมน์

#include <stdio.h>

int main() {
    int array[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    // วนลูปเพื่อแสดงผลอาร์เรย์
    for (int i = 0; i < 2; i++) { // ลูปตามแถว
        for (int j = 0; j < 3; j++) { // ลูปตามคอลัมน์
            printf("array[%d][%d] = %d\n", i, j, array[i][j]);
        }
    }

    return 0;
}

ผลลัพธ์ที่ได้

array[0][0] = 1
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 6

ตัวอย่างการประยุกต์: กำหนดค่าทุกองค์ประกอบเป็นค่าเดียวกัน

สามารถใช้ลูปเพื่อกำหนดค่าทุกตำแหน่งของอาร์เรย์เป็นค่าที่ต้องการได้

ตัวอย่าง: กำหนดทุกองค์ประกอบเป็น 5

#include <stdio.h>

int main() {
    int array[3][3];

    // กำหนดค่าเป็น 5 ทุกตำแหน่ง
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            array[i][j] = 5;
        }
    }

    // แสดงผล
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("array[%d][%d] = %d\n", i, j, array[i][j]);
        }
    }

    return 0;
}

ผลลัพธ์ที่ได้

array[0][0] = 5
array[0][1] = 5
array[0][2] = 5
array[1][0] = 5
array[1][1] = 5
array[1][2] = 5
array[2][0] = 5
array[2][1] = 5
array[2][2] = 5

5. โครงสร้างของหน่วยความจำสำหรับอาร์เรย์สองมิติ

ในการเขียนภาษา C การเข้าใจว่าอาร์เรย์สองมิติถูกจัดเก็บในหน่วยความจำอย่างไรมีความสำคัญมาก ความรู้ตรงนี้จะช่วยให้เขียนโปรแกรมได้อย่างมีประสิทธิภาพมากขึ้น และสามารถใช้งานตัวชี้ (pointer) ได้อย่างคล่องแคล่ว

การจัดเก็บข้อมูลของอาร์เรย์สองมิติในหน่วยความจำ

อาร์เรย์สองมิติในภาษา C แท้จริงแล้วถูกจัดเก็บต่อเนื่องในหน่วยความจำแบบหนึ่งมิติ โดยใช้รูปแบบที่เรียกว่า row-major order หรือการจัดเรียงตามแถว

row-major คืออะไร

row-major คือวิธีจัดเก็บที่ข้อมูลในแต่ละแถวจะถูกจัดเก็บต่อเนื่องกันในหน่วยความจำ

ตัวอย่าง: การจัดเก็บในหน่วยความจำ

พิจารณาอาร์เรย์สองมิติด้านล่าง:

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

อาร์เรย์นี้จะถูกจัดเก็บในหน่วยความจำดังนี้:

1  2  3  4  5  6
  • array[0][0] → ตำแหน่งแรกของหน่วยความจำ
  • array[0][1] → ตำแหน่งที่สอง
  • array[1][0] → ตำแหน่งที่สี่

การอ้างอิงองค์ประกอบด้วยดัชนี

เมื่อใช้อาร์เรย์ในภาษา C การอ้างอิงองค์ประกอบด้วยดัชนีจะถูกคำนวณตามสูตรดังนี้:

array[i][j] = *(array + (i * จำนวนคอลัมน์) + j)

ตัวอย่าง: การคำนวณตำแหน่งหน่วยความจำ

สำหรับอาร์เรย์ array[2][3]:

  • array[1][2] สามารถคำนวณได้ดังนี้:
*(array + (1 * 3) + 2) = *(array + 5)

ซึ่งหมายความว่า เนื่องจากอาร์เรย์ถูกเก็บแบบ row-major จึงข้ามตำแหน่งของ 3 องค์ประกอบจากแถวแรก (i = 1) แล้วบวกเพิ่มด้วย 2 (j = 2) เพื่อเข้าถึงตำแหน่งที่ถูกต้อง

การใช้งานตัวชี้ (Pointer) กับอาร์เรย์สองมิติ

อาร์เรย์สองมิติในภาษา C สามารถเข้าถึงได้โดยใช้ตัวชี้ ทำให้มีความยืดหยุ่นมากขึ้น

ความสัมพันธ์ระหว่างตัวชี้และอาร์เรย์สองมิติ

อาร์เรย์สองมิติจริง ๆ แล้วคือ “อาร์เรย์ของอาร์เรย์” ดังนั้นสามารถใช้ตัวชี้เข้าถึงได้ เช่น:

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};
int *ptr = &array[0][0];

printf("%d\n", *(ptr + 4)); // แสดงผล: 5

ในกรณีนี้ ptr จะชี้ไปยังองค์ประกอบแรกของอาร์เรย์ และสามารถเข้าถึงองค์ประกอบอื่น ๆ ได้ด้วยการบวก offset

การอธิบายการจัดเก็บในหน่วยความจำแบบภาพ

ตัวอย่างการจัดเก็บข้อมูลของ array[2][3] ในหน่วยความจำ:

หน่วยความจำ: 1  2  3  4  5  6
ดัชนี:
  [0][0] [0][1] [0][2] [1][0] [1][1] [1][2]

จะเห็นได้ว่าอาร์เรย์สองมิติถูกจัดเก็บต่อเนื่องกันในหน่วยความจำเสมือนเป็นอาร์เรย์หนึ่งมิติ

ข้อควรระวังเพื่อการทำงานที่มีประสิทธิภาพ

เพื่อใช้งานอาร์เรย์สองมิติอย่างมีประสิทธิภาพ ควรใส่ใจประเด็นต่อไปนี้:

  1. เข้าถึงข้อมูลแบบ row-major
    เวลาวนลูปเข้าถึงข้อมูล ควรยึดตามแถวเป็นหลักแล้วค่อยวนคอลัมน์ จะทำให้ทำงานได้รวดเร็วกว่า
for (int i = 0; i < จำนวนแถว; i++) {
    for (int j = 0; j < จำนวนคอลัมน์; j++) {
        // เข้าถึงแบบ row-major
    }
}

การเข้าถึงแบบนี้จะช่วยให้ใช้ cache ของหน่วยความจำได้อย่างมีประสิทธิภาพ

  1. ใช้ pointer อย่างเหมาะสม
    การใช้ pointer สามารถลดการคำนวณของดัชนี และเพิ่มประสิทธิภาพของโปรแกรม

6. ตัวอย่างการใช้งานจริง: การคำนวณเมทริกซ์และการสร้างกระดานเกม

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

ตัวอย่างการคำนวณเมทริกซ์

การคำนวณเมทริกซ์เป็นสิ่งที่ใช้บ่อยทั้งในคณิตศาสตร์และวิศวกรรม โดยการใช้อาร์เรย์สองมิติสามารถทำการบวกและคูณเมทริกซ์ได้อย่างง่ายดาย

ตัวอย่างที่ 1: การบวกเมทริกซ์

โปรแกรมตัวอย่างด้านล่างเป็นการบวกเมทริกซ์:

#include <stdio.h>

int main() {
    // เมทริกซ์ 3x3 จำนวน 2 ชุด
    int matrix1[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int matrix2[3][3] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };
    int result[3][3];

    // การบวกเมทริกซ์
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            result[i][j] = matrix1[i][j] + matrix2[i][j];
        }
    }

    // แสดงผลลัพธ์
    printf("ผลลัพธ์การบวกเมทริกซ์:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

ผลลัพธ์ที่ได้

ผลลัพธ์การบวกเมทริกซ์:
10 10 10
10 10 10
10 10 10

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

ตัวอย่างที่ 2: การคูณเมทริกซ์

โปรแกรมด้านล่างแสดงการคูณเมทริกซ์:

#include <stdio.h>

int main() {
    // เมทริกซ์ 3x3 จำนวน 2 ชุด
    int matrix1[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int matrix2[3][3] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };
    int result[3][3] = {0};

    // การคูณเมทริกซ์
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 3; k++) {
                result[i][j] += matrix1[i][k] * matrix2[k][j];
            }
        }
    }

    // แสดงผลลัพธ์
    printf("ผลลัพธ์การคูณเมทริกซ์:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

ผลลัพธ์ที่ได้

ผลลัพธ์การคูณเมทริกซ์:
30 24 18
84 69 54
138 114 90

ตัวอย่างการสร้างกระดานเกม

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

ตัวอย่าง: การกำหนดค่าเริ่มต้นและการแสดงผลกระดานโอเทลโล

#include <stdio.h>

int main() {
    // กระดานโอเทลโลขนาด 8x8
    int board[8][8] = {0};

    // กำหนดค่าตำแหน่งเริ่มต้น
    board[3][3] = 1; // ขาว
    board[3][4] = 2; // ดำ
    board[4][3] = 2; // ดำ
    board[4][4] = 1; // ขาว

    // แสดงผลกระดาน
    printf("สถานะเริ่มต้นของกระดานโอเทลโล:\n");
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            printf("%d ", board[i][j]);
        }
        printf("\n");
    }

    return 0;
}

ผลลัพธ์ที่ได้

สถานะเริ่มต้นของกระดานโอเทลโล:
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 1 2 0 0 0
0 0 0 2 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

ในโปรแกรมนี้ 0 แทนค่าว่าง 1 แทนหมากขาว และ 2 แทนหมากดำ

7. ความสัมพันธ์ระหว่างอาร์เรย์สองมิติและตัวชี้ (Pointer)

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

ความสัมพันธ์พื้นฐานระหว่างอาร์เรย์สองมิติและตัวชี้

อาร์เรย์สองมิติจริง ๆ แล้วคือ “อาร์เรย์ของอาร์เรย์” ดังนั้นแถวแต่ละแถวสามารถมองเป็นตัวชี้ได้

ตัวอย่าง: โครงสร้างพื้นฐานของอาร์เรย์สองมิติ

การประกาศอาร์เรย์สองมิติ:

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

การจัดเก็บในหน่วยความจำ:

[1] [2] [3] [4] [5] [6]
  • array ชี้ไปยังตำแหน่งแรกของอาร์เรย์
  • array[i] ชี้ไปยังแถวที่ i
  • array[i][j] คือค่าในตำแหน่งนั้น

การอ้างอิงองค์ประกอบด้วยตัวชี้

สามารถใช้การคำนวณด้วย pointer เพื่อเข้าถึงค่าได้ดังนี้:

*(array[0] + 1)   // เท่ากับ array[0][1]
*(*(array + 1) + 2) // เท่ากับ array[1][2]

การใช้อาร์เรย์สองมิติเป็นอาร์กิวเมนต์ของฟังก์ชัน

เมื่อส่งอาร์เรย์สองมิติให้กับฟังก์ชัน มักจะใช้ pointer เพื่อจัดการ เช่น:

ตัวอย่าง: การจัดการอาร์เรย์สองมิติภายในฟังก์ชัน

#include <stdio.h>

// ฟังก์ชันสำหรับพิมพ์อาร์เรย์
void printArray(int (*array)[3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int array[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    // ส่งอาร์เรย์ไปยังฟังก์ชัน
    printArray(array, 2);

    return 0;
}

ผลลัพธ์ที่ได้

1 2 3
4 5 6

จุดสำคัญ

  • int (*array)[3] หมายถึง pointer ที่ชี้ไปยังอาร์เรย์ที่มี 3 คอลัมน์
  • ภายในฟังก์ชันสามารถเข้าถึงข้อมูลด้วยแถวและคอลัมน์ได้

การใช้งานอาร์เรย์สองมิติแบบไดนามิกด้วยตัวชี้

โดยใช้ตัวชี้ (pointer) สามารถสร้างอาร์เรย์สองมิติแบบไดนามิกได้ ทำให้จัดการขนาดของอาร์เรย์ได้ยืดหยุ่นมากขึ้น

ตัวอย่าง: การสร้างอาร์เรย์สองมิติแบบไดนามิก

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 2, cols = 3;

    // จองหน่วยความจำแบบไดนามิก
    int** array = malloc(rows * sizeof(int*));
    for (int i = 0; i < rows; i++) {
        array[i] = malloc(cols * sizeof(int));
    }

    // กำหนดค่า
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            array[i][j] = i * cols + j + 1;
        }
    }

    // แสดงผล
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }

    // คืนหน่วยความจำ
    for (int i = 0; i < rows; i++) {
        free(array[i]);
    }
    free(array);

    return 0;
}

ผลลัพธ์ที่ได้

1 2 3
4 5 6

8. วิธีการจองและคืนหน่วยความจำสำหรับอาร์เรย์สองมิติ

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

พื้นฐานของการจัดการหน่วยความจำแบบไดนามิก

การจัดการหน่วยความจำแบบไดนามิกทำได้โดยใช้ฟังก์ชัน malloc หรือ calloc ซึ่งสามารถกำหนดขนาดของอาร์เรย์ได้ในระหว่างที่โปรแกรมทำงาน

วิธีการสร้างอาร์เรย์สองมิติแบบไดนามิก

มีสองวิธีหลัก ๆ สำหรับสร้างอาร์เรย์สองมิติแบบไดนามิก:

  1. ใช้ตัวชี้ของแถว (array of pointers)
  2. ใช้การจองหน่วยความจำแบบอาร์เรย์หนึ่งมิติแล้วเข้าถึงแบบสองมิติ

วิธีที่ 1: ใช้อาร์เรย์ของตัวชี้

วิธีนี้คือการจองหน่วยความจำสำหรับแต่ละแถวแยกกัน

ขั้นตอน
  1. จองหน่วยความจำสำหรับตัวชี้ตามจำนวนแถว
  2. จองหน่วยความจำให้แต่ละแถวตามจำนวนคอลัมน์
ตัวอย่าง: การจองและใช้อาร์เรย์สองมิติแบบไดนามิก
#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 4;

    // จองตัวชี้สำหรับแต่ละแถว
    int** array = malloc(rows * sizeof(int*));

    // จองหน่วยความจำสำหรับแต่ละแถว
    for (int i = 0; i < rows; i++) {
        array[i] = malloc(cols * sizeof(int));
    }

    // กำหนดค่า
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            array[i][j] = i * cols + j + 1;
        }
    }

    // แสดงผล
    printf("อาร์เรย์สองมิติที่จองแบบไดนามิก:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }

    // คืนหน่วยความจำ
    for (int i = 0; i < rows; i++) {
        free(array[i]);
    }
    free(array);

    return 0;
}

ผลลัพธ์ที่ได้

อาร์เรย์สองมิติที่จองแบบไดนามิก:
1 2 3 4
5 6 7 8
9 10 11 12

วิธีที่ 2: ใช้อาร์เรย์หนึ่งมิติแล้วเข้าถึงแบบสองมิติ

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

ขั้นตอน
  1. จองหน่วยความจำสำหรับ (จำนวนแถว × จำนวนคอลัมน์)
  2. คำนวณตำแหน่งด้วยสูตร i * cols + j
ตัวอย่าง: การสร้างอาร์เรย์สองมิติจากอาร์เรย์หนึ่งมิติ
#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 4;

    // จองหน่วยความจำทั้งหมด
    int* array = malloc(rows * cols * sizeof(int));

    // กำหนดค่า
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            array[i * cols + j] = i * cols + j + 1;
        }
    }

    // แสดงผล
    printf("อาร์เรย์สองมิติที่สร้างจากอาร์เรย์หนึ่งมิติ:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", array[i * cols + j]);
        }
        printf("\n");
    }

    // คืนหน่วยความจำ
    free(array);

    return 0;
}

ผลลัพธ์ที่ได้

อาร์เรย์สองมิติที่สร้างจากอาร์เรย์หนึ่งมิติ:
1 2 3 4
5 6 7 8
9 10 11 12

ข้อควรระวังในการจัดการหน่วยความจำแบบไดนามิก

  1. หลีกเลี่ยง memory leak
    หากไม่คืนหน่วยความจำหลังใช้งาน จะทำให้เกิด memory leak ต้องใช้ free ทุกครั้ง
  2. ตรวจสอบข้อผิดพลาดในการจองหน่วยความจำ
    เมื่อใช้ malloc หรือ calloc ต้องตรวจสอบว่าได้หน่วยความจำสำเร็จหรือไม่ หากไม่สำเร็จจะคืนค่า NULL
if (array == NULL) {
    printf("การจองหน่วยความจำล้มเหลว\n");
    return 1;
}
  1. คำนวณขนาดหน่วยความจำให้ถูกต้อง
    ต้องคำนวณขนาดที่ต้องการอย่างแม่นยำก่อนจองหน่วยความจำ

9. ข้อควรระวังในการใช้อาร์เรย์สองมิติ

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

การป้องกันการเข้าถึงนอกขอบเขต (Out of bounds)

หากเข้าถึงอาร์เรย์นอกขอบเขต จะทำให้โปรแกรมทำงานผิดพลาดหรือเกิดการล่มได้

ตัวอย่าง: การเข้าถึงนอกขอบเขต

#include <stdio.h>

int main() {
    int array[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    // การเข้าถึงนอกขอบเขต (ผิดพลาด)
    printf("%d\n", array[2][0]);  // ไม่มีแถวที่ 3

    return 0;
}

ในโค้ดนี้ array[2][0] พยายามเข้าถึงแถวที่ไม่มีอยู่จริง ส่งผลให้เกิด undefined behavior

วิธีป้องกัน

  1. เขียนเงื่อนไขลูปไม่ให้เกินจำนวนแถวและคอลัมน์
  2. เพิ่มการตรวจสอบขอบเขตก่อนเข้าถึง
โค้ดที่แก้ไขแล้ว
for (int i = 0; i < 2; i++) { // ใช้จำนวนแถวจริง
    for (int j = 0; j < 3; j++) {
        printf("%d ", array[i][j]);
    }
}

การป้องกัน memory leak

ถ้าไม่คืนหน่วยความจำที่จองแบบไดนามิก จะทำให้เกิด memory leak

ตัวอย่าง: memory leak

#include <stdlib.h>

int main() {
    int rows = 2, cols = 3;

    int** array = malloc(rows * sizeof(int*));
    for (int i = 0; i < rows; i++) {
        array[i] = malloc(cols * sizeof(int));
    }

    // คืนหน่วยความจำไม่ครบ
    free(array);

    return 0;
}

โค้ดนี้ไม่คืนหน่วยความจำของแต่ละแถว ทำให้เกิด memory leak

วิธีป้องกัน

ต้องคืนหน่วยความจำทุกครั้งตามลำดับ:

  1. คืนหน่วยความจำแต่ละแถว
  2. คืนหน่วยความจำตัวชี้หลัก
วิธีที่ถูกต้อง
for (int i = 0; i < rows; i++) {
    free(array[i]);
}
free(array);

ข้อจำกัดของการเปลี่ยนขนาด

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

วิธีแก้

  1. ใช้การจัดการหน่วยความจำแบบไดนามิก
  2. หากต้องการเปลี่ยนขนาด ใช้ realloc

การลืมกำหนดค่าเริ่มต้น

หากไม่กำหนดค่าเริ่มต้นให้ตัวแปรอาร์เรย์ ค่าในหน่วยความจำอาจเป็น “ค่าขยะ”

ตัวอย่าง: ลืมกำหนดค่าเริ่มต้น

int array[2][3];
printf("%d\n", array[0][0]); // อาจได้ค่าที่ไม่คาดคิด

วิธีป้องกัน

  1. กำหนดค่าเริ่มต้นตอนประกาศ
int array[2][3] = {0}; // กำหนดค่าเป็น 0 ทุกตำแหน่ง
  1. หากใช้อาร์เรย์ไดนามิก ใช้ calloc
int* array = calloc(rows * cols, sizeof(int));

ประสิทธิภาพและ cache

เพื่อให้การเข้าถึงข้อมูลมีประสิทธิภาพ ควรเข้าถึงแบบ row-major

ตัวอย่าง: การเข้าถึงแบบ row-major

for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        printf("%d ", array[i][j]);
    }
}

การเข้าถึงแบบ column-major (ที่ไม่แนะนำ)

ถ้าเข้าถึงแบบคอลัมน์ก่อน (column-major) จะทำให้ cache ทำงานไม่มีประสิทธิภาพ และช้าลง

เช็กลิสต์ข้อผิดพลาดที่พบบ่อย

ก่อนใช้งานอาร์เรย์สองมิติ ควรตรวจสอบประเด็นเหล่านี้:

  1. มีการเข้าถึงนอกขอบเขตหรือไม่
  2. มีการคืนหน่วยความจำครบถ้วนหรือไม่ (ถ้าเป็นแบบไดนามิก)
  3. มีการกำหนดค่าเริ่มต้นก่อนใช้งานหรือไม่
  4. หากต้องการเปลี่ยนขนาด มีการใช้ realloc อย่างถูกต้องหรือไม่
  5. เข้าถึงข้อมูลแบบ row-major หรือไม่

10. สรุป

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

  • อาร์เรย์สองมิติ = ใช้ในการเก็บข้อมูลแบบตาราง
  • การเข้าถึงองค์ประกอบ = ใช้อินเด็กซ์ [แถว][คอลัมน์]
  • การประมวลผลข้อมูล = ใช้ลูปซ้อน
  • การประยุกต์ใช้ = ตารางคูณ เกมเขาวงกต เมทริกซ์
  • ข้อควรระวัง = หลีกเลี่ยงการเข้าถึงนอกขอบเขตและ memory leak

การเข้าใจและฝึกฝนเนื้อหาเหล่านี้ จะทำให้การเขียนโปรแกรม C ของคุณแข็งแกร่งยิ่งขึ้น

11. แบบฝึกหัด

สุดท้ายนี้ ลองทำแบบฝึกหัดเพื่อทดสอบความเข้าใจ:

  1. ประกาศอาร์เรย์สองมิติขนาด 3×3 และกำหนดค่าเริ่มต้นเป็น 1 ถึง 9 แล้วพิมพ์ออกมาในรูปแบบตาราง
  2. เขียนโปรแกรมคำนวณผลรวมของแต่ละแถวและพิมพ์ออกมา
  3. เขียนโปรแกรมที่ตรวจสอบว่าอาร์เรย์ 3×3 ที่ป้อนเข้ามาเป็น magic square หรือไม่
  4. เขียนโปรแกรมเกมเขาวงกตง่าย ๆ โดยใช้อาร์เรย์สองมิติ

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

年収訴求