Struct
แนะนำ Struct
ในภาษา C++ เราใช้ Struct (ย่อมาจาก structure) ในการสร้างชนิดข้อมูลใหม่เพื่อเก็บค่าหลายๆอย่างรวมกันเป็นชิ้นเดียว ช่วยให้เราจัดการข้อมูลที่มีความเกี่ยวข้องกันได้ง่ายและเป็นระเบียบมากขึ้น ตัวแปรที่อยู่ภายใน struct จะถูกเรียกว่าสมาชิก (member) หรือ field ข้อมูล
struct มีประโยชน์มากเวลาที่เราอยากสร้างชนิดข้อมูลซับซ้อนที่สื่อความหมายบางอย่างที่มีรายละเอียด (attribute) หลายอย่างประกอบกันอยู่ เช่น
- ใช้ Struct เป็นข้อมูลนักเรียน
- ตำแหน่งพิกัดบนแผนที่
- รายชื่อสิ่งของในคลังสินค้า
โดยรายละเอียดทั้งหมดของแต่ละอันก็จะเป็น attribute อยู่ในนั้น
นี่คือ Basic Syntax ของ struct
struct StructName {
// Members (variables) of the struct
DataType member1;
DataType member2;
// ...
};
ตัวอย่างการใช้งาน
#include <iostream>
#include <string>
using namespace std;
// Define a struct named 'Student'
struct Student {
string name;
int age;
float gpa;
};
int main() {
// Create an instance of Student
Student student1;
// Assign values to members of student1
student1.name = "John Doe";
student1.age = 20;
student1.gpa = 3.5;
// Access and print members of student1
cout << "Name: " << student1.name << endl;
cout << "Age: " << student1.age << endl;
cout << "GPA: " << student1.gpa << endl;
return 0;
}
จาก code ด้านบน เป็นการสร้างและใช้งาน struct ชื่อ Student โดยส่วนที่ประกาศ Struct Student นั้น
struct Student { ... };
บรรทัดนี้จะสร้าง "แบบพิมพ์เขียว" ชนิดข้อมูลใหม่ ซึ่งก็คือ struct ที่ชื่อว่า Student ซึ่ง struct จะทำหน้าที่รวมข้อมูลไว้ด้วยกัน โดยแต่ละส่วนหรือตัวแปรที่อยู่ภายใน struct จะถือเป็นชิ้นส่วน หรือเป็น "สมาชิก" (member)- struct ชื่อ Student ประกอบไปด้วยสมาชิก 3 ตัว คือ
string name;
จะเก็บ "ชื่อ" ของนักเรียน เป็นชนิดข้อมูล string (ข้อความ)int age;
จะเก็บ "อายุ" ของนักเรียน เป็นชนิดข้อมูล integer (ตัวเลขจำนวนเต็ม)float gpa;
จะเก็บ "คะแนนเฉลี่ย" (GPA) เป็นชนิดข้อมูล float (ตัวเลขทศนิยม
โดยการใช้งาน Struct Student ภายในฟังก์ชัน main()
- การสร้าง instance
Student student1;
บรรทัดนี้สร้าง instance (หรือ ตัวแปร) ชื่อstudent1
จาก struct ชื่อว่า Student ตัวแปร instance น ี้จะมีสมาชิกชื่อ name, age และ gpa เป็นของตัวเอง - โดยมีการกำหนดค่าสมาชิกของ student1 คือ
student1.name = "John Doe";
ทำให้สมาชิกตัวแปรที่มีชื่อว่า "name" ที่อยู่ภายใน student1 นั้นได้เก็บค่า "John Doe" (ซึ่งเป็นข้อมูลชนิดข้อความหรือ string)student1.age = 20;
ทำให้สมาชิกตัวแปรที่มีชื่อว่า "age" ที่อยู่ภายใน student1 นั้นได้เก็บค่า 20 (ซึ่งเป็นตัวเลขจำนวนเต็ม)student1.gpa = 3.5;
ทำให้สมาชิกตัวแปรที่มีชื่อว่า "gpa" ที่อยู่ภายใน student1 นั้นได้เก็บค่า 3.5 (ซึ่งเป็นตัวเลขที่มีทศนิยม) สรุปคือเราเอาข้อมูล ชื่อ อายุ และ คะแนนเฉลี่ย ไปยัดไว้ในสมาชิกของตัวแปร instance ที่ชื่อว่า student1
- คำสั่ง
std::cout
ทำการเข้าถึงสมาชิกของ student1 แบบตรงๆ แล้วจึงพิมพ์ค่าของ ชื่อ อายุ และ คะแนนเฉลี่ย ออกมาเป็นการสาธิตว่าเราจะสามารถอ่านค่าที่อยู่ในสมาชิกของ struct เหล่านี้ออกมาได้อย่างไร
จากตัวอย ่างจะเห็นว่าแทนที่เราจะมาสร้างตัวแปรแยกระหว่าง name, age และ gpa เราสามารถรวมชุดข้อมูลและสร้างเป็นข้อมูลประเภทใหม่ออกมาได้
Struct กับ Array
ในภาษา C++ เราใช้ struct ควบคู่กับ Array เพื่อจัดการเก็บกลุ่มข้อมูลที่มีโครงสร้างแบบเดียวกันได้ง่ายขึ้น ซึ่งจะมีประโยชน์มากโดยเฉพาะเวลาที่ต้องบริหารข้อมูลจำนวนมากที่มีลักษณะที่ซับซ้อน มาดูตัวอย่างการใช้ struct กับ array กัน
จากตัวอย่างเดิมของ Student
struct เราจะลองเปลี่ยนเป็น student array ที่สามารถเก็บข้อมูล student ได้หลายคนบ้าง
#include <iostream>
#include <string>
using namespace std;
// Define the Student struct
struct Student {
string name;
int age;
float gpa;
};
int main() {
// Create an array of 3 Student structs
Student students[3];
// Initialize data for each student
students[0] = {"John Doe", 20, 3.5};
students[1] = {"Jane Smith", 22, 3.8};
students[2] = {"Bob Johnson", 19, 3.6};
// Iterate over the array and print each student's information
for (int i = 0; i < 3; i++) {
cout << "Student #" << i + 1 << ":\n"
<< "Name: " << students[i].name << "\n"
<< "Age: " << students[i].age << "\n"
<< "GPA: " << students[i].gpa << "\n\n";
}
return 0;
}
จาก code ด้านบน
Student students[3];
ประกาศสร้าง Array ชื่อว่าstudents
ที่สามารถบรรจุข้อมูล 3 ชิ้น โดยแต่ละชิ้นจะเก็บข้อมูลชนิดเดียวกับ StructStudent
หรือก็คือ Array นี้จะใช้เก็บข้อมูลนักเรียน 3 คน- แต่ละส่วน (element) ภายใน array
students
นั้น เราจะใส่ข้อมูลลงไปเป็น struct เรียกว่าStudent
พร้อมกำหนดค่าเริ่มต้น (initialize) โดยวิธีนี้เรียกว่า aggregate initialization (ใส่ค่ารวมๆ กันในเครื่องหมายปีกกา) - ในบรรทัดที่ใส่
for loop
จะเห็นว่ามีการวน loop ไปทีละส่วนใน student arrays การเข้าถึงสมาชิก หรือ member ของ struct ที่อยู่ใน Array สามารถทำได้เหมือนกับการเรียกสมาชิกจาก object นั้นๆ ด้วยตัวจุด (dot operator) เช่นstudents[i].name
ก็คือการอ่านค่าสมาชิกตัว name ของนักเรียนลำดับที่ i ในอาร์เรย์ของเรา
Struct กับ Vector
ในการใช้ struct ร่วมกับ vector จะทำให้เราสามารถสร้าง vector ที่เก็บข้อมูลของ struct ไว้ได้หลายๆ ตัว วิธีนี้จะมีประโยชน์มากเวลาที่เราต้องจัดการ list ของ Object ที่มีความซับซ้อนและมีหลายๆ field อยู่ภายใน
นี่คือตัวอย่างของการใช้งานร่วมกัน
#include <iostream>
#include <string>
#include <vector>
// Define a struct to represent a student
struct Student {
std::string name;
int age;
float gpa;
};
int main() {
// Create a vector of Student structs
std::vector<Student> students;
// Add students to the vector
students.push_back({"John Doe", 20, 3.5});
students.push_back({"Jane Smith", 22, 3.8});
students.push_back({"Bob Johnson", 19, 3.6});
// Iterate over the vector and print each student's information
for (const auto &student : students) {
std::cout << "Name: " << student.name << ", Age: " << student.age
<< ", GPA: " << student.gpa << std::endl;
}
return 0;
}
ผลลัพธ์
Name: John Doe, Age: 20, GPA: 3.5
Name: Jane Smith, Age: 22, GPA: 3.8
Name: Bob Johnson, Age: 19, GPA: 3.6
จาก code ด้านบน
- มีการสร้าง struct ชื่อ
Student
ที่ประกอบไปด้วย 3 fields: ชื่อ (name), อายุ (age), และเกรดเฉลี่ย (gpa) - มีการสร้าง vector ชื่อ
students
เพื่อเก็บข้อมูลของ struct ประเภทStudent
- ใช้ method
push_back
เพื่อเพิ่มข้อมูลนักเรียน (Student) คนใหม่เข้าไปใน vector - ใช้ range-based for loop เพื่อวนข้อมูลใน vector และแสดงรายละเอียดของนักเรียนแต่ละคนออกมา
จะเห็นว่า เราสามารถประกาศประเภทของ Vector ได้เหมือนการประกาศประเภทของชนิดข้อมูลทั่วๆไปได้เลย และสามารถใช้งานร่วมกับคำสั่งอย่าง push_back, loop เพื่อจัดการข้อมูลเหมือน Vector ทั่วไปออกมาได้เช่นเดียวกัน
Struct กับ Pointer
เมื่อเราใช้ struct ร่วมกับ pointer เราจะสามารถ
- จัดสรรหน่วยความจำให้กับ struct แบบ dynamic ได้
- ส่ง struct เข้าไปยัง function โดยอ้างอิงจาก address ของ struct เองได้ ทำให้ทำงานได้เร็วขึ้น
- สร้างโครงสร้างข้อมูลแบบเชื่อมโยง เช่น linked list, tree ออกมาได้จากคุณสมับัติของ pointer
นี่คือตัวอย่าง ของการใช้ struct และ pointer
#include <iostream>
using namespace std;
// Define a struct to represent a point in 2D space
struct Point {
int x;
int y;
};
int main() {
// Dynamically allocate a new Point
Point *pPoint = new Point;
// Access and modify the Point's fields through the pointer
pPoint->x = 10;
pPoint->y = 20;
// Print the Point's coordinates
cout << "The point is at (" << pPoint->x << ", " << pPoint->y << ")"
<< endl;
// Don't forget to free the dynamically allocated memory
delete pPoint;
return 0;
}
จาก code ด้านบน
- มีการกำหนด struct ชื่อ
Point
ซึ่งมี 2 fields ที่เป็นจำนวนเต็ม ได้แก่ x และ y - สร้าง pointer ชื่อ
pPoint
ชนิดPoint
เพื่อเก็บที่อยู่ของหน่วยความจำที่เราจะจัดสรรขึ้นใหม่แบบ dynamic (ด้วยคำสั่ง new) สำหรับเก็บข้อมูลของ struct Point - ใช้ตัวดำเนินการลูกศร (
->
) เพื่อเข้าถึงและแก้ไขค่าของสมาชิกภายใน struct ผ่านทาง pointer - เข้าถึงข้อมูล
Point
ผ่านทาง pointer เพื่อแสดงค่าพิกัด (x, y) ออกมา - สุดท้าย เราสามารถล้างหน่วยความจำที่ จัดสรรไว้ (ด้วยคำสั่ง
delete
) เพื่อป้องกันปัญหา memory leak ได้
ความแตกต่างใหญ่ๆระหว่างการใช้ pointer และไม่ใช้ pointer
เพื่อให้เห็นภาพมากขึ้นจะขอยกตัวอย่างกับการใช้ struct Point
แบบปกติ กับแบบใช้ pointer มาเปรียบเทียบกันนะครับ
1. การใช้ struct Point
แบบปกติ มีคุณสมบัติดังนี้
- เมื่อเราสร้าง instance ของ struct ชื่อ
Point
ขึ้นมา พื้นที่จะถูกจัดสรรให้บน stack (stack เป็นพื้นที่หน่วยความจำแบบพิเศษที่ทำงานคล้ายๆกองของที่เรียงกัน โดยข้อมูลล่าสุดที่ใส่เข้าไปจะอยู่บนสุด และเป็นข้อมูลแรกที่ถูกดึงออกไป) - สำหรับ struct ทั่วไป compiler จะทำการจัดการหน่วยความจำสำหรับ instance ที่อยู่บน stack โดยอัตโนมัติ และหน่วยความจำจะถูกปล่อยออกมาเมื่อตัวแปรนั้นหลุดออกจาก scope ไป
- สมาชิกของ struct สามารถเข้าถึงได้โดยตรงโดยใช้ตัวดำเนินการจุด (
.
) - การกำหนดค่าของ instance struct หนึ่ง ไปให้กับอีก instance หนึ่งจะเป็นการคัดลอกค่าของสมาชิกแต่ละตัวโดยตรง ซึ่งวิธีนี้เรียกว่า shallow copy (สำเนาข้อมูลทั้งหมด)
Point pt;
pt.x = 10;
pt.y = 20;
Point pt2 = pt; // Copy
pt2.x = 30;
cout << "pt->x: " << pt.x << endl; // Output: 10
struct Point
****ด้วย pointer
- เมื่อเราใช้ pointer ชี้ไปยัง struct ชื่อ
Point
และจัดสรรพื้นที่ด้วยคำสั่งnew
หน่วยความจำสำหรับโครงสร้างนั้นจะถูกจัดสรรอยู่บน heap - เราต้องรับผิดชอบในการจัดการหน่วยความจำที่ถูกจัดสรรบน heap ด้วยตัวเอง โดยจะต้องปล่อยหน่วยความจำอย่างชัดเจนด้วยคำสั่ง
delete
เพื่อหลีกเลี่ยงปัญหา memory leak - สมาชิกของ struct จะถูกเข้าถึงผ่าน pointer โดยใช้ตัวดำเนินการลูกศร (
->
) - วิธีนี้ทำให้สามารถจัดสรรหน่วยความจำแบบ dynamic ได้ ซึ่งเปิดโอกาสให้สร้าง data structure ตอนจังหวะ application runtime ได้ หรือ สามารถเรียกใช้จากนอก scope ได้
- เราสามารถใช้ pointer arithmetic ได้ โดยการส่ง pointer ไปยัง function เพื่อแก้ไขข้อมูลต้นฉบับได้ (pass by reference) หรืออาจมี pointer หลายตัวชี้ไปยัง instance ตัวเดียวกันได้
Point *pPt = new Point;
pPt->x = 10;
pPt->y = 20;
Point *pPt2 = pPt; // Both pointers refer to the same memory location
pPt2->x = 30;
cout << "pPt->x: " << pPt->x << endl; // Output: 30
เพราะฉะนั้นขึ้นอยู่กับ use case ที่ใช้
- หากต้องการจัดการ struct แบบส่งต่อข้อมูลไปจัดการต่อ (เช่นการส่งเข้า function) ก็แนะนำให้ใช้เป็นท่าของ pointer
- หากต้องการ copy struct เพื่อนำค่าไปจัดการต่อกับตัวแปรอื่น ก็แนะนำให้ใช้ท่า struct ทั่วไปได้