Menu
CoddyTech

Con trỏ hàm trong struct

Thuộc phần Lập trình hướng đối tượng trong hành trình C của Coddy. Bài học 38 trên 61.

Cho đến nay, chúng ta đã dùng con trỏ hàm như các biến độc lập hoặc truyền chúng làm đối số. Bước tiếp theo để tiến tới tính đa hình là nhúng trực tiếp con trỏ hàm vào một struct, nhờ đó struct có hành vi riêng.

Khi một struct chứa con trỏ hàm (function pointer), mỗi instance có thể giữ một hàm khác nhau. Điều này có nghĩa là hai đối tượng cùng kiểu có thể hoạt động khác nhau khi bạn gọi con trỏ hàm của chúng:

typedef void (*PrintFunc)(const char*);

typedef struct {
    const char* name;
    PrintFunc print;  // Con trỏ hàm (function pointer) là một thành viên
} Printer;

struct Printer hiện có hai thành phần: dữ liệu (name) và hành vi (print). Để sử dụng struct này, bạn gán một hàm cho con trỏ trong quá trình khởi tạo:

void loud_print(const char* msg) {
    printf("!!! %s !!!\n", msg);
}

int main() {
    Printer p;
    p.name = "Alert";
    p.print = loud_print;
    
    p.print("Hello");  // Đầu ra: !!! Hello !!!
    return 0;
}

Hãy chú ý cách ta gọi hàm (function) thông qua struct: p.print("Hello"). Cách này trông rất giống việc gọi phương thức trên một đối tượng trong các ngôn ngữ khác. Struct chứa cả dữ liệu lẫn hàm thao tác trên dữ liệu đó.

Mẫu này là nền tảng của tính đa hình trong C: các instance khác nhau có thể được gán những hàm (function) khác nhau, tạo ra những hành vi khác nhau từ cùng một kiểu struct.

challenge icon

Thử thách

Dễ

Hãy cùng xây dựng một hệ thống Notifier minh họa cách các cấu trúc có thể mang hành vi riêng thông qua con trỏ hàm.

Bạn sẽ tạo hai tệp để sắp xếp code:

  • notifier.h: Định nghĩa một kiểu con trỏ hàm có tên là NotifyFunc, nhận tham số const char* và không trả về giá trị. Sau đó, định nghĩa một cấu trúc Notifier chứa name (kiểu const char*) và con trỏ hàm notify có kiểu NotifyFunc.
  • main.c: Nạp tệp tiêu đề và triển khai hai hàm thông báo:
    • alert_notify: in thông báo theo định dạng: [ALERT] message
    • info_notify: in thông báo theo định dạng: [INFO] message
    Sau đó, tạo một instance Notifier, gán hàm phù hợp dựa trên đầu vào và gọi hàm thông báo thông qua con trỏ hàm của cấu trúc.

Chương trình nhận hai đầu vào: loại thông báo (1 cho cảnh báo, 2 cho thông tin) và thông báo cần hiển thị.

Dựa trên loại thông báo, gán hàm tương ứng cho thành viên notify của notifier, sau đó gọi hàm thông qua cấu trúc để hiển thị thông báo.

Ví dụ đầu ra khi loại là 1 và thông báo là "System starting":

[ALERT] System starting

Ví dụ đầu ra khi loại là 2 và thông báo là "All systems normal":

[INFO] All systems normal

Nhớ dùng include guard trong tệp tiêu đề và gọi hàm thông qua thành viên của cấu trúc: n.notify(message)

Tự làm thử

#include <stdio.h>
#include "notifier.h"

// TODO: Triển khai hàm (function) alert_notify
// Cần in ra: [ALERT] message


// TODO: Triển khai hàm info_notify
// Cần in ra: [INFO] message


int main() {
    int type;
    char message[256];
    
    // Đọc đầu vào
    scanf("%d", &type);
    getchar(); // Đọc bỏ ký tự xuống dòng
    fgets(message, sizeof(message), stdin);
    
    // Xóa ký tự xuống dòng ở cuối message nếu có
    int len = 0;
    while (message[len] != '\0') len++;
    if (len > 0 && message[len-1] == '\n') message[len-1] = '\0';
    
    // TODO: Tạo một instance của Notifier
    
    
    // TODO: Dựa vào type (1 cho alert, 2 cho info),
    // gán hàm phù hợp cho thành viên notify của notifier
    
    
    // TODO: Gọi hàm thông báo qua con trỏ hàm của struct
    // Dùng: n.notify(message)
    
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C online