Объектная ориентация в C
Каким будет набор изящных препроцессорных хаков (совместимых с ANSI C89/ISO C90), которые обеспечивают некую некрасивую (но пригодную для использования) объектную ориентацию в C?
Я знаком с несколькими различными объектно-ориентированными языками, поэтому, пожалуйста, не отвечайте с ответами типа "Изучите C++!". Я прочитал " Объектно-ориентированное программирование с ANSI C" (будьте осторожны: формат PDF) и несколько других интересных решений, но в основном я заинтересован в вашем:-)!
Смотрите также Можете ли вы написать объектно-ориентированный код на C?
24 ответа
C Object System (COS) звучит многообещающе (все еще в альфа-версии). Он пытается свести к минимуму доступные концепции ради простоты и гибкости: единообразное объектно-ориентированное программирование, включая открытые классы, метаклассы, метаклассы свойств, универсальные шаблоны, мультиметоды, делегирование, владение, исключения, контракты и замыкания. Существует черновик документа (PDF), который описывает это.
Исключением в C является C89-реализация TRY-CATCH-FINALLY, найденная в других языках OO. Он поставляется с комплектом тестов и некоторыми примерами.
Оба от Лорана Денио, который много работает над ООП в Си.
Я бы посоветовал не использовать препроцессор (ab), чтобы попытаться сделать синтаксис C более похожим на синтаксис другого более объектно-ориентированного языка. На самом базовом уровне вы просто используете простые структуры в качестве объектов и передаете их указателями:
struct monkey
{
float age;
bool is_male;
int happiness;
};
void monkey_dance(struct monkey *monkey)
{
/* do a little dance */
}
Чтобы получить такие вещи, как наследование и полиморфизм, вы должны работать немного усерднее. Вы можете сделать наследование вручную, если первый член структуры будет экземпляром суперкласса, а затем вы можете свободно использовать указатели на базовые и производные классы:
struct base
{
/* base class members */
};
struct derived
{
struct base super;
/* derived class members */
};
struct derived d;
struct base *base_ptr = (struct base *)&d; // upcast
struct derived *derived_ptr = (struct derived *)base_ptr; // downcast
Чтобы получить полиморфизм (то есть виртуальные функции), вы используете указатели на функции и, необязательно, таблицы указателей на функции, также известные как виртуальные таблицы или vtables:
struct base;
struct base_vtable
{
void (*dance)(struct base *);
void (*jump)(struct base *, int how_high);
};
struct base
{
struct base_vtable *vtable;
/* base members */
};
void base_dance(struct base *b)
{
b->vtable->dance(b);
}
void base_jump(struct base *b, int how_high)
{
b->vtable->jump(b, how_high);
}
struct derived1
{
struct base super;
/* derived1 members */
};
void derived1_dance(struct derived1 *d)
{
/* implementation of derived1's dance function */
}
void derived1_jump(struct derived1 *d, int how_high)
{
/* implementation of derived 1's jump function */
}
/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
&derived1_dance, /* you might get a warning here about incompatible pointer types */
&derived1_jump /* you can ignore it, or perform a cast to get rid of it */
};
void derived1_init(struct derived1 *d)
{
d->super.vtable = &derived1_vtable;
/* init base members d->super.foo */
/* init derived1 members d->foo */
}
struct derived2
{
struct base super;
/* derived2 members */
};
void derived2_dance(struct derived2 *d)
{
/* implementation of derived2's dance function */
}
void derived2_jump(struct derived2 *d, int how_high)
{
/* implementation of derived2's jump function */
}
struct base_vtable derived2_vtable =
{
&derived2_dance,
&derived2_jump
};
void derived2_init(struct derived2 *d)
{
d->super.vtable = &derived2_vtable;
/* init base members d->super.foo */
/* init derived1 members d->foo */
}
int main(void)
{
/* OK! We're done with our declarations, now we can finally do some
polymorphism in C */
struct derived1 d1;
derived1_init(&d1);
struct derived2 d2;
derived2_init(&d2);
struct base *b1_ptr = (struct base *)&d1;
struct base *b2_ptr = (struct base *)&d2;
base_dance(b1_ptr); /* calls derived1_dance */
base_dance(b2_ptr); /* calls derived2_dance */
base_jump(b1_ptr, 42); /* calls derived1_jump */
base_jump(b2_ptr, 42); /* calls derived2_jump */
return 0;
}
И вот как вы делаете полиморфизм в C. Это не красиво, но это делает работу. Существуют некоторые проблемы, связанные с приведением указателей между базовым и производным классами, которые безопасны, если базовый класс является первым членом производного класса. Множественное наследование намного сложнее - в этом случае, чтобы найти между базовыми классами, отличными от первого, вам нужно вручную настроить указатели на основе правильных смещений, что действительно сложно и подвержено ошибкам.
Еще одна (хитрая) вещь, которую вы можете сделать, это изменить динамический тип объекта во время выполнения! Вы просто переназначаете ему новый указатель vtable. Вы даже можете выборочно изменять некоторые виртуальные функции, сохраняя другие, создавая новые гибридные типы. Просто будьте осторожны, создавая новую виртуальную таблицу вместо изменения глобальной виртуальной таблицы, иначе вы случайно затронете все объекты данного типа.
Однажды я работал с библиотекой C, которая была реализована таким образом, что мне показалось довольно элегантным. Они написали в C способ определения объектов, а затем наследуют их, чтобы они были такими же расширяемыми, как объект C++. Основная идея заключалась в следующем:
- У каждого объекта был свой файл
- Открытые функции и переменные определены в файле.h для объекта
- Закрытые переменные и функции были расположены только в файле.c
- Для "наследования" создается новая структура, в которой первый член структуры является объектом, наследуемым от
Наследование сложно описать, но в основном это было так:
struct vehicle {
int power;
int weight;
}
Тогда в другом файле:
struct van {
struct vehicle base;
int cubic_size;
}
Тогда вы могли бы создать фургон в памяти и использовать код, который знал только о транспортных средствах:
struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );
Он работал прекрасно, и файлы.h точно определяли, что вы должны делать с каждым объектом.
Если вы думаете о методах, вызываемых для объектов, как о статических методах, которые передают неявный 'this
"в функцию, это может сделать мышление ОО в Си легче.
Например:
String s = "hi";
System.out.println(s.length());
будет выглядеть так:
string s = "hi";
printf(length(s)); // pass in s, as an implicit this
Или что-то типа того.
Рабочий стол GNOME для Linux написан на объектно-ориентированном C и имеет объектную модель под названием " GObject", которая поддерживает свойства, наследование, полиморфизм, а также некоторые другие полезности, такие как ссылки, обработка событий (называемые "сигналы"), время выполнения. набор текста, личные данные и т. д.
Он включает в себя хаки препроцессора для выполнения таких вещей, как приведение типов в иерархии классов и т. Д. Вот пример класса, который я написал для GNOME (такие вещи, как gchar - это typedefs):
Внутри структуры GObject есть целое число GType, которое используется в качестве магического числа для системы динамической типизации GLib (вы можете привести всю структуру к "GType", чтобы найти ее тип).
Я делал подобные вещи в C, прежде чем я знал, что такое ООП.
Ниже приведен пример, который реализует буфер данных, который увеличивается по требованию, учитывая минимальный размер, приращение и максимальный размер. Эта конкретная реализация была основана на "элементах", то есть она была разработана для того, чтобы позволить подобную списку коллекцию любого типа C, а не только байтовый буфер переменной длины.
Идея состоит в том, что объект создается с помощью xxx_crt() и удаляется с помощью xxx_dlt(). Каждый из методов "member" принимает специально типизированный указатель для работы.
Таким образом, я реализовал связанный список, циклический буфер и ряд других вещей.
Должен признаться, я никогда не задумывался о том, как реализовать наследование с помощью этого подхода. Я полагаю, что какая-то смесь того, что предлагает Кивели, может быть хорошим путем.
dtb.c:
#include <limits.h>
#include <string.h>
#include <stdlib.h>
static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);
DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
DTABUF *dbp;
if(!minsiz) { return NULL; }
if(!incsiz) { incsiz=minsiz; }
if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz; }
if(minsiz+incsiz>maxsiz) { incsiz=maxsiz-minsiz; }
if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
memset(dbp,0,sizeof(*dbp));
dbp->min=minsiz;
dbp->inc=incsiz;
dbp->max=maxsiz;
dbp->siz=minsiz;
dbp->cur=0;
if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
return dbp;
}
DTABUF *dtb_dlt(DTABUF *dbp) {
if(dbp) {
free(dbp->dta);
free(dbp);
}
return NULL;
}
vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
if(!dbp) { errno=EINVAL; return -1; }
if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
if((dbp->cur + dtalen) > dbp->siz) {
void *newdta;
vint newsiz;
if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
else { newsiz=dbp->cur+dtalen; }
if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
dbp->dta=newdta; dbp->siz=newsiz;
}
if(dtalen) {
if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
else { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen); }
dbp->cur+=dtalen;
}
return 0;
}
static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
byte *sp,*dp;
for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
}
vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
byte textÝ501¨;
va_list ap;
vint len;
va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
else { va_start(ap,format); vsprintf(text,format,ap); va_end(ap); }
return dtb_adddta(dbp,xlt256,text,len);
}
vint dtb_rmvdta(DTABUF *dbp,vint len) {
if(!dbp) { errno=EINVAL; return -1; }
if(len > dbp->cur) { len=dbp->cur; }
dbp->cur-=len;
return 0;
}
vint dtb_reset(DTABUF *dbp) {
if(!dbp) { errno=EINVAL; return -1; }
dbp->cur=0;
if(dbp->siz > dbp->min) {
byte *newdta;
if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
free(dbp->dta); dbp->dta=null; dbp->siz=0;
return -1;
}
dbp->dta=newdta; dbp->siz=dbp->min;
}
return 0;
}
void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
return ((byte*)dbp->dta+(elmidx*elmlen));
}
dtb.h
typedef _Packed struct {
vint min; /* initial size */
vint inc; /* increment size */
vint max; /* maximum size */
vint siz; /* current size */
vint cur; /* current data length */
void *dta; /* data pointer */
} DTABUF;
#define dtb_dtaptr(mDBP) (mDBP->dta)
#define dtb_dtalen(mDBP) (mDBP->cur)
DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF *dtb_dlt(DTABUF *dbp);
vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint dtb_rmvdta(DTABUF *dbp,vint len);
vint dtb_reset(DTABUF *dbp);
void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);
PS: vint был просто typedef от int - я использовал его, чтобы напомнить мне, что его длина варьируется от платформы к платформе (для портирования).
Я думаю, что то, что написал Адам Розенфилд, является правильным способом выполнения ООП на C. Я хотел бы добавить, что он показывает реализацию объекта. Другими словами, фактическая реализация будет помещена в .c
файл, в то время как интерфейс будет помещен в заголовок .h
файл. Например, используя пример обезьяны выше:
Интерфейс будет выглядеть так:
//monkey.h
struct _monkey;
typedef struct _monkey monkey;
//memory management
monkey * monkey_new();
int monkey_delete(monkey *thisobj);
//methods
void monkey_dance(monkey *thisobj);
Вы можете увидеть в интерфейсе .h
файл вы только определяете прототипы. Затем вы можете скомпилировать часть реализации " .c
файл "в статическую или динамическую библиотеку. Это создает инкапсуляцию, а также вы можете изменить реализацию по своему усмотрению. Пользователь вашего объекта должен почти ничего не знать о его реализации. Это также ставит акцент на общий дизайн объекта.
По моему личному убеждению, oop - это способ концептуализации структуры вашего кода и возможности его повторного использования, который на самом деле не имеет ничего общего с теми другими вещами, которые добавляются в C++, такими как перегрузка или шаблоны. Да, это очень хорошие полезные функции, но они не представляют, что такое объектно-ориентированное программирование.
ffmpeg (инструментарий для обработки видео) написан на простом C (и ассемблере), но с использованием объектно-ориентированного стиля. Он полон структур с указателями на функции. Существует набор фабричных функций, которые инициализируют структуры с помощью соответствующих указателей "метода".
Моя рекомендация: будь проще. Одна из самых больших проблем, с которыми я сталкиваюсь, - это поддержка старого программного обеспечения (иногда старше 10 лет). Если код не простой, это может быть сложно. Да, можно написать очень полезный ООП с полиморфизмом на C, но это может быть трудно читать.
Я предпочитаю простые объекты, которые инкапсулируют некоторые четко определенные функциональные возможности. Отличным примером этого является GLIB2, например, хеш-таблица:
GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...
g_hash_table_remove(my_hash, some_key);
Ключи:
- Простая архитектура и дизайн
- Достигает базовой ООП инкапсуляции.
- Легко реализовать, читать, понимать и поддерживать
Для меня объектная ориентация в Си должна иметь следующие особенности:
Инкапсуляция и сокрытие данных (может быть достигнуто с помощью структур / непрозрачных указателей)
Наследование и поддержка полиморфизма (одиночное наследование может быть достигнуто с помощью структур - убедитесь, что абстрактная база не является экземпляром)
Функциональность конструктора и деструктора (нелегко достичь)
Проверка типов (по крайней мере, для пользовательских типов, так как C не применяет их)
Подсчет ссылок (или что-то для реализации RAII)
Ограниченная поддержка обработки исключений (setjmp и longjmp)
Помимо вышесказанного, он должен опираться на спецификации ANSI/ISO и не должен полагаться на специфичные для компилятора функциональные возможности.
Если вы действительно любите мысли, даже стандартная библиотека C использует ООП - подумайте FILE *
В качестве примера: fopen()
инициализирует FILE *
объект, и вы используете его, используйте методы-члены fscanf()
, fprintf()
, fread()
, fwrite()
и другие, и в конечном итоге завершить его с fclose()
,
Вы также можете пойти по пути псевдо-Objective-C, который также не сложен:
typedef void *Class;
typedef struct __class_Foo
{
Class isa;
int ivar;
} Foo;
typedef struct __meta_Foo
{
Foo *(*alloc)(void);
Foo *(*init)(Foo *self);
int (*ivar)(Foo *self);
void (*setIvar)(Foo *self);
} meta_Foo;
meta_Foo *class_Foo;
void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
class_Foo = malloc(sizeof(meta_Foo));
if (class_Foo)
{
class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
}
}
Foo *__imp_Foo_alloc(void)
{
Foo *foo = malloc(sizeof(Foo));
if (foo)
{
memset(foo, 0, sizeof(Foo));
foo->isa = class_Foo;
}
return foo;
}
Foo *__imp_Foo_init(Foo *self)
{
if (self)
{
self->ivar = 42;
}
return self;
}
// ...
Использовать:
int main(void)
{
Foo *foo = (class_Foo->init)((class_Foo->alloc)());
printf("%d\n", (foo->isa->ivar)(foo)); // 42
foo->isa->setIvar(foo, 60);
printf("%d\n", (foo->isa->ivar)(foo)); // 60
free(foo);
}
Это то, что может быть получено из некоторого кода Objective C, подобного этому, если используется довольно старый переводчик Objective C-to-C:
@interface Foo : NSObject
{
int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end
@implementation Foo
- (id)init
{
if (self = [super init])
{
ivar = 42;
}
return self;
}
@end
int main(void)
{
Foo *foo = [[Foo alloc] init];
printf("%d\n", [foo ivar]);
[foo setIvar:60];
printf("%d\n", [foo ivar]);
[foo release];
}
Я немного опоздал на вечеринку здесь, но мне нравится избегать обеих крайностей макросов - слишком много или слишком много запутывает код, но пара очевидных макросов может облегчить разработку и чтение кода ООП:
/*
* OOP in C
*
* gcc -o oop oop.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct obj2d {
float x; // object center x
float y; // object center y
float (* area)(void *);
};
#define X(obj) (obj)->b1.x
#define Y(obj) (obj)->b1.y
#define AREA(obj) (obj)->b1.area(obj)
void *
_new_obj2d(int size, void * areafn)
{
struct obj2d * x = calloc(1, size);
x->area = areafn;
// obj2d constructor code ...
return x;
}
// --------------------------------------------------------
struct rectangle {
struct obj2d b1; // base class
float width;
float height;
float rotation;
};
#define WIDTH(obj) (obj)->width
#define HEIGHT(obj) (obj)->height
float rectangle_area(struct rectangle * self)
{
return self->width * self->height;
}
#define NEW_rectangle() _new_obj2d(sizeof(struct rectangle), rectangle_area)
// --------------------------------------------------------
struct triangle {
struct obj2d b1;
// deliberately unfinished to test error messages
};
#define NEW_triangle() _new_obj2d(sizeof(struct triangle), triangle_area)
// --------------------------------------------------------
struct circle {
struct obj2d b1;
float radius;
};
#define RADIUS(obj) (obj)->radius
float circle_area(struct circle * self)
{
return M_PI * self->radius * self->radius;
}
#define NEW_circle() _new_obj2d(sizeof(struct circle), circle_area)
// --------------------------------------------------------
#define NEW(objname) (struct objname *) NEW_##objname()
int
main(int ac, char * av[])
{
struct rectangle * obj1 = NEW(rectangle);
struct circle * obj2 = NEW(circle);
X(obj1) = 1;
Y(obj1) = 1;
// your decision as to which of these is clearer, but note above that
// macros also hide the fact that a member is in the base class
WIDTH(obj1) = 2;
obj1->height = 3;
printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));
X(obj2) = 10;
Y(obj2) = 10;
RADIUS(obj2) = 1.5;
printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));
// WIDTH(obj2) = 2; // error: struct circle has no member named width
// struct triangle * obj3 = NEW(triangle); // error: triangle_area undefined
}
Я думаю, что у этого есть хороший баланс, и ошибки, которые это генерирует (по крайней мере с опциями gcc 6.3 по умолчанию) для некоторых из более вероятных ошибок, полезны вместо того, чтобы путать. Весь смысл в повышении производительности труда программиста нет?
Я также работаю над этим на основе макро решения. Так что это только для самых смелых, я думаю;-) Но это уже довольно приятно, и я уже работаю над несколькими проектами в дополнение к этому. Это работает так, что вы сначала определяете отдельный заголовочный файл для каждого класса. Как это:
#define CLASS Point
#define BUILD_JSON
#define Point__define \
METHOD(Point,public,int,move_up,(int steps)) \
METHOD(Point,public,void,draw) \
\
VAR(read,int,x,JSON(json_int)) \
VAR(read,int,y,JSON(json_int)) \
Чтобы реализовать класс, вы создаете для него заголовочный файл и файл C, в котором вы реализуете методы:
METHOD(Point,public,void,draw)
{
printf("point at %d,%d\n", self->x, self->y);
}
В заголовок, который вы создали для класса, вы включаете другие необходимые заголовки и определяете типы и т. Д., Относящиеся к классу. И в заголовке класса, и в файле C вы включаете файл спецификации класса (см. Первый пример кода) и X-макрос. Эти X-макросы ( 1, 2, 3 и т. Д.) Расширят код до фактических структур классов и других объявлений.
Унаследовать класс, #define SUPER supername
и добавить supername__define \
как первая строка в определении класса. Оба должны быть там. Также есть поддержка JSON, сигналы, абстрактные классы и т. Д.
Чтобы создать объект, просто используйте W_NEW(classname, .x=1, .y=2,...)
, Инициализация основана на инициализации структуры, представленной в C11. Это работает хорошо, и все, что не перечислено, установлено в ноль.
Чтобы вызвать метод, используйте W_CALL(o,method)(1,2,3)
, Это похоже на вызов функции более высокого порядка, но это просто макрос. Расширяется до ((o)->klass->method(o,1,2,3))
это действительно хороший взлом.
Смотрите документацию и сам код.
Поскольку фреймворку нужен некоторый шаблонный код, я написал Perl-скрипт (wobject), который выполняет эту работу. Если вы используете это, вы можете просто написать
class Point
public int move_up(int steps)
public void draw()
read int x
read int y
и он создаст файл спецификации класса, заголовок класса и файл C, который включает в себя Point_impl.c
где вы реализуете класс. Это экономит довольно много работы, если у вас много простых классов, но все еще в C. wobject - очень простой сканер на основе регулярных выражений, который легко адаптировать к конкретным потребностям или переписать с нуля.
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"
#include <stdio.h>
int main()
{
Triangle tr1= CTriangle->new();
Rectangle rc1= CRectangle->new();
tr1->width= rc1->width= 3.2;
tr1->height= rc1->height= 4.1;
CPolygon->printArea((Polygon)tr1);
printf("\n");
CPolygon->printArea((Polygon)rc1);
}
Выход:
6.56
13.12
Вот пример того, что такое ОО-программирование на C.
Это настоящий, чистый C, без макросов препроцессора. У нас есть наследование, полиморфизм и инкапсуляция данных (включая данные, частные для классов или объектов). У защищенного эквивалента квалификатора нет никаких шансов, то есть частные данные также являются частными в цепочке наследования. Но это не неудобство, потому что я не думаю, что это необходимо.
CPolygon
не создается, потому что мы используем его только для манипулирования объектами цепочки наследования, имеющими общие аспекты, но отличающиеся друг от друга их реализацией (полиморфизм).
Если бы я собирался написать ООП на C I, вероятно, пошел бы с псевдо- Pimpl дизайн. Вместо того, чтобы передавать указатели на структуры, вы в конечном итоге передаете указатели на указатели на структуры. Это делает содержимое непрозрачным и облегчает полиморфизм и наследование.
Настоящая проблема с ООП в C состоит в том, что происходит, когда переменные выходят из области видимости. Деструкторов, сгенерированных компилятором, нет, и это может вызвать проблемы. Макросы могут помочь, но смотреть на них всегда будет некрасиво.
Другой способ программирования в объектно-ориентированном стиле на C - это использовать генератор кода, который преобразует язык, специфичный для предметной области, в C. Как это делается с помощью TypeScript и JavaScript, чтобы перенести OOP в js.
Вы можете попробовать COOP, удобный для программиста фреймворк для ООП на C, который включает классы, исключения, полиморфизм и управление памятью (что важно для встроенного кода). Это относительно легкий синтаксис, см. Руководство в Wiki.
Мне удалось реализовать наследование и полиморфизм в C. Я могу сделать одиночное наследование с виртуальными таблицами, и я могу реализовать несколько интерфейсов с помощью метода, при котором структура, реализующая интерфейс, просто создает структуру интерфейса, предоставляя ей свои собственные методы и указатель на сам. Затем структура интерфейса вызывает эти методы и, среди прочих параметров, передает им указатель на структуру, создавшую реализацию интерфейса. Когда дело доходит до наследования неабстрактных классов, я добился этого с виртуальными таблицами, я уже объяснил наследование с виртуальными таблицами в этом ответе.Код из этого ответа не позволяет реализовать несколько интерфейсов. Однако в этом ответе я изменил свой код, чтобы он позволял реализовать несколько интерфейсов.Вот весь код, который я разместил на github.Я также опубликую код здесь, но, возможно, он более удобочитаем на github, так как я помещаю код в несколько файлов. Вот код, у меня есть структуры Животиня, Пас, Автомобиль и структура МозеПроизвестиЗвук. Эта последняя структура является интерфейсом. Pas и Automobil реализуют это. Struct Pas также наследует Zivotinja.
Вот код основной функции
Pas *pas = Pas_new_sve(4, 20, "some dog name");
MozeProizvestiZvuk *mozeProizvestiZvuk = pas->getMozeProizvestiZvuk(pas);
mozeProizvestiZvuk->proizvediZvuk(mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste);
mozeProizvestiZvuk->proizvediZvuk(mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste);
printf("number of times it made noise = %d\n", mozeProizvestiZvuk->getKolikoPutaJeProizveoZvuk(mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste));
Automobil *automobil = Automobil_new("Sandero", 2009);
MozeProizvestiZvuk *zvukAutomobil = automobil->getMozeProizvestiZvuk(automobil);
for(int i=0; i<3; i++){
zvukAutomobil->proizvediZvuk(zvukAutomobil->strukturaKojuMetodeInterfejsaKoriste);
}
printf("number of times it made noise = %d\n", zvukAutomobil->getKolikoPutaJeProizveoZvuk(zvukAutomobil->strukturaKojuMetodeInterfejsaKoriste));
Zivotinja *zivotinja = Zivotinja_new(10);
zivotinja->vTable->ispisiPodatkeOZivotinji(zivotinja);
zivotinja->vTable->obrisi(&zivotinja);
Zivotinja *pasKaoZivotinja = Pas_new_sve(5, 50, "Milojko");
pasKaoZivotinja->vTable->ispisiPodatkeOZivotinji(pasKaoZivotinja);
int godine = pasKaoZivotinja->vTable->dajGodine(pasKaoZivotinja);
printf("age of the dog which was upcasted to an animal = %d \n", godine);
pasKaoZivotinja->vTable->obrisi(&pasKaoZivotinja);
Вот файл MozeProizvestiZvuk.h
#ifndef MOZE_PROIZVESTI_ZVUK_H
#define MOZE_PROIZVESTI_ZVUK_H
typedef struct MozeProizvestiZvukStruct{
void (*proizvediZvuk)(void *strukturaKojuMetodeInterfejsaKoriste);
unsigned int (*getKolikoPutaJeProizveoZvuk)(void *strukturaKojaImplementiraInterfejs);
void *strukturaKojuMetodeInterfejsaKoriste;
}MozeProizvestiZvuk;
#endif
Вот структура Automobil, которая реализует этот интерфейс.
#include"MozeProizvestiZvuk.h"
#include<stdlib.h>
typedef struct AutomobilStruct{
const char *naziv;
int godinaProizvodnje;
unsigned int kolikoPutaJeProizveoZvuk;
MozeProizvestiZvuk* (*getMozeProizvestiZvuk)(struct AutomobilStruct *_this);
}Automobil;
MozeProizvestiZvuk* Automobil_getMozeProizvestiZvuk(Automobil *automobil);
Automobil* Automobil_new(const char* naziv, int godiste){
Automobil *automobil = (Automobil*) malloc(sizeof(Automobil));
automobil->naziv = naziv;
automobil->godinaProizvodnje = godiste;
automobil->kolikoPutaJeProizveoZvuk = 0;
automobil->getMozeProizvestiZvuk = Automobil_getMozeProizvestiZvuk;
return automobil;
}
void Automobil_delete(Automobil **adresaAutomobilPointera){
free(*adresaAutomobilPointera);
*adresaAutomobilPointera = NULL;
}
unsigned int Automobil_getKolikoJeZvukovaProizveo(Automobil *automobil){
return automobil->kolikoPutaJeProizveoZvuk;
}
void Automobil_proizvediZvuk(Automobil *automobil){
printf("Automobil koji se zove %s, godiste %d proizvodi zvuk. \n", automobil->naziv, automobil->godinaProizvodnje);
automobil->kolikoPutaJeProizveoZvuk++;
}
MozeProizvestiZvuk* Automobil_getMozeProizvestiZvuk(Automobil *automobil){
MozeProizvestiZvuk *mozeProizvestiZvuk = (MozeProizvestiZvuk*) malloc(sizeof(MozeProizvestiZvuk));
mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste = automobil;
mozeProizvestiZvuk->proizvediZvuk = Automobil_proizvediZvuk;
mozeProizvestiZvuk->getKolikoPutaJeProizveoZvuk = Automobil_getKolikoJeZvukovaProizveo;
return mozeProizvestiZvuk;
}
Вот структура Zivotinja, эта структура ни от чего не наследуется и не реализует никаких интерфейсов, но структура Pas будет наследоваться от Zivotinja.
#include<stdio.h>
#include<stdlib.h>
typedef struct ZivotinjaVTableStruct{
void (*ispisiPodatkeOZivotinji)(void *zivotinja);
int (*dajGodine) (void *zivotinja);
} ZivotinjaVTable;
typedef struct ZivotinjaStruct{
ZivotinjaVTable *vTable;
int godine;
} Zivotinja;
void ispisiPodatkeOOvojZivotinji(Zivotinja* zivotinja){
printf("Ova zivotinja ima %d godina. \n", zivotinja->godine);
}
int dajGodineOveZivotinje(Zivotinja *z){
return z->godine;
}
void Zivotinja_obrisi(Zivotinja **adresaPointeraKaZivotinji){
Zivotinja *zivotinjaZaBrisanje = *adresaPointeraKaZivotinji;
free(zivotinjaZaBrisanje);
*adresaPointeraKaZivotinji = NULL;
}
struct ZivotinjaVTableStruct zivotinjaVTableGlobal = {Zivotinja_obrisi, ispisiPodatkeOOvojZivotinji, dajGodineOveZivotinje};
Zivotinja* Zivotinja_new(int godine){
ZivotinjaVTable *vTable = &zivotinjaVTableGlobal;
Zivotinja *z = (Zivotinja*) malloc(sizeof(Zivotinja));
z->vTable = vTable;
z->godine = godine;
}
И, наконец, структура Pas, которая наследуется от Zivotinja и реализует интерфейс MozeProizvestiZvuk.
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include"Zivotinja.h"
#include"MozeProizvestiZvuk.h"
typedef struct PasVTableStruct{
bool (*obrisi)(void **Pas);
void (*ispisiPodatkeOZivotinji)(void *Pas);
int (*dajGodine) (void *Pas);
bool (*daLiJeVlasnikStariji) (void *Pas);
} PasVTable;
typedef struct PasStruct{
PasVTable *vTable;
int godine;
const char* vlasnik;
int godineVlasnika;
unsigned int kolikoPutaJeProizveoZvuk;
MozeProizvestiZvuk* (*getMozeProizvestiZvuk)(struct PasStruct *_this);
} Pas;
MozeProizvestiZvuk* Pas_getMozeProizvestiZvuk(Pas *_this);
void ispisiPodatkeOPsu(void *pasVoid){
Pas *pas = (Pas*)pasVoid;
printf("Pas ima %d godina, vlasnik se zove %s, vlasnik ima %d godina. \n", pas->godine, pas->vlasnik, pas->godineVlasnika);
}
int dajGodinePsa(void *pasVoid){
Pas *pas = (Pas*) pasVoid;
return pas->godine;
}
bool daLiJeVlasnikStariji(Pas *pas){
return pas->godineVlasnika >= pas->godine;
}
void Pas_obrisi(Pas **adresaPointeraPsa){
Pas *pasZaBrisanje = *adresaPointeraPsa;
free(pasZaBrisanje);
*adresaPointeraPsa = NULL;
}
struct PasVTableStruct pasVTableGlobal = {
Pas_obrisi,
ispisiPodatkeOPsu,
dajGodinePsa,
daLiJeVlasnikStariji
};
Pas* Pas_new(int godine){
Pas *z = (Pas*) malloc(sizeof(Pas));
z->godine = godine;
z->kolikoPutaJeProizveoZvuk = 0;
z->vTable = (&pasVTableGlobal);
z->getMozeProizvestiZvuk = Pas_getMozeProizvestiZvuk;
return z;
}
Pas *Pas_new_sve(int godine, int godineVlasnika, char* imeVlasnika){
Pas *pas = (Pas*) malloc(sizeof(Pas));
pas->kolikoPutaJeProizveoZvuk = 0;
pas->godine = godine;
pas->godineVlasnika = godineVlasnika;
pas->vlasnik = imeVlasnika;
pas->vTable = &pasVTableGlobal;
pas->getMozeProizvestiZvuk = Pas_getMozeProizvestiZvuk;
return pas;
}
unsigned int Pas_getBrojZvukova(Pas *_this){
return _this->kolikoPutaJeProizveoZvuk;
}
void Pas_proizvediZvuk(Pas *_this){
printf("Pas godina %d, vlasnika %s je proizveo zvuk.\n", _this->godine, _this->vlasnik);
_this->kolikoPutaJeProizveoZvuk++;
}
MozeProizvestiZvuk* Pas_getMozeProizvestiZvuk(Pas *_this){
MozeProizvestiZvuk *mozeProizvestiZvuk = (MozeProizvestiZvuk*) malloc(sizeof(MozeProizvestiZvuk));
mozeProizvestiZvuk->getKolikoPutaJeProizveoZvuk = Pas_getBrojZvukova;
mozeProizvestiZvuk->proizvediZvuk = Pas_proizvediZvuk;
mozeProizvestiZvuk->strukturaKojuMetodeInterfejsaKoriste = _this;
return mozeProizvestiZvuk;
}
Проект с открытым исходным кодом Dynace делает именно это. Это на https://github.com/blakemcbride/Dynace
У @Adam Rosenfield есть очень хорошее объяснение того, как достичь ООП с C
Кроме того, я бы порекомендовал вам прочитать
1) pjsip
Очень хорошая библиотека C для VoIP. Вы можете узнать, как он достигает ООП, хотя структуры и таблицы указателей на функции
2) iOS Runtime
Узнайте, как среда выполнения iOS поддерживает Objective C. Он достигает ООП с помощью указателя isa, мета-класса.
Посмотрите на http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html. Если ничто иное, как чтение документации, не является полезным опытом.
Если вам нужно написать небольшой код, попробуйте это: https://github.com/fulminati/class-framework
#include "class-framework.h"
CLASS (People) {
int age;
};
int main()
{
People *p = NEW (People);
p->age = 10;
printf("%d\n", p->age);
}
У меня есть небольшой проект на эту тему. Если кому-то все еще интересно, вот ссылка на репо: https://github.com/alexmarincu/Cbject
Документация пока недоступна, но примеры должны быть довольно простыми для понимания.