質問
ある種のsomeい(しかし使用可能な)オブジェクト指向をCで可能にする気の利いたプリプロセッサハック(ANSI C89 / ISO C90互換)のセットは何でしょうか?
私はいくつかの異なるオブジェクト指向言語に精通しているので、「Learn C ++!」のような回答を返さないでください。 " オブジェクト指向プログラミングWith ANSI C " (注意: PDF形式)および他のいくつかの興味深いソリューションですが、私はあなたのことに主に興味があります:-)!
解決
Cオブジェクトシステム(COS)は有望に聞こえます(まだアルファ版です)。シンプルさと柔軟性のために、利用可能なコンセプトを最小限に抑えようとしています。オープンクラス、メタクラス、プロパティメタクラス、ジェネリック、マルチメソッド、委任、所有権、例外、コントラクト、クロージャを含む統一オブジェクト指向プログラミング。 ドラフトペーパー(PDF)があります。
Cの例外は、TRY-CATCH-FINALLYのC89実装です。他のオブジェクト指向言語で見つかりました。テストスイートといくつかの例が付属しています。
どちらもLaurent Deniauによるもので、 CのOOP 。
他のヒント
プリプロセッサ(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
ポリモーフィズム(仮想関数)を取得するには、関数ポインターを使用し、オプションで仮想テーブルまたはvtableとも呼ばれる関数ポインターテーブルを使用します:
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でポリモーフィズムを行う方法です。それはきれいではありませんが、仕事はします。基本クラスと派生クラス間のポインターキャストに関連するいくつかのスティッキーな問題があります。これは、基本クラスが派生クラスの最初のメンバーである限り安全です。多重継承ははるかに困難です-その場合、最初のクラス以外の基本クラス間でケースを行うには、適切なオフセットに基づいてポインターを手動で調整する必要があります。
もう1つできる(厄介な)ことは、実行時にオブジェクトの動的な型を変更することです!新しいvtableポインターを再割り当てするだけです。仮想機能の一部を選択的に変更し、その他を維持して、新しいハイブリッドタイプを作成することもできます。グローバルvtableを変更するのではなく、新しい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ファイルは各オブジェクトでできることを正確に定義しました。
Linux用のGNOMEデスクトップはオブジェクト指向Cで記述されており、" GObject "プロパティ、継承、ポリモーフィズム、および参照、イベント処理(「シグナル」と呼ばれる)、ランタイムタイピング、プライベートデータなどのその他の機能をサポートします。
これには、クラス階層での型キャストなどを行うためのプリプロセッサハックが含まれます。これは、GNOME用に作成したクラスの例です(gcharなどはtypedefです):
GObject構造体の内部には、GLibの動的型付けシステムのマジックナンバーとして使用されるGType整数があります(構造体全体を" GType"にキャストして型を見つけることができます)。
オブジェクトで呼び出されるメソッドを、暗黙的な「 this
」を関数に渡す静的メソッドと考えると、Cでのオブジェクト指向の考え方が容易になります。
例:
String s = "hi";
System.out.println(s.length());
なる:
string s = "hi";
printf(length(s)); // pass in s, as an implicit this
またはそのようなもの。
OOPが何であるかを知る前に、私はこの種のことをCで行っていました。
次の例は、最小サイズ、増分、最大サイズを指定すると、オンデマンドで成長するデータバッファを実装します。この特定の実装は「要素」でした。つまり、可変長のバイトバッファだけでなく、任意のCタイプのリストのようなコレクションを許可するように設計されています。
アイデアは、xxx_crt()を使用してオブジェクトをインスタンス化し、xxx_dlt()を使用してオブジェクトを削除するというものです。 「メンバー」のそれぞれメソッドは、操作するために特別に型指定されたポインターを受け取ります。
リンクリスト、サイクリックバッファ、およびその他の多くのものをこの方法で実装しました。
告白する必要があります。このアプローチで継承を実装する方法について考えたことはありません。 Kieveliが提供するもののいくつかのブレンドが良い道になると思います。
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は単にintのtypedefでした-長さはプラットフォームによって異なることを思い出させるために使用しました(移植のため)。
ffmpeg (ビデオ処理のツールキット)は、ストレートC(およびアセンブリ言語)で記述されていますが、オブジェクト指向スタイルを使用します。関数ポインターを持つ構造体でいっぱいです。適切な「メソッド」で構造体を初期化するファクトリ関数のセットがあります。ポインター。
本当に慎重に考えている場合、標準CライブラリでさえOOPを使用します-例として FILE *
を考慮してください: fopen()
は FILE *
オブジェクト、およびそれを使用するメンバーメソッド fscanf()
、 fprintf()
、 fread()
、 fwrite()
およびその他、最終的には fclose()
でファイナライズします。
同様に難しくない擬似目的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-to-Cトランスレータが使用されている場合、次のようなObjective-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];
}
Adam Rosenfieldが投稿したのは、CでOOPを行う正しい方法だと思います。彼が示しているのはオブジェクトの実装であると付け加えたいと思います。つまり、実際の実装は .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
ファイルで、プロトタイプのみを定義していることがわかります。その後、実装部分&quot;をコンパイルできます。 .c
ファイル&quot;静的または動的ライブラリに。これによりカプセル化が作成され、実装を自由に変更することもできます。オブジェクトのユーザーは、その実装についてほとんど何も知る必要がありません。これは、オブジェクトの全体的な設計にも焦点を当てています。
oopは、コード構造と再利用性を概念化する方法であり、オーバーロードやテンプレートなど、c ++に追加された他のこととはまったく関係がないという私の個人的な考えです。はい、これらは非常に便利な機能ですが、オブジェクト指向プログラミングが実際に何であるかを代表するものではありません。
私の推奨事項:シンプルにしてください。私が抱えている最大の問題の1つは、古いソフトウェア(場合によっては10年以上)の保守です。コードが単純ではない場合、困難になる可能性があります。はい、Cのポリモーフィズムを使用して非常に便利なOOPを作成できますが、読みにくい場合があります。
明確に定義された機能をカプセル化したシンプルなオブジェクトが好きです。これの素晴らしい例は、 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);
キーは次のとおりです。
- シンプルなアーキテクチャとデザインパターン
- 基本的なOOPカプセル化を実現します。
- 実装、読み取り、理解、および保守が簡単
Cを使用してオブジェクト指向スタイルでプログラミングする別の方法は、ドメイン固有の言語をCに変換するコードジェネレーターを使用することです。TypeScriptとJavaScriptを使用してOOPをjsに変換します。
#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のオブジェクト指向には次の機能が必要です。
-
カプセル化とデータの非表示(構造体/不透明ポインターを使用して実現可能)
-
多相性の継承とサポート(構造体を使用して単一の継承を実現できます-抽象ベースがインスタンス化できないことを確認してください)
-
コンストラクタおよびデストラクタの機能(簡単に実現できない)
-
型チェック(少なくともCは強制を行わないため、ユーザー定義型の場合)
-
参照カウント(または RAII を実装するもの)
-
例外処理(setjmpおよびlongjmp)の限定サポート
上記に加えて、ANSI / ISO仕様に依存する必要があり、コンパイラ固有の機能に依存するべきではありません。
http://ldeniau.web.cern.ch/をご覧くださいldeniau / html / oopc / oopc.html 。ドキュメントを読むことが他に何もなければ、啓発的な経験になります。
ここでのパーティーには少し遅れていますが、極端なマクロの両方を避けたいのです-コードが多すぎたり難しすぎたりしますが、いくつかの明らかなマクロはOOPコードの開発を容易にしますそして読む:
/*
* 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 、< a href = "https://github.com/plainC/wondermacros/blob/master/wondermacros/objects/x/object_instance.h" rel = "nofollow noreferrer"> 2 、 3 など)は、コードを実際のクラス構造体などに拡張します。宣言。
クラスを継承するには、 #define SUPER supername
を追加し、クラス定義の最初の行として supername__define \
を追加します。両方が存在する必要があります。 JSONサポート、シグナル、抽象クラスなどもあります。
オブジェクトを作成するには、 W_NEW(classname、.x = 1、.y = 2、...)
を使用します。初期化は、C11で導入された構造体の初期化に基づいています。うまく機能し、リストされていないものはすべてゼロに設定されます。
メソッドを呼び出すには、 W_CALL(o、method)(1,2,3)
を使用します。高階関数呼び出しのように見えますが、これは単なるマクロです。これは((o)-&gt; klass-&gt; method(o、1,2,3))
に展開されます。これは本当に素晴らしいハックです。
フレームワークには定型的なコードが必要なので、仕事をするPerlスクリプト(wobject)を作成しました。それを使用すれば、書くことができます
class Point
public int move_up(int steps)
public void draw()
read int x
read int y
そして、クラス仕様ファイル、クラスヘッダー、およびクラスを実装する Point_impl.c
を含むCファイルを作成します。単純なクラスが多数ある場合でも、多くの作業を節約できますが、それでもすべてがCにあります。 wobject は非常にシンプルな正規表現ベースのスキャナーで、特定のニーズに簡単に適応したり、ゼロから書き直したりできます。
オープンソースのDynaceプロジェクトはまさにそれを行います。 https://github.com/blakemcbride/Dynace
にあります。小さなコードを書く必要がある場合 これを試してください: 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);
}