Объектная ориентация в C

Каким должен быть набор превосходных хакеров препроцессора (совместимый с ANSI C89/ISO C90), которые позволяют использовать некоторую уродливую (но полезную) объектно-ориентированную ориентацию в C?

Я знаком с несколькими объектно-ориентированными языками, поэтому, пожалуйста, не отвечайте на такие ответы, как "Learn С++!". Я прочитал " Объектно-ориентированное программирование с ANSI C" (остерегайтесь: PDF-формат) и несколько других интересных решений, но меня больше всего интересуют ваши: -)!


См. также Можете ли вы написать объектно-ориентированный код в C?

Ответы

Ответ 1

C Object System (COS) звучит многообещающе (это все еще в альфа-версии). Он пытается свести к минимуму имеющиеся концепции ради простоты и гибкости: единообразное объектно-ориентированное программирование, включая открытые классы, метаклассы, метаклассы свойств, универсальные шаблоны, мультиметоды, делегирование, владение, исключения, контракты и замыкания. Существует черновик документа (PDF), который описывает это.

Исключением в C является C89-реализация TRY-CATCH-FINALLY, найденная в других языках OO. Он поставляется с комплектом тестов и некоторыми примерами.

Оба от Лорана Денио, который много работает над ООП в Си.

Ответ 2

Я бы посоветовал использовать препроцессор (ab), чтобы попытаться сделать синтаксис Си более похожим на еще один объектно-ориентированный язык. На самом базовом уровне вы просто используете простые структуры как объекты и передаете их указателями:

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 dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1 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 dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2 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. Вы можете даже выборочно изменять некоторые из виртуальных функций, сохраняя других, создавая новые гибридные типы. Просто будьте осторожны, чтобы создать новую vtable вместо изменения глобальной таблицы vtable, иначе вы случайно затронете все объекты определенного типа.

Ответ 3

Я когда-то работал с библиотекой 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 точно определяли, что вы должны делать с каждым объектом.

Ответ 4

Рабочий стол GNOME для Linux написан в объектно-ориентированном C и имеет объектную модель под названием " GObject", которая поддерживает свойства, наследование, полиморфизм, а также некоторые другие полезные свойства, такие как ссылки, обработка событий (называемые "сигналы" ), ввод времени выполнения, личные данные и т.д.

Он включает в себя препроцессорные хаки, чтобы делать такие вещи, как typecasting в иерархии классов и т.д. Здесь примерный класс, который я написал для GNOME (такие вещи, как gchar, являются typedefs):

Источник класса

Заголовок заголовка

Внутри структуры GObject есть целое число GType, которое используется в качестве магического числа для динамической системы типизации GLib (вы можете отнести всю структуру к "GType", чтобы найти ее тип).

Ответ 5

Если вы думаете о методах, называемых объектами в качестве статических методов, которые передают неявный "this" в функцию, он может облегчить мышление OO в C.

Например:

String s = "hi";
System.out.println(s.length());

становится:

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

Или что-то в этом роде.

Ответ 6

Немного не по теме, но исходный компилятор С++, Cfront, скомпилировал С++ на C, а затем на ассемблер.

Сохранено здесь.

Ответ 7

Раньше я делал подобные вещи на 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 - я использовал его, чтобы напомнить мне, что длина была переменной от платформы к платформе (для портирования).

Ответ 8

ffmpeg (набор инструментов для обработки видео) написан на прямом языке C (и ассемблере), но с использованием объектно-ориентированного стиль. Он содержит структуры с указателями функций. Существует набор функций factory, которые инициализируют структуры с помощью соответствующих указателей "метод".

Ответ 9

Если вы действительно думаете с умом, даже стандартная библиотека C использует OOP - рассмотрите 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];
}

Ответ 10

Я думаю, что то, что написал Адам Розенфилд, является правильным способом делать ООП на 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 file" в статическую или динамическую библиотеку. Это создает инкапсуляцию, а также вы можете изменить реализацию по своему усмотрению. Пользователь вашего объекта не должен знать почти ничего о его реализации. Это также фокусируется на общем дизайне объекта.

Это мое личное убеждение, что oop - это способ концептуализации вашей структуры кода и повторного использования и не имеет никакого отношения к тем другим вещам, которые добавляются в С++, например, при перегрузке или шаблонах. Да, это очень приятные полезные функции, но они не являются репрезентативными для объектно-ориентированного программирования.

