Objektorientierung in C

Was wäre eine Reihe von raffinierten Präprozessor-Hacks (ANSI C89 / ISO C90 kompatibel), die eine Art hässliche (aber brauchbare) Objektorientierung in C ermöglichen?

Ich kenne einige verschiedene objektorientierte Sprachen. Bitte antworten Sie nicht mit Antworten wie “Lernen Sie C ++!”. Ich habe gelesen ” Objektorientierte Programmierung mit ANSI C ” (Vorsicht: PDF-Format ) und einige andere interessante Lösungen, aber ich interessiere mich hauptsächlich für deins :-)!


Siehe auch Können Sie objektorientierten Code in C schreiben?

C Object System (COS) klingt vielversprechend (es ist immer noch in der Alpha-Version). Es versucht, die verfügbaren Konzepte aus Gründen der Einfachheit und Flexibilität minimal zu halten: einheitliche objektorientierte Programmierung einschließlich offener classn, Metaklassen, Eigenschaftenmetaklassen, Generika, Multimethoden, Delegierung, Eigentumsrechte, Ausnahmen, Verträge und Schließungen. Es gibt ein Entwurfspapier (PDF), das es beschreibt.

Ausnahme in C ist eine C89-Implementierung des TRY-CATCH-FINALLY, die in anderen OO-Sprachen gefunden wurde. Es kommt mit einer Testsuite und einigen Beispielen.

Sowohl von Laurent Deniau, der viel an OOP in C arbeitet .

Ich würde davon abraten, dass Präprozessor (ab) verwendet wird, um zu versuchen, die C-Syntax der einer eher objektorientierten Sprache ähnlicher zu machen. Auf der einfachsten Ebene verwenden Sie nur einfache Strukturen als Objekte und übergeben sie durch pointers:

struct monkey { float age; bool is_male; int happiness; }; void monkey_dance(struct monkey *monkey) { /* do a little dance */ } 

Um Dinge wie inheritance und Polymorphie zu bekommen, muss man etwas härter arbeiten. Sie können eine manuelle inheritance durchführen, indem Sie das erste Mitglied einer Struktur als Instanz der Oberklasse verwenden und anschließend pointers auf Basis- und abgeleitete classn frei umwandeln können:

 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 

Um Polymorphismus (dh virtuelle functionen) zu erhalten, verwenden Sie functionszeiger und optional functionszeigertabellen, die auch als virtuelle Tabellen oder VTabellen bezeichnet werden:

 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; } 

Und so macht man Polymorphie in C. Es ist nicht schön, aber es macht den Job. Es gibt einige Probleme, die pointersdarstellungen zwischen Basis- und abgeleiteten classn betreffen, die sicher sind, solange die Basisklasse das erste Mitglied der abgeleiteten class ist. Mehrfache inheritance ist viel schwieriger – in diesem Fall müssen Sie, um zwischen anderen Basisklassen als der ersten zu unterscheiden, Ihre pointers basierend auf den richtigen Offsets manuell anpassen, was wirklich schwierig und errorsanfällig ist.

Eine andere (trickreiche) Sache, die Sie tun können, ist den dynamischen Typ eines Objekts zur Laufzeit zu ändern! Sie weisen ihm einfach einen neuen Vtable-pointers zu. Sie können sogar einige der virtuellen functionen selektiv ändern, während Sie andere beibehalten und neue Hybridtypen erstellen. Seien Sie vorsichtig, wenn Sie eine neue V-Tabelle erstellen, anstatt die globale V-Tabelle zu ändern. Andernfalls werden Sie versehentlich alle Objekte eines bestimmten Typs beeinflussen.

Ich habe einmal mit einer C-Bibliothek gearbeitet, die so elegant umgesetzt wurde. Sie hatten in C geschrieben, um Objekte zu definieren und dann von ihnen zu erben, so dass sie genauso erweiterbar waren wie ein C ++ – Objekt. Die Grundidee war:

  • Jedes Objekt hatte seine eigene Datei
  • Öffentliche functionen und Variablen sind in der H-Datei für ein Objekt definiert
  • Private Variablen und functionen wurden nur in der .c-Datei gefunden
  • Um eine neue Struktur “zu erben”, wird mit dem ersten Element der Struktur, von der das Objekt erbt, erzeugt

inheritance ist schwer zu beschreiben, aber im Grunde war es das:

 struct vehicle { int power; int weight; } 

Dann in einer anderen Datei:

 struct van { struct vehicle base; int cubic_size; } 

Dann könnten Sie einen Van im Speicher erstellen lassen, der von einem Code verwendet wird, der nur über Fahrzeuge Bescheid wusste:

 struct van my_van; struct vehicle *something = &my_van; vehicle_function( something ); 

Es funktionierte wunderbar, und die .h-Dateien definierten genau, was Sie mit jedem Objekt machen können.

