Язык Си не является объектно-ориентированным языком. И значит все что будет описано ниже это костыли и велосипеды.
ООП включает в себя три столпа: инкапсуляция, наследование, полиморфизм. Ниже я покажу как этих вещей можно добиться в С.
Инкапсуляция
подразумевает скрытие данных от разработчика. В ООП языках мы обычно скрываем поля класса, а для доступа к ним пишем сеттеры и геттеры. Для скрытия данных в Си, существует ключевое слово 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;
}
Теперь одну и туже функцию можно использовать с разными структурами.
Статьи по теме:
http://ift.tt/1CXImUr
http://ift.tt/1CXIpj2
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий