
Наверное, стоило бы ребенка на велосипеде с квадратным колесом ставить сейчас. Сегодня будем фантазировать на тему ООП на С.
Итак, что такое ООП? Это паттерн (шаблон, архитектура приложения), построенный на идее, что все - объект. А объект, в свою очередь, это данные и действия по обработке этих данных. Будь то автомобиль: текущие координаты - это данные, доступно действие "переместить". Если смотреть на ООП в философском смысле, то открываются три волшебных заповеди ООП: инкапсуляция, полиморфизм, наследование. В С ничего для этого нет: единственные доступные агрегаты, структуры, никак не скрывают свои члены.
Наследование
Общеизвестно, что члены объекта структуры располагаются один за другим ( + выравнивание, почему рекомендуется выкладывать от меньших по размеру членов к большим), потому мы можем на некую "сырую" область памяти "наложить" тип структуры, разметив этим области, которые относятся к отдельным членам. Это мы делаем приведением указателя с 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;
}
Комментариев нет:
Отправить комментарий