Наверное, стоило бы ребенка на велосипеде с квадратным колесом ставить сейчас. Сегодня будем фантазировать на тему ООП на С.
Итак, что такое ООП? Это паттерн (шаблон, архитектура приложения), построенный на идее, что все - объект. А объект, в свою очередь, это данные и действия по обработке этих данных. Будь то автомобиль: текущие координаты - это данные, доступно действие "переместить". Если смотреть на ООП в философском смысле, то открываются три волшебных заповеди ООП: инкапсуляция, полиморфизм, наследование. В С ничего для этого нет: единственные доступные агрегаты, структуры, никак не скрывают свои члены.
Наследование
Общеизвестно, что члены объекта структуры располагаются один за другим ( + выравнивание, почему рекомендуется выкладывать от меньших по размеру членов к большим), потому мы можем на некую "сырую" область памяти "наложить" тип структуры, разметив этим области, которые относятся к отдельным членам. Это мы делаем приведением указателя с void на наш тип. Или же наоборот, чтобы записать структуру в файл, нам выгодно представить ее отдельными байтами, для этого мы приводим указатель на структуру к char и записываем в файл sizeof(имя_типа) количество байт, начиная с того-самого указателя
Что нам даст это знание? А то, что мы можем включать структуру (не указатель, а именно объект) в другую структуру, таким образом наследуя все данные первой структуры. Теперь у нас есть два шаблона: исходная структура и исходная структура + новые данные. А имея указатель на начало такой комплексной структуры (нового типа), мы можем приводить его в тот, или иной тип.
struct Base { // ... Данные базового типа }; struct Derived { struct Base header; //... Данные наследника } Base; // Пример использования: struct Derived *der = (struct Derived*)malloc( sizeof(struct Derived) ); // Память выделяется единожды! struct Base *bas = (struct Base*)der; // Можем работать с объектом как с базовым - через bas // или как с наследником - через der. // Ну и, конечно же, к членам Base через der можем // обращаться как der->header.имя_члена
Полиморфизм
Добавим в базовый класс поле типа, проверив которое, мы в рантайме сможем узнать истинный тип переменной. Таким образом, внутри для обеспечения полиморфизма, в функциях достаточно сделать любую удобную конструкцию условного перехода, а аргументом сделать базовый тип
void DoPolymorphicAction(Base *baseRef) { switch(baseRef->type) { //... Смотрим на тип, делаем действие } }
ЗЫ
В заключение, маленький пример. Можно было бы избежать дублирования логики в конструкторе наследника, это потребует, как вариант, конструктор копирования, но пусть это будет твоим "домашним заданием" =)
enum ObjType { /* ... , */ T_DERIVED }; // Базовый класс typedef struct tagBase { ObjType type; // Метаданные // Данные базового класса struct tagBase *parent; } Base; // Класс-наследник typedef struct tagDerived { Base header; // Унаследованные данные базового класса char *name; } Derived; // Конструкторы Base* CreateBase(Base *parent) { Base *newObj = (Base*)malloc( sizeof(Base) ); newObj->parent = parent; return newObj; } Derived* CreateDerived(Base *parent, const char *name) { Derived *newObj = (Derived*)malloc( sizeof(Derived) ); newObj->header.parent = parent; newObj->name = (char*)malloc( sizeof(char) * (strlen(name) + 1) ); strcpy(newObj->name, name); return newObj; } // Универсальный деструктор void ReleaseNode(Base **base) { switch( (*base)->type) { //... case T_DERIVED: free( ( (Derived*)(*base) )->name ); break; }; free( *base ); base = NULL; }
Комментариев нет:
Отправить комментарий