Псевдо ООП в C

от автора


Язык Си не является объектно-ориентированным языком. И значит все что будет описано ниже это костыли и велосипеды.
ООП включает в себя три столпа: инкапсуляция, наследование, полиморфизм. Ниже я покажу как этих вещей можно добиться в С.

Инкапсуляция
подразумевает скрытие данных от разработчика. В ООП языках мы обычно скрываем поля класса, а для доступа к ним пишем сеттеры и геттеры. Для скрытия данных в Си, существует ключевое слово 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/