- 1 1. บทนำ
- 2 2. ความรู้พื้นฐานเกี่ยวกับโครงสร้างและพอยน์เตอร์
- 3 3. โครงสร้างคืออะไร?
- 4 สรุป
- 5 4. พื้นฐานของพอยน์เตอร์ (Pointer)
- 6 สรุป
- 7 5. การรวมโครงสร้าง (struct) และพอยน์เตอร์ (pointer)
- 8 สรุป
- 9 6. การทำงานร่วมกันระหว่างฟังก์ชันและโครงสร้างพอยน์เตอร์
- 10 สรุป
- 11 7. การใช้งานพอยน์เตอร์ภายในโครงสร้าง
- 12 สรุป
- 13 8. ตัวอย่างการใช้งานจริง: การสร้างลิงก์ลิสต์ (Linked List)
- 14 สรุป
- 15 9. ข้อผิดพลาดที่พบบ่อยและวิธีการดีบัก
- 16 สรุป
- 17 10. สรุป
- 18 สรุปท้ายบท
1. บทนำ
ภาษา C เป็นภาษาการเขียนโปรแกรมที่ถูกใช้อย่างแพร่หลายในงานพัฒนาระบบและโปรแกรมฝังตัว ภายในนั้น “โครงสร้าง (struct)” และ “พอยน์เตอร์ (pointer)” เป็นองค์ประกอบที่ขาดไม่ได้เพื่อให้การจัดการข้อมูลและการทำงานกับหน่วยความจำมีประสิทธิภาพ บทความนี้จะอธิบายแนวคิดเหล่านี้ตั้งแต่พื้นฐานจนถึงการประยุกต์ใช้อย่างละเอียด
เมื่ออ่านบทความนี้ คุณจะเข้าใจบทบาทของโครงสร้างและพอยน์เตอร์ในภาษา C และสามารถเรียนรู้การใช้งานผ่านตัวอย่างโค้ดจริงได้ แม้ผู้เริ่มต้นก็สามารถเข้าใจได้ง่าย เนื่องจากมีการยกตัวอย่างที่ชัดเจนประกอบ
2. ความรู้พื้นฐานเกี่ยวกับโครงสร้างและพอยน์เตอร์
โครงสร้าง (struct) คืออะไร?
โครงสร้างคือโครงสร้างข้อมูลที่ใช้รวมชนิดข้อมูลต่าง ๆ เข้าด้วยกันให้อยู่ในรูปแบบเดียว ตัวอย่างเช่น หากต้องการเก็บข้อมูลของบุคคล (ชื่อ อายุ ส่วนสูง) ไว้เป็นหน่วยเดียว โครงสร้างจะเป็นเครื่องมือที่สะดวกมาก
โค้ดด้านล่างนี้แสดงการประกาศและการใช้งานโครงสร้างแบบพื้นฐาน
#include <stdio.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person person1; // การประกาศตัวแปรโครงสร้าง
// การกำหนดค่า
strcpy(person1.name, "Taro");
person1.age = 20;
person1.height = 170.5;
// การแสดงผลข้อมูล
printf("ชื่อ: %sn", person1.name);
printf("อายุ: %dn", person1.age);
printf("ส่วนสูง: %.1fcmn", person1.height);
return 0;
}
ในตัวอย่างนี้ เราได้ประกาศโครงสร้างชื่อ Person
และรวมข้อมูล 3 ชนิดให้อยู่ในโครงสร้างเดียว ทำให้สามารถจัดการข้อมูลที่เกี่ยวข้องได้ง่ายและเป็นระบบมากขึ้น
พอยน์เตอร์ (pointer) คืออะไร?
พอยน์เตอร์คือ ตัวแปรที่เก็บ “ที่อยู่หน่วยความจำ” ของตัวแปรอื่น ใช้เพื่อจัดการหน่วยความจำแบบไดนามิกในโปรแกรม ตัวอย่างพื้นฐานของพอยน์เตอร์มีดังนี้
#include <stdio.h>
int main() {
int a = 10;
int *p; // การประกาศตัวแปรพอยน์เตอร์
p = &a; // กำหนดที่อยู่ของ a ให้กับพอยน์เตอร์
printf("ค่าของตัวแปร a: %dn", a);
printf("ค่าที่พอยน์เตอร์ p ชี้ไป: %dn", *p);
return 0;
}
ในตัวอย่างนี้ ตัวแปรพอยน์เตอร์ p
ถูกใช้เพื่อเข้าถึงค่าของตัวแปร a
โดยตรง พอยน์เตอร์มีพลังในการจัดการหน่วยความจำ แต่หากใช้อย่างไม่ถูกต้องอาจทำให้เกิดบั๊กหรือ memory leak ได้
ความสัมพันธ์ระหว่างโครงสร้างและพอยน์เตอร์
เมื่อรวมโครงสร้างกับพอยน์เตอร์ จะสามารถทำให้การจัดการข้อมูลมีความยืดหยุ่นมากขึ้น ส่วนนี้จะมีการอธิบายอย่างละเอียดภายหลัง แต่การเข้าใจพื้นฐานจะช่วยให้คุณสามารถต่อยอดไปสู่การประยุกต์ใช้ได้ง่ายขึ้น
3. โครงสร้างคืออะไร?
การประกาศโครงสร้างพื้นฐาน
โครงสร้าง (struct) เป็นโครงสร้างข้อมูลที่รวมข้อมูลชนิดต่าง ๆ เข้าด้วยกัน ภาษา C มักใช้โครงสร้างเพื่อจัดกลุ่มข้อมูลที่เกี่ยวข้อง ทำให้การจัดการข้อมูลง่ายและเป็นระบบมากขึ้น
ตัวอย่างโค้ดการประกาศโครงสร้างมีดังนี้
struct Person {
char name[50];
int age;
float height;
};
ในตัวอย่างนี้ โครงสร้างชื่อ Person
มีสมาชิก 3 ตัว ได้แก่:
name
: เก็บชื่อเป็นสตริง (array)age
: เก็บอายุเป็นจำนวนเต็มheight
: เก็บส่วนสูงเป็นทศนิยม
การประกาศโครงสร้างถือเป็นการสร้าง “ชนิดข้อมูลใหม่” ที่สามารถใช้สร้างตัวแปรได้
การประกาศและใช้งานตัวแปรโครงสร้าง
เมื่อประกาศโครงสร้างแล้ว สามารถสร้างตัวแปรจากโครงสร้างนั้นได้ ตัวอย่างดังนี้
#include <stdio.h>
#include <string.h>
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person person1; // การประกาศตัวแปรโครงสร้าง
// การกำหนดค่า
strcpy(person1.name, "Taro");
person1.age = 20;
person1.height = 170.5;
// การแสดงผลข้อมูล
printf("ชื่อ: %sn", person1.name);
printf("อายุ: %dn", person1.age);
printf("ส่วนสูง: %.1fcmn", person1.height);
return 0;
}
ในโค้ดนี้ ตัวแปร person1
ได้รับค่าและสามารถเข้าถึงสมาชิกแต่ละตัวได้
การกำหนดค่าเริ่มต้น (Initialization)
สามารถกำหนดค่าเริ่มต้นให้โครงสร้างได้ตั้งแต่ตอนประกาศตัวแปร
struct Person person2 = {"Hanako", 25, 160.0};
วิธีนี้ช่วยให้โค้ดกระชับและอ่านง่ายขึ้น
การใช้โครงสร้างแบบอาร์เรย์
หากต้องการจัดการข้อมูลหลายชุด สามารถใช้โครงสร้างเป็นอาร์เรย์ได้
struct Person people[2] = {
{"Taro", 20, 170.5},
{"Hanako", 25, 160.0}
};
for (int i = 0; i < 2; i++) {
printf("ชื่อ: %s, อายุ: %d, ส่วนสูง: %.1fcmn", people[i].name, people[i].age, people[i].height);
}
ในตัวอย่างนี้ มีการเก็บข้อมูลบุคคล 2 คนในอาร์เรย์ และแสดงผลด้วยการวนลูป
การส่งโครงสร้างให้กับฟังก์ชัน
สามารถส่งโครงสร้างเป็นอาร์กิวเมนต์ให้กับฟังก์ชันได้ ตัวอย่างดังนี้
void printPerson(struct Person p) {
printf("ชื่อ: %s, อายุ: %d, ส่วนสูง: %.1fcmn", p.name, p.age, p.height);
}
ฟังก์ชันนี้จะรับโครงสร้างเป็นพารามิเตอร์และแสดงข้อมูลที่อยู่ภายใน
สรุป
โครงสร้าง (struct) เป็นชนิดข้อมูลที่สะดวกมากในการจัดการข้อมูลที่เกี่ยวข้องกัน เมื่อเข้าใจพื้นฐานแล้ว คุณสามารถจัดการข้อมูลได้อย่างมีประสิทธิภาพและโค้ดก็อ่านง่ายขึ้น
4. พื้นฐานของพอยน์เตอร์ (Pointer)
พอยน์เตอร์คืออะไร?
พอยน์เตอร์คือคุณสมบัติอันทรงพลังในภาษา C ที่สามารถเข้าถึงและจัดการที่อยู่ของหน่วยความจำโดยตรง ในส่วนนี้ เราจะอธิบายตั้งแต่แนวคิดพื้นฐาน การประกาศ การใช้งาน ไปจนถึงตัวอย่างการประยุกต์ใช้งาน
การประกาศและการกำหนดค่าเริ่มต้นของพอยน์เตอร์
พอยน์เตอร์จะถูกประกาศด้วยการใส่เครื่องหมาย *
หน้าชนิดข้อมูล
int a = 10; // ตัวแปรปกติ
int *p; // การประกาศตัวแปรพอยน์เตอร์
p = &a; // กำหนดที่อยู่ของ a ให้กับ p
*p
หมายถึง “ค่าที่อยู่ในหน่วยความจำ” ที่พอยน์เตอร์ชี้ไป (indirection)&a
หมายถึง “ที่อยู่ของตัวแปร a” (address operator)
การเปลี่ยนแปลงค่าด้วยพอยน์เตอร์
เราสามารถใช้พอยน์เตอร์ในการเข้าถึงและแก้ไขค่าของตัวแปรได้
#include <stdio.h>
int main() {
int a = 10; // ตัวแปรปกติ
int *p = &a; // ประกาศพอยน์เตอร์และกำหนดที่อยู่ของ a
printf("ค่า a: %dn", a);
printf("ที่อยู่ของ a: %pn", &a);
printf("ค่าของ p (ที่อยู่): %pn", p);
printf("ค่าที่ p ชี้ไป: %dn", *p);
*p = 20; // เปลี่ยนค่า a ผ่านพอยน์เตอร์
printf("ค่าใหม่ของ a: %dn", a);
return 0;
}
จากโค้ดนี้จะเห็นว่า สามารถเปลี่ยนค่า a
ได้โดยอ้อมผ่านตัวแปรพอยน์เตอร์ p
การใช้งานพอยน์เตอร์กับอาร์เรย์
อาร์เรย์สามารถเข้าถึงได้ด้วยพอยน์เตอร์เช่นกัน
#include <stdio.h>
int main() {
int arr[3] = {10, 20, 30};
int *p = arr; // ชี้ไปที่ตำแหน่งแรกของอาร์เรย์
printf("สมาชิกตัวแรก: %dn", *p);
printf("สมาชิกตัวที่สอง: %dn", *(p+1));
printf("สมาชิกตัวที่สาม: %dn", *(p+2));
return 0;
}
ในตัวอย่างนี้ ใช้พอยน์เตอร์ p
เพื่อเข้าถึงสมาชิกแต่ละตัวของอาร์เรย์
สรุป
พอยน์เตอร์เป็นคุณสมบัติสำคัญในภาษา C ที่ช่วยให้การจัดการหน่วยความจำและการออกแบบโปรแกรมยืดหยุ่นและมีประสิทธิภาพมากขึ้น ในส่วนนี้เราได้เรียนรู้พื้นฐานของพอยน์เตอร์ การประกาศ การกำหนดค่า และการประยุกต์ใช้ร่วมกับอาร์เรย์ ตอนต่อไปจะอธิบาย “5. การรวมโครงสร้างกับพอยน์เตอร์” อย่างละเอียด
5. การรวมโครงสร้าง (struct) และพอยน์เตอร์ (pointer)
พื้นฐานของโครงสร้างพอยน์เตอร์
เมื่อรวมโครงสร้างกับพอยน์เตอร์ จะทำให้การจัดการข้อมูลมีความยืดหยุ่นและมีประสิทธิภาพมากขึ้น ส่วนนี้จะอธิบายวิธีใช้โครงสร้างพอยน์เตอร์ตั้งแต่พื้นฐานจนถึงตัวอย่างการประยุกต์ใช้งาน
โค้ดตัวอย่างการใช้โครงสร้างพอยน์เตอร์แบบพื้นฐานมีดังนี้
#include <stdio.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person person1 = {"Taro", 20, 170.5}; // กำหนดค่าเริ่มต้นให้โครงสร้าง
struct Person *p = &person1; // ประกาศและกำหนดค่าโครงสร้างพอยน์เตอร์
// เข้าถึงข้อมูลผ่านพอยน์เตอร์
printf("ชื่อ: %sn", p->name);
printf("อายุ: %dn", p->age);
printf("ส่วนสูง: %.1fcmn", p->height);
// เปลี่ยนค่าผ่านพอยน์เตอร์
p->age = 25;
printf("อายุใหม่: %dn", p->age);
return 0;
}
การใช้ร่วมกับการจัดสรรหน่วยความจำแบบไดนามิก
โครงสร้างพอยน์เตอร์สามารถใช้ร่วมกับการจัดสรรหน่วยความจำแบบไดนามิกได้ดี ทำให้เหมาะกับการจัดการข้อมูลจำนวนมาก ตัวอย่างดังนี้
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
float height;
};
int main() {
// การสร้างโครงสร้างด้วย malloc
struct Person *p = (struct Person *)malloc(sizeof(struct Person));
// การกำหนดค่า
strcpy(p->name, "Hanako");
p->age = 22;
p->height = 160.0;
// การแสดงผล
printf("ชื่อ: %sn", p->name);
printf("อายุ: %dn", p->age);
printf("ส่วนสูง: %.1fcmn", p->height);
// การคืนหน่วยความจำ
free(p);
return 0;
}
การใช้โครงสร้างอาร์เรย์ร่วมกับพอยน์เตอร์
เมื่อรวมอาร์เรย์ของโครงสร้างกับพอยน์เตอร์ จะช่วยให้จัดการข้อมูลหลายชุดได้สะดวกและรวดเร็ว
#include <stdio.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person people[2] = {{"Taro", 20, 170.5}, {"Hanako", 25, 160.0}};
struct Person *p = people; // พอยน์เตอร์ชี้ไปยังสมาชิกแรกของอาร์เรย์
for (int i = 0; i < 2; i++) {
printf("ชื่อ: %sn", (p + i)->name);
printf("อายุ: %dn", (p + i)->age);
printf("ส่วนสูง: %.1fcmn", (p + i)->height);
}
return 0;
}
สรุป
การรวมโครงสร้างและพอยน์เตอร์เข้าด้วยกันช่วยให้จัดการข้อมูลได้อย่างมีประสิทธิภาพ และยังเพิ่มความยืดหยุ่นในการทำงานกับหน่วยความจำ ส่วนนี้ได้ครอบคลุมตั้งแต่พื้นฐานจนถึงการใช้งานร่วมกับการจัดสรรหน่วยความจำแบบไดนามิก
6. การทำงานร่วมกันระหว่างฟังก์ชันและโครงสร้างพอยน์เตอร์
วิธีการส่งโครงสร้างไปยังฟังก์ชัน
ในการส่งโครงสร้างไปยังฟังก์ชัน สามารถทำได้ 2 วิธีหลัก ๆ ดังนี้
- การส่งค่า (Pass by Value)
เป็นการส่งสำเนาของโครงสร้างไปยังฟังก์ชัน แต่หากโครงสร้างมีข้อมูลขนาดใหญ่จะใช้หน่วยความจำมาก - การส่งแบบอ้างอิง (Pass by Reference / Pointer)
เป็นการส่งที่อยู่ของโครงสร้างไปยังฟังก์ชัน ทำให้ใช้หน่วยความจำมีประสิทธิภาพมากกว่า และสามารถแก้ไขค่าต้นฉบับได้
ตัวอย่าง: การส่งค่า (Pass by Value)
#include <stdio.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
};
// ฟังก์ชัน: ส่งค่า
void printPerson(struct Person p) {
printf("ชื่อ: %sn", p.name);
printf("อายุ: %dn", p.age);
}
int main() {
struct Person person1 = {"Taro", 20};
printPerson(person1); // ส่งค่า
return 0;
}
ในตัวอย่างนี้ ฟังก์ชัน printPerson
ได้รับสำเนาของโครงสร้าง ทำให้ข้อมูลต้นฉบับไม่ถูกแก้ไข
ตัวอย่าง: การส่งแบบอ้างอิง (Pointer)
#include <stdio.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
};
// ฟังก์ชัน: อัปเดตอายุ
void updateAge(struct Person *p) {
p->age += 1; // เพิ่มอายุ 1 ปี
}
// ฟังก์ชัน: แสดงข้อมูล
void printPerson(const struct Person *p) {
printf("ชื่อ: %sn", p->name);
printf("อายุ: %dn", p->age);
}
int main() {
struct Person person1 = {"Hanako", 25};
printf("ก่อนการเปลี่ยนแปลง:n");
printPerson(&person1);
updateAge(&person1); // ส่งแบบ pointer
printf("หลังการเปลี่ยนแปลง:n");
printPerson(&person1);
return 0;
}
ในตัวอย่างนี้ ฟังก์ชัน updateAge
ใช้พอยน์เตอร์ในการแก้ไขข้อมูลต้นฉบับโดยตรง
การทำงานร่วมกับหน่วยความจำแบบไดนามิก
โครงสร้างที่สร้างด้วย malloc
ก็สามารถจัดการผ่านฟังก์ชันได้ ตัวอย่างดังนี้
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char name[50];
int age;
};
// ฟังก์ชัน: สร้างและกำหนดค่า
struct Person *createPerson(const char *name, int age) {
struct Person *p = (struct Person *)malloc(sizeof(struct Person));
strcpy(p->name, name);
p->age = age;
return p;
}
// ฟังก์ชัน: แสดงข้อมูล
void printPerson(const struct Person *p) {
printf("ชื่อ: %sn", p->name);
printf("อายุ: %dn", p->age);
}
// ฟังก์ชัน: คืนหน่วยความจำ
void deletePerson(struct Person *p) {
free(p);
}
int main() {
struct Person *person1 = createPerson("Taro", 30); // สร้างโครงสร้างด้วย malloc
printPerson(person1);
deletePerson(person1); // คืนหน่วยความจำ
return 0;
}
ตัวอย่างนี้แสดงให้เห็นการสร้างโครงสร้างด้วยการจัดสรรหน่วยความจำแบบไดนามิก และใช้ฟังก์ชันในการจัดการข้อมูลและคืนหน่วยความจำ
สรุป
ในส่วนนี้เราได้เรียนรู้การใช้ฟังก์ชันร่วมกับโครงสร้างพอยน์เตอร์ ไม่ว่าจะเป็นการส่งค่า การส่งแบบอ้างอิง หรือการทำงานกับหน่วยความจำแบบไดนามิก การใช้พอยน์เตอร์ช่วยให้สามารถแก้ไขข้อมูลโดยตรงและจัดการหน่วยความจำได้อย่างมีประสิทธิภาพ
7. การใช้งานพอยน์เตอร์ภายในโครงสร้าง
ข้อดีของการใช้พอยน์เตอร์ในโครงสร้าง
เมื่อใส่พอยน์เตอร์ไว้ภายในโครงสร้าง จะช่วยให้การจัดการข้อมูลและการทำงานกับหน่วยความจำมีความยืดหยุ่นและมีประสิทธิภาพมากขึ้น ในส่วนนี้จะอธิบายวิธีการพื้นฐานและตัวอย่างการประยุกต์ใช้งาน
ตัวอย่างพื้นฐาน: การจัดการสตริงแบบไดนามิก
ในตัวอย่างนี้ เราจะใช้พอยน์เตอร์ภายในโครงสร้างเพื่อจัดการข้อมูลสตริงแบบไดนามิก
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Person {
char *name; // พอยน์เตอร์สำหรับชื่อ
int age;
};
// การกำหนดค่า
void setPerson(struct Person *p, const char *name, int age) {
p->name = (char *)malloc(strlen(name) + 1); // จัดสรรหน่วยความจำแบบไดนามิก
strcpy(p->name, name);
p->age = age;
}
// การแสดงผลข้อมูล
void printPerson(const struct Person *p) {
printf("ชื่อ: %sn", p->name);
printf("อายุ: %dn", p->age);
}
// คืนหน่วยความจำ
void freePerson(struct Person *p) {
free(p->name); // คืนหน่วยความจำของสตริง
}
int main() {
struct Person person;
// กำหนดข้อมูล
setPerson(&person, "Taro", 30);
// แสดงข้อมูล
printPerson(&person);
// คืนหน่วยความจำ
freePerson(&person);
return 0;
}
ตัวอย่างนี้แสดงให้เห็นว่าการใช้พอยน์เตอร์สำหรับสตริงภายในโครงสร้าง ช่วยให้สามารถจัดการข้อมูลที่มีความยาวไม่แน่นอนได้อย่างยืดหยุ่น
การจัดการหลายข้อมูลด้วยพอยน์เตอร์
เมื่อมีข้อมูลหลายชุด เราสามารถใช้พอยน์เตอร์เพื่อสร้างอาร์เรย์ของโครงสร้างและจัดการแบบไดนามิกได้
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// การประกาศโครงสร้าง
struct Student {
char *name;
int score;
};
// ฟังก์ชัน: สร้างและกำหนดค่า
struct Student *createStudent(const char *name, int score) {
struct Student *s = (struct Student *)malloc(sizeof(struct Student));
s->name = (char *)malloc(strlen(name) + 1);
strcpy(s->name, name);
s->score = score;
return s;
}
// ฟังก์ชัน: คืนหน่วยความจำ
void freeStudent(struct Student *s) {
free(s->name);
free(s);
}
int main() {
// อาร์เรย์นักเรียน
struct Student *students[2];
students[0] = createStudent("Taro", 85);
students[1] = createStudent("Hanako", 90);
// แสดงข้อมูล
for (int i = 0; i < 2; i++) {
printf("ชื่อ: %s, คะแนน: %dn", students[i]->name, students[i]->score);
}
// คืนหน่วยความจำ
for (int i = 0; i < 2; i++) {
freeStudent(students[i]);
}
return 0;
}
โค้ดนี้แสดงการจัดการข้อมูลนักเรียนหลายคนด้วยพอยน์เตอร์ภายในโครงสร้าง ทำให้เพิ่มความยืดหยุ่นในการเก็บและใช้งานข้อมูล
สรุป
การใช้พอยน์เตอร์ภายในโครงสร้างช่วยให้สามารถจัดการข้อมูลได้อย่างยืดหยุ่น รองรับการใช้งานกับข้อมูลที่มีขนาดเปลี่ยนแปลงได้ รวมถึงการสร้างโครงสร้างข้อมูลที่ซับซ้อน เช่น รายการเชื่อมโยง (linked list) หรือโครงสร้างข้อมูลหลายมิติ
8. ตัวอย่างการใช้งานจริง: การสร้างลิงก์ลิสต์ (Linked List)
โครงสร้างพื้นฐานของลิงก์ลิสต์
ลิงก์ลิสต์ (Linked List) เป็นโครงสร้างข้อมูลที่เก็บข้อมูลในรูปแบบโหนด (Node) แต่ละโหนดสามารถเพิ่มหรือลบได้แบบไดนามิก ในภาษา C เราสามารถใช้โครงสร้างและพอยน์เตอร์เพื่อสร้างลิงก์ลิสต์
โครงสร้างของลิงก์ลิสต์มีดังนี้
[ข้อมูล | พอยน์เตอร์ไปยังโหนดถัดไป] → [ข้อมูล | พอยน์เตอร์ไปยังโหนดถัดไป] → NULL
แต่ละโหนดจะมีข้อมูลและพอยน์เตอร์ไปยังโหนดถัดไป ส่วนโหนดสุดท้ายจะมีค่า NULL
เพื่อบอกว่าถึงจุดสิ้นสุดของลิสต์
การประกาศโหนด
โค้ดต่อไปนี้แสดงการประกาศโครงสร้างสำหรับโหนดของลิงก์ลิสต์
#include <stdio.h>
#include <stdlib.h>
// การประกาศโครงสร้างโหนด
struct Node {
int data; // ข้อมูล
struct Node *next; // พอยน์เตอร์ไปยังโหนดถัดไป
};
การเพิ่มโหนด
โค้ดตัวอย่างการเพิ่มโหนดใหม่ที่ท้ายลิสต์มีดังนี้
void append(struct Node **head, int newData) {
// สร้างโหนดใหม่
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
struct Node *last = *head; // ใช้สำหรับหาตำแหน่งท้ายสุด
newNode->data = newData; // กำหนดค่า
newNode->next = NULL; // โหนดใหม่คือโหนดท้ายสุด
// ถ้าลิสต์ยังว่าง
if (*head == NULL) {
*head = newNode;
return;
}
// เลื่อนไปจนถึงโหนดท้ายสุด
while (last->next != NULL) {
last = last->next;
}
// เพิ่มโหนดใหม่ที่ท้ายสุด
last->next = newNode;
}
การแสดงผลโหนดทั้งหมด
ฟังก์ชันสำหรับแสดงผลโหนดทั้งหมดในลิสต์
void printList(struct Node *node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULLn");
}
การลบโหนด
โค้ดตัวอย่างการลบโหนดที่มีค่าตรงกับข้อมูลที่ระบุ
void deleteNode(struct Node **head, int key) {
struct Node *temp = *head, *prev;
// ถ้าโหนดแรกคือโหนดที่ต้องการลบ
if (temp != NULL && temp->data == key) {
*head = temp->next;
free(temp);
return;
}
// ค้นหาโหนดที่ต้องการลบ
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
// ถ้าไม่พบข้อมูล
if (temp == NULL) return;
// ลบโหนดออกจากลิสต์
prev->next = temp->next;
free(temp);
}
โปรแกรมตัวอย่าง: การทำงานกับลิงก์ลิสต์
รวมโค้ดทั้งหมดเข้าด้วยกัน จะได้โปรแกรมตัวอย่างดังนี้
#include <stdio.h>
#include <stdlib.h>
// การประกาศโครงสร้างโหนด
struct Node {
int data;
struct Node *next;
};
// เพิ่มโหนดใหม่ที่ท้ายลิสต์
void append(struct Node **head, int newData) {
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
struct Node *last = *head;
newNode->data = newData;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
return;
}
while (last->next != NULL) {
last = last->next;
}
last->next = newNode;
}
// แสดงผลลิสต์
void printList(struct Node *node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULLn");
}
// ลบโหนด
void deleteNode(struct Node **head, int key) {
struct Node *temp = *head, *prev;
if (temp != NULL && temp->data == key) {
*head = temp->next;
free(temp);
return;
}
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) return;
prev->next = temp->next;
free(temp);
}
int main() {
struct Node *head = NULL;
// เพิ่มโหนด
append(&head, 10);
append(&head, 20);
append(&head, 30);
printf("ข้อมูลในลิงก์ลิสต์:n");
printList(head);
// ลบโหนดที่มีค่า 20
deleteNode(&head, 20);
printf("หลังจากลบค่า 20:n");
printList(head);
return 0;
}
สรุป
ในส่วนนี้เราได้เรียนรู้การสร้างและใช้งานลิงก์ลิสต์ด้วยโครงสร้างและพอยน์เตอร์ ลิงก์ลิสต์มีความยืดหยุ่นสูง สามารถเพิ่มหรือลบข้อมูลได้ง่าย จึงถูกนำไปใช้ในอัลกอริทึมและระบบการจัดการข้อมูลหลายประเภท
9. ข้อผิดพลาดที่พบบ่อยและวิธีการดีบัก
การใช้โครงสร้างและพอยน์เตอร์ในภาษา C มีความทรงพลังมาก แต่หากใช้งานผิดพลาดอาจทำให้โปรแกรมล้มเหลวหรือทำงานไม่ถูกต้องได้ ส่วนนี้จะแนะนำข้อผิดพลาดที่พบบ่อยและแนวทางแก้ไข
1. การไม่กำหนดค่าเริ่มต้นให้พอยน์เตอร์
ตัวอย่างปัญหา:
struct Node *p; // พอยน์เตอร์ที่ยังไม่ได้กำหนดค่า
p->data = 10; // เกิดข้อผิดพลาด
สาเหตุ:
พอยน์เตอร์ p
ยังไม่ได้ชี้ไปยังหน่วยความจำที่ถูกต้อง ทำให้เกิดการเข้าถึงหน่วยความจำผิดพลาด
วิธีแก้ไข:
กำหนดค่าเริ่มต้นให้พอยน์เตอร์เสมอ เช่นใช้ malloc
struct Node *p = (struct Node *)malloc(sizeof(struct Node));
p->data = 10; // ใช้งานได้ตามปกติ
2. การรั่วไหลของหน่วยความจำ (Memory Leak)
ตัวอย่างปัญหา:
struct Node *p = (struct Node *)malloc(sizeof(struct Node));
// ใช้งานเสร็จแล้วแต่ไม่คืนหน่วยความจำ
สาเหตุ:
หากไม่คืนหน่วยความจำที่ถูกจัดสรรด้วย malloc
หน่วยความจำจะถูกจองไว้จนจบการทำงานของโปรแกรม
วิธีแก้ไข:
ใช้ free
เพื่อคืนหน่วยความจำทุกครั้งหลังใช้งาน
free(p);
ถ้ามีหลายโหนด เช่น ลิงก์ลิสต์ ต้องคืนหน่วยความจำทุกโหนด
struct Node *current = head;
struct Node *next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
3. พอยน์เตอร์ล่องลอย (Dangling Pointer)
ตัวอย่างปัญหา:
struct Node *p = (struct Node *)malloc(sizeof(struct Node));
free(p);
p->data = 10; // การเข้าถึงหลัง free → พฤติกรรมไม่สามารถคาดเดาได้
สาเหตุ:
หลังจากคืนหน่วยความจำ พอยน์เตอร์ยังคงชี้ไปที่ตำแหน่งเดิมซึ่งไม่ถูกต้อง
วิธีแก้ไข:
ตั้งค่าพอยน์เตอร์ให้เป็น NULL
หลังจากคืนหน่วยความจำ
free(p);
p = NULL;
4. การเข้าถึงพอยน์เตอร์ NULL
ตัวอย่างปัญหา:
struct Node *p = NULL;
p->data = 10; // เข้าถึง NULL → เกิดข้อผิดพลาด
สาเหตุ:
การเข้าถึงหน่วยความจำผ่านพอยน์เตอร์ NULL
ทำให้เกิด segmentation fault
วิธีแก้ไข:
ตรวจสอบว่าพอยน์เตอร์ไม่เป็น NULL
ก่อนใช้งาน
if (p != NULL) {
p->data = 10;
} else {
printf("พอยน์เตอร์เป็น NULLn");
}
วิธีการดีบัก
1. ใช้ Debugger
ใช้เครื่องมืออย่าง GDB เพื่อตรวจสอบค่าตัวแปรและการทำงานของโปรแกรมแบบทีละขั้น
gcc -g program.c -o program
gdb ./program
2. ใช้ printf เพื่อตรวจสอบค่า
สามารถพิมพ์ค่าที่อยู่หรือค่าที่พอยน์เตอร์ชี้เพื่อช่วยวิเคราะห์ข้อผิดพลาด
printf("ที่อยู่: %p, ค่า: %dn", (void *)p, *p);
3. ตรวจสอบการรั่วไหลของหน่วยความจำ
ใช้ valgrind
เพื่อตรวจสอบ memory leak หรือการเข้าถึงหน่วยความจำที่ไม่ถูกต้อง
valgrind --leak-check=full ./program
สรุป
ในส่วนนี้เราได้อธิบายข้อผิดพลาดที่พบบ่อยเมื่อใช้โครงสร้างและพอยน์เตอร์ในภาษา C และแนวทางแก้ไข ได้แก่
- พอยน์เตอร์ที่ยังไม่ได้กำหนดค่า
- การรั่วไหลของหน่วยความจำ
- พอยน์เตอร์ล่องลอย
- การเข้าถึงพอยน์เตอร์ NULL
การหลีกเลี่ยงข้อผิดพลาดเหล่านี้และการใช้เครื่องมือดีบักจะช่วยให้โปรแกรมมีความปลอดภัยและเชื่อถือได้มากขึ้น
10. สรุป
ทบทวนสิ่งที่ได้เรียนรู้
ในส่วนที่ผ่านมา เราได้ศึกษาเกี่ยวกับโครงสร้าง (struct) และพอยน์เตอร์ (pointer) ในภาษา C ตั้งแต่พื้นฐานจนถึงการประยุกต์ใช้งาน ในส่วนนี้จะสรุปสิ่งสำคัญที่ได้เรียนรู้และแนวทางการต่อยอด
- พื้นฐานของโครงสร้าง
- เป็นโครงสร้างข้อมูลที่รวมชนิดข้อมูลต่าง ๆ เข้าด้วยกัน
- ช่วยจัดระเบียบข้อมูลที่เกี่ยวข้องให้ง่ายต่อการใช้งาน
- พื้นฐานของพอยน์เตอร์
- สามารถจัดการที่อยู่หน่วยความจำได้โดยตรง
- จำเป็นสำหรับการใช้งานหน่วยความจำแบบไดนามิกและการอ้างอิงข้อมูล
- การรวมโครงสร้างและพอยน์เตอร์
- ทำให้การจัดการข้อมูลมีประสิทธิภาพยิ่งขึ้น
- รองรับการใช้งานกับหน่วยความจำแบบไดนามิก
- การทำงานร่วมกับฟังก์ชัน
- สามารถแก้ไขข้อมูลต้นฉบับได้เมื่อส่งโครงสร้างแบบพอยน์เตอร์
- ช่วยให้โปรแกรมออกแบบได้ยืดหยุ่นและใช้หน่วยความจำอย่างมีประสิทธิภาพ
- การใช้พอยน์เตอร์ภายในโครงสร้าง
- เหมาะสำหรับการจัดการข้อมูลที่มีขนาดเปลี่ยนแปลงได้
- สามารถสร้างโครงสร้างข้อมูลที่ซับซ้อน เช่น ลิงก์ลิสต์หรือเมทริกซ์
- การสร้างลิงก์ลิสต์
- เป็นตัวอย่างของโครงสร้างข้อมูลแบบไดนามิกที่ใช้โครงสร้างและพอยน์เตอร์
- รองรับการเพิ่มและลบข้อมูลได้อย่างยืดหยุ่น
- ข้อผิดพลาดที่พบบ่อยและการดีบัก
- เข้าใจปัญหา เช่น พอยน์เตอร์ที่ไม่ได้กำหนดค่า, memory leak, dangling pointer
- ใช้เครื่องมืออย่าง GDB หรือ Valgrind เพื่อตรวจสอบและแก้ไขปัญหา
การประยุกต์ใช้งานจริง
สิ่งที่เรียนรู้สามารถนำไปประยุกต์ในงานจริง เช่น
- ระบบจัดการไฟล์ → ใช้โครงสร้างและพอยน์เตอร์เพื่อเก็บข้อมูลไฟล์
- โครงสร้างข้อมูลไดนามิก → ขยายจากลิงก์ลิสต์ไปสู่สแตก (stack) และคิว (queue)
- เกมหรือการจำลอง → ใช้โครงสร้างเพื่อเก็บข้อมูลตัวละครหรือสถานะ
- ระบบฐานข้อมูล → ใช้โครงสร้างและพอยน์เตอร์เพื่อจัดการเรคคอร์ดข้อมูล
ก้าวต่อไป
- การปรับแต่งโค้ด
ทดลองแก้ไขและนำตัวอย่างโค้ดไปใช้ในโปรเจกต์จริง - เรียนรู้โครงสร้างข้อมูลที่ซับซ้อนยิ่งขึ้น
เช่น ลิงก์ลิสต์สองทาง, ต้นไม้ (Tree), กราฟ (Graph) - การผสานกับอัลกอริทึม
ใช้โครงสร้างและพอยน์เตอร์เพื่อสร้างอัลกอริทึมการค้นหาและการจัดเรียง - พัฒนาทักษะการดีบักและเพิ่มประสิทธิภาพ
ใช้เครื่องมือดีบักและการวิเคราะห์หน่วยความจำเพื่อสร้างโปรแกรมที่เสถียร
สรุปท้ายบท
โครงสร้าง (struct) และพอยน์เตอร์ (pointer) ในภาษา C เป็นหัวใจสำคัญของการเขียนโปรแกรมที่มีประสิทธิภาพและยืดหยุ่น บทความนี้ได้อธิบายตั้งแต่พื้นฐานจนถึงการประยุกต์ใช้งาน พร้อมตัวอย่างจริงเพื่อช่วยให้เข้าใจได้ลึกซึ้งยิ่งขึ้น
เมื่อฝึกฝนและนำไปประยุกต์ใช้ คุณจะสามารถพัฒนาโปรแกรมที่ซับซ้อนขึ้น เช่น ระบบจัดการข้อมูล อัลกอริทึมขั้นสูง หรือแม้แต่การพัฒนาเกมได้
ใช้ความรู้เหล่านี้เพื่อก้าวสู่การเป็นนักพัฒนา C ที่แข็งแกร่งยิ่งขึ้น!