Der GNOME-Desktop für Linux ist in objektorientiertem C geschrieben und hat ein Objektmodell namens ” GObject “, das Eigenschaften, inheritance, Polymorphie sowie einige andere Extras wie Referenzen, Ereignisbehandlung (“Signale” genannt), Laufzeit unterstützt Tippen, private Daten usw.

Es enthält Präprozessor-Hacks, um Dinge wie Typumwandlung in der classnhierarchie usw. auszuführen. Hier ist eine Beispielklasse, die ich für GNOME geschrieben habe (Dinge wie gchar sind typedefs):

classnquelle

classnüberschrift

Innerhalb der GObject-Struktur gibt es eine GType-Ganzzahl, die als magische Zahl für GLibs dynamisches Typisierungssystem verwendet wird (Sie können die gesamte Struktur in einen “GType” umwandeln, um ihren Typ zu finden).

Wenn Sie an Methoden denken, die Objekte als statische Methoden bezeichnen, die ein implizites ” this ” in die function übergeben, kann dies das Denken von O in C erleichtern.

Beispielsweise:

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

wird:

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

Oder etwas ähnliches.

Ich habe so etwas in C gemacht, bevor ich wusste, was OOP war.

Es folgt ein Beispiel, das einen Datenpuffer implementiert, der bei einer minimalen Größe, einem Inkrement und einer maximalen Größe bei Bedarf wächst. Diese spezielle Implementierung war “element” -basiert, was bedeutet, dass sie entworfen wurde, um eine listenähnliche Sammlung eines beliebigen C-Typs zu ermöglichen, nicht nur eines Byte-Puffers variabler Länge.

Die Idee ist, dass das Objekt mit xxx_crt () instanziiert und mit xxx_dlt () gelöscht wird. Jede der “member” -Methoden benötigt einen speziell typisierten pointers, auf den zugegriffen werden kann.

Ich habe auf diese Weise eine verkettete Liste, einen zyklischen Puffer und eine Anzahl anderer Dinge implementiert.

Ich muss gestehen, ich habe nie darüber nachgedacht, wie man inheritance mit diesem Ansatz umsetzen kann. Ich stelle mir vor, dass eine Mischung aus dem, was Kieveli anbietet, ein guter Weg sein könnte.

dtb.c:

 #include  #include  #include  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 || maxsizmaxsiz) { 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 war einfach ein typedef von int – ich erinnerte mich daran, dass die Länge von Plattform zu Plattform (für die Portierung) variabel ist.

Etwas off-topic, aber der ursprüngliche C ++ – Compiler, Cfront , kompilierte C ++ zu C und dann zu Assembler.

Hier erhalten .

ffmpeg (ein Toolkit für Videoverarbeitung) ist in gerader C (und Assemblersprache) geschrieben, aber mit einem objektorientierten Stil. Es ist voll von Strukturen mit functionszeigern. Es gibt eine Reihe von Factory-functionen, die die Strukturen mit den entsprechenden “Methoden” -pointersn initialisieren.

Wenn Sie wirklich nachdenken, verwenden Sie selbst Standard-C-Bibliothek OOP – betrachten Sie FILE * als Beispiel: fopen() initialisiert ein FILE * -Objekt, und Sie verwenden es mit den Methoden fscanf() , fprintf() , fread() , fwrite() und andere, und finalisieren sie schließlich mit fclose() .

Sie können auch mit der Pseudo-Objective-C-Methode gehen, die auch nicht schwierig ist:

 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; } // ... 

Benutzen:

 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); } 

Dies kann aus irgendeinem Objective-C-Code wie diesem resultieren, wenn ein ziemlich alter Objective-C-to-C-Translator verwendet wird:

 @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]; } 

Ich denke, was Adam Rosenfield geschrieben hat, ist die korrekte Art, OOP in C zu machen. Ich möchte hinzufügen, dass das, was er zeigt, die Implementierung des Objekts ist. Mit anderen Worten, die tatsächliche Implementierung würde in die .c Datei .h , während die Schnittstelle in die Header-Datei .h . Verwenden Sie beispielsweise das obige Beispiel für Affen:

Die Schnittstelle würde wie folgt aussehen:

 //monkey.h struct _monkey; typedef struct _monkey monkey; //memory management monkey * monkey_new(); int monkey_delete(monkey *thisobj); //methods void monkey_dance(monkey *thisobj); 

Sie können in der Schnittstelle .h Datei sehen, dass Sie nur Prototypen definieren. Sie können dann den Implementierungsteil ” .c file” in eine statische oder dynamische Bibliothek kompilieren. Dies schafft eine Kapselung und Sie können die Implementierung nach Belieben ändern. Der Benutzer Ihres Objekts muss fast nichts über die Implementierung wissen. Dies legt auch den Fokus auf das Gesamtdesign des Objekts.

