Menu
CoddyTech

Định nghĩa struct mờ

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

Bây giờ bạn đã hiểu khái niệm con trỏ không trong suốt (opaque pointer), hãy cùng xem cách hoàn thiện mẫu này. Tệp header khai báo rằng một kiểu dữ liệu tồn tại, nhưng tệp mã nguồn mới là nơi bạn định nghĩa nội dung thực sự bên trong.

Đây là phần thiết lập hoàn chỉnh. Đầu tiên là tệp tiêu đề chỉ có khai báo trước:

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

typedef struct Counter Counter;  // Kiểu dữ liệu chưa hoàn chỉnh

Counter *create_counter(int start);
void free_counter(Counter *c);

#endif

Sau đó, tệp mã nguồn chứa định nghĩa struct thực tế:

// counter.c
#include "counter.h"
#include <stdlib.h>

struct Counter {
    int value;      // Ẩn với bên ngoài!
    int step;
};

Counter *create_counter(int start) {
    Counter *c = malloc(sizeof(Counter));
    c->value = start;
    c->step = 1;
    return c;
}

void free_counter(Counter *c) {
    free(c);
}

Điểm mấu chốt: struct Counter { ... } chỉ xuất hiện trong tệp .c. Bất kỳ tệp nào có counter.h đều biết Counter tồn tại nhưng không thể thấy value hoặc step. Nếu viết c->value trong main.c, trình biên dịch sẽ báo lỗi: các thành viên này thực sự không thể nhìn thấy.

Sự tách biệt này là nền tảng của tính đóng gói trong C. Bước tiếp theo là cung cấp quyền truy cập có kiểm soát thông qua các hàm getter và setter.

challenge icon

Thử thách

Dễ

Hãy tạo một module Timer minh họa mẫu con trỏ mờ đục. Bạn sẽ luyện cách giấu định nghĩa cấu trúc bên trong tệp nguồn, khiến các thành viên bên trong hoàn toàn vô hình với mọi code sử dụng module của bạn.

Bạn sẽ tạo ba tệp:

  • timer.h: Đây là giao diện công khai. Khai báo kiểu Timer mờ đục chỉ bằng một khai báo trước, không viết phần thân cấu trúc ở đây! Đồng thời khai báo hai hàm: create_timer (nhận số nguyên chỉ số giây và trả về con trỏ Timer) và free_timer (nhận con trỏ Timer và giải phóng tài nguyên). Dùng include guard với ký hiệu TIMER_H.
  • timer.c: Đây là nơi chứa phần bí mật. Định nghĩa struct Timer thực sự với hai thành viên được ẩn: int seconds và int running (dùng 1 khi đang chạy, 0 khi đã dừng). Triển khai hàm khởi tạo để cấp phát cấu trúc, khởi tạo seconds bằng giá trị được cung cấp và đặt running thành 1. Triển khai hàm hủy để giải phóng bộ nhớ.
  • main.c: Dùng module Timer để tạo bộ hẹn giờ và xác nhận tạo thành công. Vì các thành viên của cấu trúc bị ẩn, bạn không thể truy cập trực tiếp. Đó chính là mục đích! Chỉ cần in thông báo xác nhận đã tạo, sau đó giải phóng bộ hẹn giờ và in thông báo xác nhận giải phóng.

Bạn sẽ nhận một đầu vào: số giây (một số nguyên) dùng để khởi tạo bộ hẹn giờ.

Trong tệp chính, hãy tạo bộ hẹn giờ với giá trị số giây được cung cấp. In thông báo tạo thành công, sau đó giải phóng bộ hẹn giờ và in thông báo xác nhận.

In đầu ra theo định dạng sau:

Timer created
Timer freed

Điểm mấu chốt là main.c có thể làm việc với con trỏ Timer nhưng không có cách nào truy cập trực tiếp vào seconds hoặc running. Nếu thử viết t->seconds trong main.c, trình biên dịch sẽ báo lỗi vì phần thân cấu trúc chỉ được định nghĩa trong timer.c. Đây là tính đóng gói thực sự: các chi tiết triển khai hoàn toàn bị ẩn với người dùng module.

Tự làm thử

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

int main() {
    int seconds;
    scanf("%d", &seconds);
    
    // TODO: Tạo bộ hẹn giờ với giá trị giây đã cho
    
    // TODO: In ra "Timer created"
    
    // TODO: Giải phóng bộ hẹn giờ
    
    // TODO: In ra "Timer freed"
    
    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