Определение непрозрачных структур
Часть раздела 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. Следующим шагом является обеспечение контролируемого доступа через функции геттеры и сеттеры.
Задание
ЛегкоДавайте создадим модуль 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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга