C에서 어떤 종류의 추악하지만 사용 가능한 객체 지향을 가능하게하는 멋진 전 처리기 핵 (ANSI C89 / ISO C90 호환) 세트는 무엇입니까?
몇 가지 다른 객체 지향 언어에 익숙하므로 "Learn C ++!"와 같은 답변으로 응답하지 마십시오. " ANSI C를 사용한 객체 지향 프로그래밍 "(주의 : PDF 형식 ) 및 기타 여러 가지 흥미로운 솔루션 을 읽었 지만 대부분 귀하의 관심에 관심이 있습니다. :-)!
C에서 어떤 종류의 추악하지만 사용 가능한 객체 지향을 가능하게하는 멋진 전 처리기 핵 (ANSI C89 / ISO C90 호환) 세트는 무엇입니까?
몇 가지 다른 객체 지향 언어에 익숙하므로 "Learn C ++!"와 같은 답변으로 응답하지 마십시오. " ANSI C를 사용한 객체 지향 프로그래밍 "(주의 : PDF 형식 ) 및 기타 여러 가지 흥미로운 솔루션 을 읽었 지만 대부분 귀하의 관심에 관심이 있습니다. :-)!
답변:
COS (C Object System) 가 유망하게 들립니다 (아직 알파 버전 임). 개방형 클래스, 메타 클래스, 속성 메타 클래스, 제네릭, 멀티 메소드, 위임, 소유권, 예외, 계약 및 폐쇄를 포함한 균일 한 객체 지향 프로그래밍을 통해 단순성과 유연성을 위해 사용 가능한 개념을 최소화하려고합니다. 이를 설명 하는 초안 (PDF)이 있습니다.
C의 예외는 다른 OO 언어에서 발견 된 TRY-CATCH-FINALLY의 C89 구현입니다. 테스트 슈트와 예제가 함께 제공됩니다.
둘 다 C의 OOP에서 많은 작업을하고있는 Laurent Deniau의 작품 입니다.
전 처리기 (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
다형성 (즉, 가상 함수)을 얻으려면 함수 포인터를 사용하고 선택적으로 가상 테이블 또는 가상 테이블이라고도하는 함수 포인터 테이블을 사용합니다.
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 포인터를 다시 할당하기 만하면됩니다. 가상 기능 중 일부를 선택적으로 변경하면서 다른 기능을 유지하면서 새로운 하이브리드 유형을 만들 수도 있습니다. 전역 vtable을 수정하는 대신 새 vtable을 작성하면됩니다. 그렇지 않으면 주어진 유형의 모든 객체에 실수로 영향을 미칩니다.
struct derived {struct base super;};
바이트하여 올바른의 주문 때문에 그것이 어떻게 작동하는지 추측 분명하다.
나는 한때 나에게 아주 우아한 방식으로 구현 된 C 라이브러리로 작업했다. 그들은 C로 객체를 정의하는 방법을 작성한 다음 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 용 그놈 데스크탑은 객체 지향 C로 작성되었으며, 속성, 상속, 다형성 및 참조, 이벤트 처리 ( "신호"라고 함), 런타임과 같은 다른 기능을 지원하는 " GObject " 라는 객체 모델을 가지고 있습니다. 타이핑, 개인 정보 등
여기에는 클래스 계층 구조에서 typecasting과 같은 작업을 수행하는 전 처리기 해킹이 포함되어 있습니다. 다음은 그놈에 대해 작성한 클래스의 예입니다 (gchar와 같은 것들은 typedef입니다).
GObject 구조에는 GLib의 동적 타이핑 시스템을위한 매직 넘버로 사용되는 GType 정수가 있습니다 (전체 구조체를 "GType"으로 캐스팅하여 유형을 찾을 수 있습니다).
나는 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);
추신 : vint는 단순히 int의 typedef였습니다. 길이가 플랫폼마다 다를 수 있음을 상기시키기 위해 사용했습니다 (포팅).
객체에서 호출 된 메소드를 this
함수에 암시적인 ' '를 전달하는 정적 메소드 로 생각하면 C에서 OO를 쉽게 생각할 수 있습니다.
예를 들면 다음과 같습니다.
String s = "hi";
System.out.println(s.length());
된다 :
string s = "hi";
printf(length(s)); // pass in s, as an implicit this
아니면 그런 것.
string->length(s);
ffmpeg (비디오 처리 용 툴킷)는 직선 C (및 어셈블리 언어)로 작성되지만 객체 지향 스타일을 사용합니다. 함수 포인터가있는 구조체로 가득합니다. 적절한 "메소드"포인터로 구조체를 초기화하는 팩토리 함수 세트가 있습니다.
- 당신이 정말로 catefully 생각하는 경우에도 표준 C 라이브러리를 사용 OOP 고려 FILE *
예를 들어 : fopen()
초기화 FILE *
개체를 당신은 회원의 방법을 사용 사용 fscanf()
, fprintf()
, fread()
, fwrite()
등을, 결국 그것을 마무리 fclose()
.
pseudo-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-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];
}
__attribute__((constructor))
을 void __meta_Foo_init(void) __attribute__((constructor))
합니까?
popen(3)
FILE *
또 다른 예를 위해를 반환합니다 .
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
파일 에서 프로토 타입 만 정의 하고 있음을 알 수 있습니다 . 그런 다음 구현 부분 " .c
file"을 정적 또는 동적 라이브러리로 컴파일 할 수 있습니다 . 이렇게하면 캡슐화가 생성되며 원하는대로 구현을 변경할 수 있습니다. 객체의 사용자는 객체의 구현에 대해 거의 알 필요가 없습니다. 또한 객체의 전체 디자인에 중점을 둡니다.
oop은 코드 구조와 재사용 성을 개념화하는 방법이며 과부하 또는 템플릿과 같이 C ++에 추가 된 다른 것들과는 아무런 관련이 없다고 생각합니다. 그렇습니다. 이들은 매우 유용한 유용한 기능이지만 객체 지향 프로그래밍이 실제로 무엇인지 나타내는 것은 아닙니다.
typedef struct Monkey {} Monkey;
생성 한 후 typedef 's point는 무엇입니까?를 사용 하여 구조체를 선언 할 수 있습니다 .
struct _monkey
단순히 프로토 타입입니다. 실제 유형 정의는 구현 파일 (.c 파일)에 정의되어 있습니다. 이렇게하면 캡슐화 효과가 만들어지고 API 개발자가 API를 수정하지 않고도 향후 원숭이 구조를 재정의 할 수 있습니다. API 사용자는 실제 메소드에만 관심이 있으면됩니다. API 디자이너는 객체 / 구조물 배치 방법을 포함하여 구현을 관리합니다. 따라서 객체 / 구조체의 세부 정보는 사용자 (불투명 한 유형)에서 숨겨집니다.
내 추천 : 단순하게 유지하십시오. 내가 가지고있는 가장 큰 문제 중 하나는 오래된 소프트웨어 (때로는 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);
열쇠는 다음과 같습니다.
#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로 OO 프로그래밍이 무엇인지 보여줍니다.
이것은 전 처리기 매크로가없는 실제 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 옵션으로)가 혼란 대신 도움이됩니다. 요점은 프로그래머의 생산성을 향상시키는 것입니다.
작은 코드를 작성 해야하는 경우 다음을 시도하십시오 : 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);
}
또한 매크로 솔루션을 기반 으로이 작업을 수행하고 있습니다. 그래서 그것은 용감한 사람만을위한 것입니다. ;-) 그러나 그것은 이미 훌륭하고 이미 그 위에 몇 가지 프로젝트를 진행하고 있습니다. 먼저 각 클래스에 대해 별도의 헤더 파일을 정의하도록 작동합니다. 이처럼 :
#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에 도입 된 구조체 초기화를 기반으로합니다. 잘 작동하고 나열되지 않은 모든 것이 0으로 설정됩니다.
메소드를 호출하려면을 사용하십시오 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
클래스 스펙 파일, 클래스 헤더 및 Point_impl.c
클래스를 구현하는 위치를 포함하는 C 파일을 작성합니다 . 간단한 클래스가 많지만 여전히 모든 것이 C 인 경우 상당히 많은 작업을 절약 할 수 있습니다. wobject 는 특정 요구에 쉽게 적응하거나 처음부터 다시 작성할 수있는 매우 간단한 정규식 기반 스캐너입니다.
오픈 소스 Dynace 프로젝트는 정확히 그렇게합니다. 그것은에서의 https://github.com/blakemcbride/Dynace