Ответ 11

Если бы я собирался писать ООП в C, я бы, вероятно, пошел с pseudo- Pimpl. Вместо того, чтобы передавать указатели на структуры, вы в конечном итоге передаете указатели на указатели на структуры. Это делает контент непрозрачным и облегчает полиморфизм и наследование.

Реальная проблема с ООП в С - это то, что происходит, когда переменные выходят из области видимости. Нет деструкторов, генерируемых компилятором, и это может вызвать проблемы. Макросы могут помочь, но это всегда будет уродливо смотреть.

Ответ 12

Моя рекомендация: держите это просто. Одна из самых больших проблем, с которыми я столкнулась, заключается в том, чтобы поддерживать более старое программное обеспечение (иногда более 10 лет). Если код не прост, это может быть сложно. Да, можно написать очень полезный ООП с полиморфизмом в С, но его трудно прочитать.

Я предпочитаю простые объекты, которые инкапсулируют некоторые четко определенные функции. Отличным примером этого является 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);

Ключами являются:

  • Простая архитектура и шаблон дизайна
  • Достигает базовой инкапсуляции ООП.
  • Легко внедрять, читать, понимать и поддерживать

Ответ 13

Для меня объектная ориентация в C должна иметь следующие особенности:

  • Инкапсуляция и скрытие данных (может быть достигнуто с помощью структур/непрозрачных указателей)

  • Наследование и поддержка полиморфизма (одно наследование может быть достигнуто с помощью структур - убедитесь, что абстрактная база не является реальной)

  • Конструктор и деструктор (нелегко достичь)

  • Проверка типов (по крайней мере для пользовательских типов, поскольку C не применяет никаких действий)

  • Подсчет ссылок (или что-то для реализации RAII)

  • Ограниченная поддержка обработки исключений (setjmp и longjmp)

В дополнение к вышеизложенному, он должен полагаться на спецификации ANSI/ISO и не должен полагаться на специфические для компилятора функции.

Ответ 14

#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

Здесь показано, что такое OO-программирование с C.

Это реальный, чистый C, не препроцессорные макросы. У нас есть наследство,   полиморфизм и инкапсуляция данных (включая данные, частные для классов или объектов).   Нет никакой возможности для защищенного эквивалента квалификатора, то есть,   частные данные являются частными по цепочке преемственности.   Но это не неудобство, потому что я не думаю, что это необходимо.

CPolygon не создается, потому что мы используем его только для манипулирования объектами   вниз по цепочке преследований, которые имеют общие аспекты, но разные   их реализации (полиморфизм).

Ответ 15

@Adam Rosenfield имеет очень хорошее объяснение того, как достичь ООП с помощью C

Кроме того, я бы рекомендовал вам прочитать

1) pjsip

Очень хорошая библиотека C для VoIP. Вы можете узнать, как он достигает OOP, хотя структуры и таблицы указателей функций

2) iOS Runtime

Узнайте, как работает среда выполнения iOS Objective C. Достигает OOP через указатель isa, meta class

Ответ 17

Я немного опаздываю на вечеринку, но мне нравится избегать обоих крайних макросов - слишком много или слишком много запутывает код, но пара очевидных макросов может сделать код ООП более легким для разработки и чтения:

/*
 * 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 по умолчанию), для некоторых более вероятных ошибок полезны, а не путают. Все дело в том, чтобы улучшить производительность программистов?

Ответ 18

Я также работаю над этим на основе макро решения. Так что это только для самых смелых, я думаю ;-) Но это уже довольно приятно, и я уже работаю над несколькими проектами в дополнение к этому. Это работает так, что вы сначала определяете отдельный заголовочный файл для каждого класса. Как это:

#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 - очень простой сканер на основе регулярных выражений, который легко адаптировать к конкретным потребностям или переписать с нуля.

Ответ 20

Другой способ программирования в объектно-ориентированном стиле на C - это использовать генератор кода, который преобразует язык, специфичный для предметной области, в C. Как это было сделано с TypeScript и JavaScript, чтобы перенести ООП в js.

Ответ 21

Если вам нужно написать небольшой код попробуйте следующее: 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);
}