C อาร์เรย์
อาร์เรย์คือ ตัวแปรหลายตัวชนิดเดียวกัน วางเรียงติดกันในหน่วยความจำ เข้าถึงด้วย index
ประกาศและใช้งาน
#include <stdio.h>
int main(void)
{
int scores[5]; // อาร์เรย์ int 5 ตัว — ค่าเริ่มต้นเป็นขยะ
scores[0] = 80;
scores[1] = 75;
scores[2] = 90;
scores[3] = 65;
scores[4] = 88;
printf("%d\n", scores[0]); // 80
printf("%d\n", scores[4]); // 88
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
printf("รวม %d เฉลี่ย %.1f\n", sum, sum / 5.0);
return 0;
}- ขนาดในวงเล็บ
[5]= จำนวนสมาชิก ต้องเป็นค่าคงที่ตอนคอมไพล์ (หรือ VLA — ดูท้ายบท) - index จาก
0ถึงขนาด - 1 - ไม่มีการตรวจขอบเขต —
scores[5],scores[-1],scores[999]คอมไพล์ผ่านและรัน = undefined behavior
initialize ตอนประกาศ
int a[5] = {10, 20, 30, 40, 50};
int b[5] = {1, 2}; // ที่เหลือเป็น 0 → {1, 2, 0, 0, 0}
int c[5] = {0}; // ทุกตัวเป็น 0
int d[] = {1, 2, 3, 4}; // ให้คอมไพเลอร์นับขนาดเอง → d มี 4 ตัว
int e[100] = {0}; // 100 ตัว เป็น 0 หมด
// designated initializer (C99)
int f[10] = {[0] = 1, [9] = 99}; // f[0]=1, f[9]=99, ที่เหลือ 0int arr[100]; (ไม่ init) มีค่าขยะ ทั้ง 100 ตัว — ถ้าต้องการ 0 เขียน = {0}
หาความยาวด้วย sizeof
int nums[] = {5, 10, 15, 20, 25, 30};
size_t len = sizeof(nums) / sizeof(nums[0]); // 24 / 4 = 6
for (size_t i = 0; i < len; i++) {
printf("%d ", nums[i]);
}
printf("\n");sizeof(nums) = ขนาดทั้งอาร์เรย์เป็นไบต์, sizeof(nums[0]) = ขนาดหนึ่งสมาชิก — หารกันได้จำนวนสมาชิก
เทคนิคนี้ใช้ได้เฉพาะกับตัวแปรอาร์เรย์จริง ในขอบเขตที่ประกาศ — ไม่ได้กับ pointer หรืออาร์เรย์ที่ส่งเข้าฟังก์ชันแล้ว (ดูหัวข้อถัดไป)
array decay — ส่งอาร์เรย์เข้าฟังก์ชัน
เมื่อส่งอาร์เรย์เป็น argument มันจะ “สลายตัว” (decay) เป็น pointer ไปสมาชิกตัวแรก ฟังก์ชันได้แค่ที่อยู่ ไม่รู้ขนาด:
#include <stdio.h>
void print_all(int arr[], int n) // int arr[] จริง ๆ คือ int *arr
{
printf("sizeof ในฟังก์ชัน: %zu\n", sizeof(arr)); // ขนาด pointer (มัก 8) ไม่ใช่ 20
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int data[5] = {1, 2, 3, 4, 5};
printf("sizeof ใน main: %zu\n", sizeof(data)); // 5 * sizeof(int) (มัก 20)
print_all(data, 5); // ต้องส่งขนาดไปด้วยเสมอ
return 0;
}ผลที่ตามมา: ฟังก์ชันที่รับอาร์เรย์ต้องมีทางรู้ขนาด — วิธีที่พบบ่อยคือรับ “จำนวนสมาชิก” เป็นพารามิเตอร์แยก อีกวิธีคือใช้ตัวปิดท้าย (สตริงใช้ '\0', บางอาร์เรย์ใช้ sentinel เช่น NULL ตัวสุดท้าย) — C ไม่มีทางรู้ขนาดจาก pointer เปล่า ๆ
void f(int arr[]), void f(int arr[5]), void f(int *arr) — ทั้งสามเหมือนกันหมดสำหรับคอมไพเลอร์ ตัวเลขใน [] ถูกเมิน
ฟังก์ชันแก้เนื้ออาร์เรย์ได้
เพราะฟังก์ชันได้ pointer ไปอาร์เรย์จริง (ไม่ใช่สำเนา) มันแก้ค่าข้างในได้:
#include <stdio.h>
void double_all(int arr[], int n)
{
for (int i = 0; i < n; i++) {
arr[i] *= 2; // แก้อาร์เรย์ตัวจริงของผู้เรียก
}
}
int main(void)
{
int v[3] = {1, 2, 3};
double_all(v, 3);
printf("%d %d %d\n", v[0], v[1], v[2]); // 2 4 6
return 0;
}ทุกอย่างในการเรียกฟังก์ชันยังเป็น pass by value — pointer ที่เกิดจากการ decay ก็ถูก *คัดลอก* เข้าพารามิเตอร์เหมือนกัน เพียงแต่มันชี้ไปที่อาร์เรย์เดิมของผู้เรียก ฟังก์ชันจึงแก้ *เนื้อ* อาร์เรย์ได้ (ต่างจากการส่งตัวแปร int ตรง ๆ ในบทที่ 8 ที่ได้แค่สำเนาของค่า)
อาร์เรย์ของ char = สตริง
char name[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char name2[] = "Hello"; // เหมือนกัน — คอมไพเลอร์ใส่ '\0' ให้ (ขนาด 6)
printf("%s\n", name2);บทหน้าว่าด้วยสตริงโดยเฉพาะ
VLA — variable length array (C99, ทางเลือก C11)
void process(int n)
{
int buffer[n]; // ขนาดกำหนดตอนรัน — VLA
// ...
}VLA จองบน stack — ถ้า n ใหญ่มากจะ stack overflow และมาตรฐานบางส่วนไม่บังคับให้รองรับ หลายทีมห้ามใช้ VLA สำหรับข้อมูลขนาดไม่รู้แน่ใช้ malloc (บทที่ 14) ดีกว่า
เรื่องที่คนมักพลาด
- เข้าถึงนอกขอบเขต —
arr[n]เมื่ออาร์เรย์มีnตัว (index สุดท้ายคือn-1),arr[-1], index จากตัวแปรที่ไม่ได้เช็ค — UB ทั้งหมด C ไม่เตือน int arr[10];แล้วอ่านก่อน assign — ค่าขยะ ใส่= {0}ถ้าต้องการ 0sizeof(arr)ในฟังก์ชันที่รับอาร์เรย์ — ได้ขนาด pointer (มัก 8) ไม่ใช่ขนาดอาร์เรย์ ต้องบอกขนาดด้วยวิธีอื่น (พารามิเตอร์แยก หรือตัวปิดท้าย/sentinel)int n = 5; int arr[n];แล้วคาดว่าเป็นอาร์เรย์ปกติ — เป็น VLA (C99) พฤติกรรมต่างกัน และ= {0}ใช้ไม่ได้- copy อาร์เรย์ด้วย
b = a;— ไม่ได้ อาร์เรย์ assign ไม่ได้ ใช้memcpy(b, a, sizeof a);หรือ loop - เปรียบเทียบอาร์เรย์ด้วย
a == b— เทียบที่อยู่ ไม่ใช่เนื้อหา ต้องเทียบทีละสมาชิกตามชนิดmemcmpเทียบ *ไบต์ดิบ* — ใช้ได้เฉพาะชนิดจำนวนเต็มที่ไม่มี padding (อาร์เรย์ของdouble:0.0กับ-0.0value เท่ากันแต่memcmpบอกไม่เท่า) - คืนอาร์เรย์ local จากฟังก์ชัน —
int* f() { int a[5]; return a; }— dangling
สรุป
- อาร์เรย์ = บล็อกต่อเนื่อง index จาก 0, ไม่มี bounds check; ขนาดคงที่ (ประกาศปกติ กำหนดตอนคอมไพล์ / VLA กำหนดตอนรัน — ปรับขนาดหลังสร้างไม่ได้)
- init:
{v1, v2}(ที่เหลือ 0),{0}(ทุกตัว 0),[]ให้คอมไพเลอร์นับขนาด - ความยาว:
sizeof(arr) / sizeof(arr[0])— เฉพาะตัวแปรอาร์เรย์จริง - ส่งเข้าฟังก์ชัน → decay เป็น pointer (ที่ถูกส่งแบบ pass by value เช่นกัน) → ต้องบอกขนาด (พารามิเตอร์แยก หรือ sentinel), และฟังก์ชันแก้เนื้ออาร์เรย์ได้
- อาร์เรย์ assign / เปรียบเทียบตรง ๆ ไม่ได้ — ใช้
memcpy/memcmp
บทถัดไปว่าด้วยสตริงใน C — ทำไมมันคืออาร์เรย์ของ char ที่ปิดด้วย '\0' และฟังก์ชันใน <string.h>