Язык Си не является объектно-ориентированным языком. И значит все что будет описано ниже это костыли и велосипеды.
ООП включает в себя три столпа: инкапсуляция, наследование, полиморфизм. Ниже я покажу как этих вещей можно добиться в С.
Инкапсуляция
подразумевает скрытие данных от разработчика. В ООП языках мы обычно скрываем поля класса, а для доступа к ним пишем сеттеры и геттеры. Для скрытия данных в Си, существует ключевое слово static, которое, помимо других своих назначений, ограничивает видимость переменной (функции, структуры) одним файлом.
Пример:
//foo.c static void foo1 () { puts("foo1"); } void foo2 () { puts("foo2"); } //main.c #include <stdio.h> int main() { foo1(); foo2(); return 0; }
Компилятор выдает ошибку
[main.c:(.text+0x1b): undefined reference to `foo1' collect2.exe: error: ld returned 1 exit status]
Имея такую возможность, можно разделить public и private данные по разным файлам, а в структуре хранить только указатель на приватные данные. Понадобится две структуры: одна приватная, а вторая с методами для работы и указателем на приватную. Чтобы вызывать функции на объекте, договоримся первым параметром передавать указатель на структуру, которая ее вызывает.
Объявим структуру с сеттерами, геттерами и указателем на приватное поле, а также функции, которые будут создавать структуру и удалять.
//point2d.h typedef struct point2D { void *prvtPoint2D; int (*getX) (struct point2D*); void (*setX)(struct point2D*, int); //... } point2D; point2D* newPoint2D(); void deletePoint2D(point2D*);
Здесь будет инициализироваться приватное поле и указатели на функции, чтобы с этой структурой можно было работать.
//point2d.c #include <stdlib.h> #include "point2d.h" typedef struct private { int x; int y; } private; static int getx(struct point2D*p) { return ((struct private*)(p->prvtPoint2D))->x; } static void setx(struct point2D *p, int val) { ((struct private*)(p->prvtPoint2D))->x = val; } point2D* newPoint2D() { point2D* ptr; ptr = (point2D*) malloc(sizeof(point2D)); ptr -> prvtPoint2D = malloc(sizeof(private)); ptr -> getX = &getx; ptr -> setX = &setx; // .... return ptr; }
Теперь, работа с этой структурой, может осуществляться с помощью сеттеров и геттеров.
// main.c #include <stdio.h> #include "point2d.h" int main() { point2D *point = newPoint2D(); int p = point->getX(point); point->setX(point, 42); p = point->getX(point); printf("p = %d\n", p); deletePoint2D(point); return 0; }
Как было показано выше, в «конструкторе» создаются две структуры, и работа с private полями ведется через функции. Конечно этот вариант не идеальный хотя бы потому, что никто не застрахован от присвоения приватной структуре null-указателя. Тем не менее, оставить один указатель лучше, чем хранить все данные в паблик структуре.
Наследование
как механизм языка не предусмотрено, поэтому тут без костылей никак не обойтись. Решение, которое приходит в голову — это просто объявить структуру внутри структуры. Но для того чтобы иметь возможность обращаться к ее полям напрямую, в C11 есть возможность объявлять анонимные структуры. Их поддерживает как gcc, так и компилятор от microsoft. Выглядит это вот так.
typedef struct point2D { int x,y; } typedef struct point3D { struct point2D; int z; } point3D; #include <stdio.h> #include "point3d.h" int main() { point3D *point = newPoint3D(); int p = point->x; printf("p = %d\n", p); return 0; }
Компилировать надо с флагом -fms-extensions. Таким образом, становится возможным доступ к полям структуры в обход ее имени.
Но надо понимать, что анонимными могут быть только структуры и перечисления, но мы не можем объявлять анонимными примитивные типы данных.
Полиморфизм
В языках программирования и теории типов полиморфизмом называется способность функции обрабатывать данные разных типов. И такую возможность предоставляет ключевое слово _Generic, которое было введено в С11. Но стоит оговориться, что не все версии gcc его поддерживают. В _Generic передаются пары тип-значение, а при компиляции они транслируются в нужное значение. В общем, лучше один раз увидеть.
Создадим «функцию», которая будет определять тип структуры, переданной в нее, и возвращать ее имя в виде строки.
//points.h #define typename(x) _Generic((x), \ point3D : "point3D", \ point2D : "point2D", \ point3D * : "pointer to point3D", \ point2D * : "pointer to point2D" \ ) //main.c int main() { point3D *point = newPoint3D(); puts(typename(point)); return 0; }
Здесь видно, что в зависимости от типа данных будет возвращаться разное значение. А раз _Generic возвращает какое-то значение, так почему бы ему не вернуть указатель на функцию, тогда можно заставить одну и ту же «функцию» работать с разными типами данных.
//points.h double do2D(point2D *p); double do3D(point3D *p); #define doSomething(X) _Generic((X), \ point3D* : do3D, \ point2D* : do2D \ ) (X) //main.c int main() { point3D *point = newPoint3D(); printf("d = %f\n", doSomething(point)); return 0; }
Теперь одну и туже функцию можно использовать с разными структурами.
Статьи по теме:
habrahabr.ru/post/205570
habrahabr.ru/post/154811
ссылка на оригинал статьи http://habrahabr.ru/post/263547/
Добавить комментарий