Es ist meine persönliche Überzeugung, dass oop eine Möglichkeit ist, Ihre Code-Struktur und Wiederverwendbarkeit zu konzeptualisieren und wirklich nichts mit den anderen Dingen zu tun hat, die zu c ++ hinzugefügt werden, wie Überladen oder Vorlagen. Ja, das sind sehr nette nützliche functionen, aber sie sind nicht repräsentativ dafür, was objektorientierte Programmierung wirklich ist.

Meine Empfehlung: behalte es einfach. Eines der größten Probleme, die ich habe, ist die Pflege älterer Software (manchmal über 10 Jahre alt). Wenn der Code nicht einfach ist, kann es schwierig sein. Ja, man kann sehr nützliche OOP mit Polymorphismus in C schreiben, aber es kann schwierig sein, sie zu lesen.

Ich bevorzuge einfache Objekte, die eine wohldefinierte functionalität kapseln. Ein gutes Beispiel dafür ist GLIB2 , zum Beispiel eine Hash-Tabelle:

 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); 

Die Schlüssel sind:

  1. Einfaches Architektur- und Designmuster
  2. Erreicht grundlegende OOP-Kapselung.
  3. Einfach zu implementieren, zu lesen, zu verstehen und zu warten

Wenn ich OOP in CI schreiben würde, würde es wahrscheinlich mit einem Pseudo- Pimpl- Design gehen. Anstatt pointers auf Strukturen zu übergeben, übergeben Sie pointers auf pointers auf Strukturen. Dies macht den Inhalt undurchsichtig und erleichtert Polymorphie und inheritance.

Das eigentliche Problem mit OOP in C ist, was passiert, wenn Variablen den Gültigkeitsbereich verlassen. Es gibt keine vom Compiler erzeugten Destruktoren, die Probleme verursachen können. Makros können möglicherweise helfen, aber es wird immer hässlich aussehen.

 #include "triangle.h" #include "rectangle.h" #include "polygon.h" #include  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); } 

Ausgabe:

 6.56 13.12 

Hier ist eine Show von was ist OO Programmierung mit C.

Das ist echtes, reines C, keine Präprozessor-Makros. Wir haben inheritance, Polymorphismus und Datenkapselung (einschließlich Daten für classn oder Objekte). Es gibt keine Möglichkeit für geschützte Qualifier-Entsprechungen, d. H. Private Daten sind auch in der inheritanceskette privat. Aber das ist keine Unannehmlichkeit, weil ich nicht denke, dass es notwendig ist.

CPolygon wird nicht instanziiert, da wir es nur verwenden, um Objekte aus der Kette der inheritance zu manipulieren, die gemeinsame Aspekte, aber unterschiedliche Implementierungen von ihnen haben (Polymorphismus).

@Adam Rosenfield hat eine sehr gute Erklärung, wie man OOP mit C erreicht

Außerdem würde ich Ihnen empfehlen, zu lesen

1) pjsip

Eine sehr gute C-Bibliothek für VoIP. Sie können lernen, wie es OOP durch Struktur- und functionszeigertabellen erreicht

2) iOS-Laufzeit

Erfahren Sie, wie iOS Runtime Objectiv C antreibt. Es erreicht OOP über isa pointer, Meta-class

Für mich sollte die Objektorientierung in C folgende Eigenschaften haben:

  1. Kapselung und Verbergen von Daten (kann mit Hilfe von structs / opaque pointers erreicht werden)

  2. inheritance und Unterstützung für Polymorphie (einzelne inheritance kann mit Hilfe von Strukturen erreicht werden – stellen Sie sicher, dass die abstrakte Basis nicht instanziierbar ist)

  3. Konstruktor- und Destruktor-functionalität (nicht einfach zu erreichen)

  4. Typprüfung (zumindest für benutzerdefinierte Typen wie C erzwingt keine)

  5. Referenzzählung (oder etwas, um RAII zu implementieren)

  6. Eingeschränkte Unterstützung für die Ausnahmebehandlung (setjmp und longjmp)

Obendrein sollte es sich auf ANSI / ISO-Spezifikationen stützen und sollte nicht auf Compiler-spezifische functionalität angewiesen sein.

Schau dir http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html an . Wenn nichts anderes das Lesen der Dokumentation eine aufschlussreiche Erfahrung ist.

Ich bin ein bisschen zu spät zur Party hier, aber ich mag es, beide Makroextreme zu vermeiden – zu viele oder zu viel Code verschleiert, aber ein paar offensichtliche Makros können den OOP-Code leichter zu entwickeln und zu lesen machen:

 /* * OOP in C * * gcc -o oop oop.c */ #include  #include  #include  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 } 

Ich denke, das hat ein gutes Gleichgewicht, und die Fehler, die es generiert (zumindest mit den Standard-GCC 6.3-Optionen) für einige der wahrscheinlicheren Fehler sind hilfreich, anstatt zu verwirren. Der springende Punkt ist, die Produktivität der Programmierer zu verbessern, nein?

Wenn Sie einen kleinen Code schreiben müssen, versuchen Sie Folgendes: 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); }