L8. Kiểu Cấu Trúc (Struct)

1. Đặt Vấn Đề

1.1. Vấn Đề Quản Lý Thông Tin

Bài toán: Quản lý thông tin sinh viên

Thông tin 1 sinh viên:

  • MSSV: kiểu chuỗi
  • Tên SV: kiểu chuỗi
  • Ngày sinh: kiểu chuỗi
  • Giới tính: ký tự
  • Điểm Toán, Lý, Hóa: số thực

Yêu cầu:

  • Lưu thông tin cho N sinh viên?
  • Truyền thông tin N sinh viên vào một hàm?

1.2. Cách Tiếp Cận Truyền Thống

Khai báo biến cho 1 sinh viên:

char mssv[7];          // "0012078"
char hoten[30];        // "Nguyen Van A"
char ntns[8];          // "29/12/82"
char phai;             // 'N' ⟷ Nam, 'F' ⟷ Nữ
float toan, ly, hoa;   // 8.5, 9.0, 10.0

Truyền cho hàm:

void xuat(char mssv[], char hoten[], char ntns[], 
          char phai, float toan, float ly, float hoa);

Nhận xét:

  • ❌ Đặt tên biến khó khăn và khó quản lý
  • ❌ Truyền tham số cho hàm quá nhiều
  • ❌ Tìm kiếm, sắp xếp, sao chép khó khăn
  • ❌ Tốn nhiều bộ nhớ

Giải pháp:

Gom những thông tin của cùng 1 sinh viên thành một kiểu dữ liệu mới → Kiểu Struct

2. Khái Niệm Kiểu Cấu Trúc

2.1. Định Nghĩa

Struct (Structure):

Kiểu dữ liệu do người dùng định nghĩa, cho phép gom nhóm các biến có kiểu dữ liệu khác nhau thành một đơn vị.

graph TD A[Struct SINHVIEN] --> B[mssv: char] A --> C[hoten: char] A --> D[ngaysinh: char] A --> E[phai: char] A --> F[toan: float] A --> G[ly: float] A --> H[hoa: float]

2.2. Khai Báo Kiểu Cấu Trúc

Cú pháp:

struct <tên_kiểu_cấu_trúc> {
    <kiểu_dữ_liệu> <tên_thành_phần_1>;
    <kiểu_dữ_liệu> <tên_thành_phần_2>;
    ...
    <kiểu_dữ_liệu> <tên_thành_phần_n>;
};

Ví dụ 1: Điểm trong mặt phẳng

struct DIEM {
    int x;
    int y;
};

Ví dụ 2: Phân số

struct PHANSO {
    int tu;
    int mau;
};

Ví dụ 3: Sinh viên

struct SINHVIEN {
    char mssv[10];
    char hoten[30];
    char ngaysinh[11];
    char phai;
    float toan, ly, hoa;
};

3. Khai Báo Biến Cấu Trúc

3.1. Khai Báo Không Tường Minh

Cú pháp:

struct <tên_kiểu_cấu_trúc> {
    <kiểu_dữ_liệu> <tên_thành_phần_1>;
    ...
    <kiểu_dữ_liệu> <tên_thành_phần_n>;
};

struct <tên_kiểu_cấu_trúc> <tên_biến>;

Ví dụ:

struct DIEM {
    int x;
    int y;
};

// Khai báo biến
struct DIEM diem1, diem2;  // C++ có thể bỏ từ khóa struct

3.2. Sử Dụng typedef

Cú pháp:

typedef struct {
    <kiểu_dữ_liệu> <tên_thành_phần_1>;
    ...
    <kiểu_dữ_liệu> <tên_thành_phần_n>;
} <tên_kiểu_cấu_trúc>;

<tên_kiểu_cấu_trúc> <tên_biến>;

Ví dụ:

typedef struct {
    int x;
    int y;
} DIEM;

// Khai báo biến
DIEM diem1, diem2;  // Không cần từ khóa struct

3.3. Khai Báo Tường Minh

Cú pháp:

struct <tên_kiểu_cấu_trúc> {
    <kiểu_dữ_liệu> <tên_thành_phần_1>;
    ...
    <kiểu_dữ_liệu> <tên_thành_phần_n>;
} <tên_biến>;

Ví dụ:

struct DIEM {
    int x;
    int y;
} diem1, diem2;

3.4. Khởi Tạo Biến Cấu Trúc

Cách 1: Khởi tạo khi khai báo

struct DIEM {
    int x;
    int y;
} diem1 = {2912, 1706}, diem2;

Cách 2: Khởi tạo với typedef

typedef struct {
    int x;
    int y;
} DIEM;

