Menu
Coddy logo textTech

Определение непрозрачных структур

Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 18 из 61.

Теперь, когда вы понимаете концепцию непрозрачных указателей (opaque pointers), давайте посмотрим, как завершить этот паттерн. Заголовочный файл объявляет о существовании типа, но именно в исходном файле определяется его фактическое содержимое.

Вот полная настройка. Сначала заголовочный файл только с опережающим объявлением:

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

typedef struct Counter Counter;  // Неполный тип

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

#endif

Затем исходный файл содержит само определение структуры:

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

struct Counter {
    int value;      // Скрыто извне!
    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);
}

Ключевой момент: struct Counter { ... } появляется только в .c файле. Любой файл, который включает counter.h, знает, что Counter существует, но не может видеть value или step. Попытка написать c->value в main.c приведет к ошибке компиляции — члены структуры действительно невидимы.

Это разделение является основой инкапсуляции в C. Следующим шагом является обеспечение контролируемого доступа через функции геттеры и сеттеры.

challenge icon

Задание

Легко

Давайте создадим модуль Timer, который демонстрирует паттерн непрозрачного указателя (opaque pointer). Вы попрактикуетесь в скрытии определения структуры внутри исходного файла, делая её внутренние члены полностью невидимыми для любого кода, использующего ваш модуль.

Вы создадите три файла:

  • timer.h: Это ваш публичный интерфейс. Объявите непрозрачный тип Timer, используя только предварительное объявление (forward declaration) — здесь не должно быть тела структуры! Также объявите две функции: create_timer (принимает целое число для секунд и возвращает указатель на Timer) и free_timer (принимает указатель на Timer и выполняет очистку). Используйте include guards с символом TIMER_H.
  • timer.c: Здесь хранится секрет. Определите саму struct Timer с двумя скрытыми членами: int seconds и int running (используйте 1 для запущенного состояния, 0 для остановленного). Реализуйте конструктор для выделения памяти под структуру, инициализируйте seconds предоставленным значением и установите running в 1. Реализуйте деструктор для освобождения памяти.
  • main.c: Используйте ваш модуль Timer, чтобы создать таймер и подтвердить, что он был успешно создан. Поскольку члены структуры скрыты, вы не можете получить к ним прямой доступ — в этом и заключается весь смысл! Просто выведите сообщение, подтверждающее создание, затем освободите таймер и выведите сообщение об очистке.

Вы получите на вход одно значение: количество секунд (целое число) для инициализации таймера.

В вашем основном файле создайте таймер с предоставленным значением секунд. Выведите сообщение о создании, затем освободите таймер и выведите подтверждение.

Выведите результат в следующем формате:

Timer created
Timer freed

Ключевая идея здесь заключается в том, что main.c может работать с указателями на Timer, но не имеет возможности напрямую обращаться к seconds или running. Попытка написать t->seconds в main.c вызовет ошибку компиляции, так как тело структуры определено только в timer.c. Это и есть истинная инкапсуляция — детали реализации полностью скрыты от пользователей вашего модуля.

Шпаргалка

Паттерн «непрозрачный указатель» (opaque pointer) отделяет объявление типа от его определения, скрывая детали реализации.

Заголовочный файл содержит только предварительное объявление:

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

typedef struct Counter Counter;  // Незавершенный тип

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

#endif

Файл исходного кода содержит фактическое определение структуры:

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

struct Counter {
    int value;      // Скрыто извне
    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);
}

Определение структуры появляется только в файле .c. Файлы, которые подключают заголовочный файл, знают о существовании типа, но не могут получить прямой доступ к его полям. Это обеспечивает истинную инкапсуляцию в языке C.

Попробуйте сами

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

int main() {
    int seconds;
    scanf("%d", &seconds);
    
    // TODO: Создать таймер с предоставленным значением секунд
    
    // TODO: Вывести "Timer created"
    
    // TODO: Освободить таймер
    
    // TODO: Вывести "Timer freed"
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming