Skip to main content

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 ชิ้น โดยแต่ละชิ้นจะเก็บข้อมูลชนิดเดียวกับ Struct Student หรือก็คือ 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
  1. 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 ทั่วไปได้