DIEM diem1 = {10, 20};
DIEM diem2 = {30, 40};

Cách 3: Khởi tạo từng thành phần

DIEM diem1;
diem1.x = 10;
diem1.y = 20;

4. Truy Xuất Dữ Liệu Kiểu Cấu Trúc

4.1. Toán Tử Chấm (Dot Operator)

Đặc điểm:

  • Không thể truy xuất trực tiếp biến cấu trúc
  • Phải thông qua toán tử thành phần cấu trúc (dot operator): .

Cú pháp:

<tên_biến_cấu_trúc>.<tên_thành_phần>

Ví dụ:

struct DIEM {
    int x;
    int y;
} diem1;

// Truy xuất
cout << diem1.x << " " << diem1.y;

// Gán giá trị
diem1.x = 100;
diem1.y = 200;

// Nhập từ bàn phím
cin >> diem1.x >> diem1.y;

4.2. Gán Dữ Liệu Kiểu Cấu Trúc

Cách 1: Gán cả cấu trúc

<biến_cấu_trúc_đích> = <biến_cấu_trúc_nguồn>;
struct DIEM {
    int x, y;
} diem1 = {2912, 1706}, diem2;

diem2 = diem1;  // Sao chép toàn bộ
// diem2.x = 2912, diem2.y = 1706

Cách 2: Gán từng thành phần

<biến_cấu_trúc_đích>.<tên_thành_phần> = <giá_trị>;
diem2.x = diem1.x;
diem2.y = diem1.y * 2;

5. Cấu Trúc Phức Tạp

5.1. Cấu Trúc Lồng Nhau

Thành phần của cấu trúc là cấu trúc khác:

struct DIEM {
    int x;
    int y;
};

struct HINHCHUNHAT {
    DIEM traitren;   // Góc trái trên
    DIEM phaiduoi;   // Góc phải dưới
} hcn1;

Truy xuất:

hcn1.traitren.x = 2912;
hcn1.traitren.y = 1706;
hcn1.phaiduoi.x = 3000;
hcn1.phaiduoi.y = 2000;

Minh họa:

Hình chữ nhật:
┌──────────────────┐ ← (traitren.x, traitren.y)
│                  │
│                  │
│                  │
└──────────────────┘ ← (phaiduoi.x, phaiduoi.y)

5.2. Cấu Trúc Đệ Quy (Tự Trỏ)

Cấu trúc có con trỏ trỏ đến chính kiểu cấu trúc đó:

struct PERSON {
    char hoten[30];
    PERSON *father;   // Con trỏ đến cha
    PERSON *mother;   // Con trỏ đến mẹ
};

Ứng dụng: Danh sách liên kết

struct NODE {
    int value;
    NODE *pNext;  // Con trỏ đến node tiếp theo
};
┌───┬────┐   ┌───┬────┐   ┌───┬────┐
│ 5 │ ●──┼──→│ 8 │ ●──┼──→│ 3 │NULL│
└───┴────┘   └───┴────┘   └───┴────┘

6. Các Lưu Ý Về Cấu Trúc

6.1. Kích Thước Cấu Trúc

Kích thước cấu trúc ≠ Tổng kích thước các thành phần

Do vấn đề căn chỉnh bộ nhớ (memory alignment).

Ví dụ:

struct B1 {
    int a;      // 4 bytes
    double c;   // 8 bytes
    int b;      // 4 bytes
};

sizeof(B1) = 24  // Không phải 16!

Cấu trúc bộ nhớ:

┌────┬────┬────────────┬────┬────┐
│ a  │pad │     c      │ b  │pad │
└────┴────┴────────────┴────┴────┘
  4    4        8         4    4  = 24 bytes

Tối ưu hóa:

struct B2 {
    int a;      // 4 bytes
    int b;      // 4 bytes
    double c;   // 8 bytes
};

sizeof(B2) = 16  // Tối ưu hơn!
┌────┬────┬────────────┐
│ a  │ b  │     c      │
└────┴────┴────────────┘
  4    4        8       = 16 bytes

6.2. Các Lưu Ý Khác

1. Kiểu và biến:

  • Kiểu cấu trúc: Khuôn mẫu (template)
  • Biến cấu trúc: Thực thể (instance)

2. Trong C++:

  • Có thể bỏ từ khóa struct khi khai báo biến

3. Nhập số thực:

  • Phải nhập thông qua biến trung gian
struct DIEM {
    float x, y;
} d1;

float temp;
cin >> temp;
d1.x = temp;

7. Mảng Cấu Trúc

7.1. Khai Báo Mảng Cấu Trúc

Tương tự như mảng thông thường:

struct DIEM {
    int x;
    int y;
};

