Menu
CoddyTech

Trình bao bọc tổng quát

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 50 trên 61.

Bản thân void* có thể lưu trữ mọi loại dữ liệu, nhưng có một nhược điểm nghiêm trọng: bạn không biết nó đang chứa kiểu dữ liệu nào. Để tạo ra các vùng chứa tổng quát thực sự hữu ích, chúng ta cần ghép con trỏ dữ liệu với thông tin kiểu dữ liệu. Đây là lúc mẫu Generic Wrapper phát huy tác dụng.

Ý tưởng rất đơn giản: tạo một struct gộp void* với một enum để xác định kiểu dữ liệu được lưu trữ. Nhờ vậy, wrapper luôn biết cách diễn giải nội dung bên trong.

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;
typedef struct {
    void* data;
    DataType type;
} Wrapper;

Khi lưu trữ dữ liệu, bạn cấp phát bộ nhớ cho giá trị thực tế và ghi lại kiểu dữ liệu của nó. Khi truy xuất, bạn kiểm tra kiểu dữ liệu trước khi ép kiểu:

Wrapper wrap_int(int value) {
    int* p = malloc(sizeof(int));
    *p = value;
    return (Wrapper){ .data = p, .type = TYPE_INT };
}
void print_wrapper(Wrapper* w) {
    if (w->type == TYPE_INT) {
        printf("%d\n", *(int*)w->data);
    } else if (w->type == TYPE_STRING) {
        printf("%s\n", (char*)w->data);
    }
}

Mẫu này là nền tảng để xây dựng các vùng chứa generic an toàn kiểu trong C. enum đóng vai trò như một thẻ kiểu khi chạy, giúp code đưa ra quyết định an toàn về cách xử lý dữ liệu được lưu trữ.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống bao bọc tổng quát, an toàn về kiểu dữ liệu, có thể lưu số nguyên hoặc chuỗi, đồng thời theo dõi kiểu dữ liệu mà nó chứa để ta có thể truy xuất và in dữ liệu một cách an toàn sau này.

Bạn sẽ sắp xếp code trong ba tệp:

  • wrapper.h: Định nghĩa một enum DataType với các giá trị TYPE_INT và TYPE_STRING. Sau đó, định nghĩa một struct Wrapper chứa con trỏ dữ liệu void* cùng một trường DataType. Khai báo nguyên mẫu các hàm để tạo wrapper và in nội dung của chúng.
  • wrapper.c: Triển khai chức năng của wrapper:
    • wrap_int: nhận một số nguyên, cấp phát bộ nhớ cho số đó, lưu giá trị và trả về một Wrapper có thẻ kiểu dữ liệu phù hợp
    • wrap_string: nhận một chuỗi (char*), cấp phát bộ nhớ và sao chép chuỗi, rồi trả về một Wrapper có thẻ kiểu chuỗi
    • print_wrapper: kiểm tra thẻ kiểu dữ liệu và in dữ liệu phù hợp (số nguyên giữ nguyên, chuỗi giữ nguyên)
    • free_wrapper: giải phóng dữ liệu được cấp phát động bên trong wrapper
  • main.c: Kết hợp tất cả. Đọc ký hiệu kiểu dữ liệu (i cho số nguyên, s cho chuỗi), rồi đọc giá trị. Tạo wrapper phù hợp, in nội dung và giải phóng bộ nhớ.

Chương trình của bạn sẽ nhận hai đầu vào:

  1. Ký hiệu kiểu dữ liệu: i cho số nguyên hoặc s cho chuỗi
  2. Giá trị cần bao bọc

Ví dụ đầu ra khi đầu vào là i và 42:

42

Ví dụ đầu ra khi đầu vào là s và Hello:

Hello

Ví dụ đầu ra khi đầu vào là i và -100:

-100

Ví dụ đầu ra khi đầu vào là s và Generic Programming:

Generic Programming

Nhớ dùng include guard trong tệp tiêu đề. Khi sao chép chuỗi, hãy cấp phát đủ chỗ cho chuỗi và ký tự kết thúc null bằng strlen và strcpy.

Tự làm thử

#include <stdio.h>
#include <string.h>
#include "wrapper.h"

int main() {
    char type;
    scanf("%c", &type);
    getchar(); // Đọc bỏ ký tự xuống dòng
    
    Wrapper w;
    
    if (type == 'i') {
        int value;
        scanf("%d", &value);
        // TODO: Tạo wrapper cho số nguyên bằng wrap_int
        
    } else if (type == 's') {
        char str[256];
        fgets(str, sizeof(str), stdin);
        // Xóa ký tự xuống dòng ở cuối nếu có
        str[strcspn(str, "\n")] = '\0';
        // TODO: Tạo wrapper cho chuỗi bằng wrap_string
        
    }
    
    // TODO: In ra nội dung của wrapper bằng print_wrapper
    
    // TODO: Giải phóng bộ nhớ của wrapper bằng free_wrapper
    
    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