DIEM mang1[20];  // Mảng 20 điểm

DIEM mang2[10] = {
    {3, 2}, 
    {4, 4}, 
    {2, 7}
};  // Khởi tạo 3 điểm đầu

7.2. Truy Xuất Mảng Cấu Trúc

DIEM arr[10];

// Truy xuất phần tử thứ i
arr[i].x = 10;
arr[i].y = 20;

// Nhập mảng
for (int i = 0; i < 10; i++) {
    cout << "Nhap diem thu " << i << ":\n";
    cout << "x = "; cin >> arr[i].x;
    cout << "y = "; cin >> arr[i].y;
}

// Xuất mảng
for (int i = 0; i < 10; i++) {
    cout << "(" << arr[i].x << ", " << arr[i].y << ")\n";
}

8. Truyền Cấu Trúc Cho Hàm

8.1. Truyền Tham Trị

Không thay đổi sau khi kết thúc hàm:

void xuat1(int x, int y) { 
    cout << "(" << x << ", " << y << ")";
}

void xuat2(DIEM diem) {
    cout << "(" << diem.x << ", " << diem.y << ")";
}

8.2. Truyền Tham Chiếu

Có thể thay đổi giá trị:

void xuat3(DIEM &diem) {
    cout << "(" << diem.x << ", " << diem.y << ")";
}

void nhap(DIEM &diem) {
    cout << "Nhap x: "; cin >> diem.x;
    cout << "Nhap y: "; cin >> diem.y;
}

8.3. Truyền Con Trỏ

void xuat4(DIEM *diem) {
    cout << "(" << diem->x << ", " << diem->y << ")";
}

void nhap2(DIEM *diem) {
    cout << "Nhap x: "; cin >> diem->x;
    cout << "Nhap y: "; cin >> diem->y;
}

9. Bài Tập Minh Họa

9.1. Cấu Trúc Sinh Viên

typedef struct {
    char ten[50];
    char mssv[10];
    char lop[5];
} SINHVIEN;

9.2. Nhập Danh Sách Sinh Viên

void NhapSinhVien(SINHVIEN dssv[], int thutu) {
    fflush(stdin);
    
    printf("Nhap ten sinh vien: ");
    gets(dssv[thutu].ten);
    
    fflush(stdin);
    printf("Nhap ma so sinh vien: ");
    gets(dssv[thutu].mssv);
    
    fflush(stdin);
    printf("Nhap lop cua sinh vien: ");
    gets(dssv[thutu].lop);
}

9.3. Xuất Danh Sách Sinh Viên

void XuatSinhVien(SINHVIEN dssv[], int thutu) {
    printf("Ten sinh vien: ");
    puts(dssv[thutu].ten);
    
    printf("Ma so sinh vien: ");
    puts(dssv[thutu].mssv);
    
    printf("Lop cua sinh vien: ");
    puts(dssv[thutu].lop);
}

9.4. Tìm Sinh Viên Theo Tên

int TimSVTheoTen(SINHVIEN dssv[], int soluongsv, char ten[]) {
    int vitritimthay = -1;
    
    for (int i = 0; i < soluongsv; i++) {
        if (strcmp(dssv[i].ten, ten) == 0) {
            vitritimthay = i;
            break;
        }
    }
    
    return vitritimthay;
}

9.5. Chương Trình Hoàn Chỉnh

#include <iostream>
#include <cstring>
using namespace std;

typedef struct {
    char ten[50];
    char mssv[10];
    char lop[5];
} SINHVIEN;

void NhapSinhVien(SINHVIEN &sv) {
    fflush(stdin);
    cout << "Nhap ten: "; gets(sv.ten);
    cout << "Nhap MSSV: "; gets(sv.mssv);
    cout << "Nhap lop: "; gets(sv.lop);
}

void XuatSinhVien(SINHVIEN sv) {
    cout << "Ten: " << sv.ten << endl;
    cout << "MSSV: " << sv.mssv << endl;
    cout << "Lop: " << sv.lop << endl;
}

int main() {
    SINHVIEN dssv[100];
    int n;
    
    cout << "Nhap so luong sinh vien: ";
    cin >> n;
    
    for (int i = 0; i < n; i++) {
        cout << "\nNhap thong tin sinh vien " << i + 1 << ":\n";
        NhapSinhVien(dssv[i]);
    }
    
    cout << "\n=== DANH SACH SINH VIEN ===\n";
    for (int i = 0; i < n; i++) {
        cout << "\nSinh vien " << i + 1 << ":\n";
        XuatSinhVien(dssv[i]);
    }
    
    return 0;
}

10. Bài Tập Bắt Buộc