C의 객체 지향


157

C에서 어떤 종류의 추악하지만 사용 가능한 객체 지향을 가능하게하는 멋진 전 처리기 핵 (ANSI C89 / ISO C90 호환) 세트는 무엇입니까?

몇 가지 다른 객체 지향 언어에 익숙하므로 "Learn C ++!"와 같은 답변으로 응답하지 마십시오. " ANSI C를 사용한 객체 지향 프로그래밍 "(주의 : PDF 형식 ) 및 기타 여러 가지 흥미로운 솔루션 을 읽었 지만 대부분 귀하의 관심에 관심이 있습니다. :-)!


참조 하면 C에서 객체 지향 코드를 작성할 수 있습니까?


1
나는 D를 배우고 C. 당신이 정말 필요로하는 곳에 대한 C 호환 ABI 사용에 응답 할 수 있습니다 digitalmars.com/d
팀 매튜스

2
@Dinah : "참고"에 감사드립니다. 그 소식은 흥미로웠다.

1
흥미로운 질문은 C에서 OOP의 전처리 프로세서 해킹을 원하는 이유 인 것 같습니다.
Calyth

3
@Calyth : OOP가 유용하고 "위에서 C 컴파일러 만 사용할 수있는 일부 임베디드 시스템에서 작업합니다"라는 것을 알게되었습니다. 또한 멋진 전 처리기 핵을 살펴 보는 것도 재미 있지 않습니까?

답변:


31

COS (C Object System) 가 유망하게 들립니다 (아직 알파 버전 임). 개방형 클래스, 메타 클래스, 속성 메타 클래스, 제네릭, 멀티 메소드, 위임, 소유권, 예외, 계약 및 폐쇄를 포함한 균일 한 객체 지향 프로그래밍을 통해 단순성과 유연성을 위해 사용 가능한 개념을 최소화하려고합니다. 이를 설명 하는 초안 (PDF)이 있습니다.

C의 예외는 다른 OO 언어에서 발견 된 TRY-CATCH-FINALLY의 C89 구현입니다. 테스트 슈트와 예제가 함께 제공됩니다.

둘 다 C의 OOP에서 많은 작업을하고있는 Laurent Deniau의 작품 입니다.


@vonbrand COS는 지난 커밋이 지난 여름에 github으로 마이그레이션되었습니다. 성숙은 커밋 부족을 설명 할 수 있습니다.
Philant

185

전 처리기 (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을 작성하면됩니다. 그렇지 않으면 주어진 유형의 모든 객체에 실수로 영향을 미칩니다.


6
Adam, 타입의 글로벌 vtable을 변경하는 재미는 C에서 오리 타이핑을 시뮬레이션하는 것입니다. :)
jmucchiello

이제 나는 C ++를 불쌍히 생각합니다 ... 물론 C ++ 구문이 더 명확하지만 사소한 구문이 아니므로 완화되었습니다. C ++과 C 사이의 하이브리드가 달성 될 수 있는지 궁금해 void *는 여전히 유효한 캐스트 유형입니다. 가있는 부분은 struct derived {struct base super;};바이트하여 올바른의 주문 때문에 그것이 어떻게 작동하는지 추측 분명하다.
jokoon

2
우아한 코드 +1, 잘 작성되었습니다. 이것이 바로 내가 찾던 것입니다!
Homunculus Reticulli

3
잘 했어. 이것은 내가 한 일과 정확히 일치하며 올바른 방법입니다. 구조체 / 객체에 대한 포인터를 염두에 두지 않고 정수 (주소)에 포인터를 전달해야합니다. 이를 통해 무제한 다형성 메소드 호출을 위해 모든 종류의 객체를 전달할 수 있습니다. 또한 누락 된 것은 구조체 (객체 / 클래스)를 초기화하는 함수입니다. 이것은 malloc 함수를 포함하고 포인터를 리턴합니다. C에서 메시지 전달 (

1
이것은 C ++을 깨뜨 렸고 C를 더 사용하기 위해 (상속을 위해 C ++ 만 사용하기 전에) 감사합니다
Anne Quinn

31

나는 한때 나에게 아주 우아한 방식으로 구현 된 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 파일은 각 객체로 할 수있는 것을 정확하게 정의했습니다.


"개체"의 내부가 모두 공개 된 것을 제외하고는이 솔루션이 정말 마음에 듭니다.
Lawrence Dol

6
@Software Monkey : C에는 액세스 제어가 없습니다. 구현 세부 정보를 숨기는 유일한 방법은 불투명 포인터를 통해 상호 작용하는 것입니다.이 방법은 인라인 할 수없는 접근 자 메서드를 통해 모든 필드에 액세스해야하므로 상당히 고통 스러울 수 있습니다.
Adam Rosenfield

1
@Adam : 링크 타임 최적화를 지원하는 컴파일러는 잘 인라인 할 것입니다.
Christoph

9
이 작업을 수행하는 경우 공용으로 정의되지 않은 .c 파일의 모든 함수가 정적으로 정의되어 오브젝트 파일에서 이름 지정된 함수로 끝나지 않아야합니다. 따라서 아무도 링크 단계에서 이름을 찾을 수 없습니다.
jmucchiello

2
@Marcel : 코드는 자율 시스템을위한 다양한 프로세서를 실행하는 저수준 보드에 배포되었으므로 C가 사용되었습니다. 그들은 모두 C에서 해당 네이티브 바이너리로의 컴파일을 지원했습니다. 이 방법을 사용하면 수행하려는 작업을 알게되면 코드를 매우 쉽게 읽을 수 있습니다.
Kieveli

18

Linux 용 그놈 데스크탑은 객체 지향 C로 작성되었으며, 속성, 상속, 다형성 및 참조, 이벤트 처리 ( "신호"라고 함), 런타임과 같은 다른 기능을 지원하는 " GObject " 라는 객체 모델을 가지고 있습니다. 타이핑, 개인 정보 등

여기에는 클래스 계층 구조에서 typecasting과 같은 작업을 수행하는 전 처리기 해킹이 포함되어 있습니다. 다음은 그놈에 대해 작성한 클래스의 예입니다 (gchar와 같은 것들은 typedef입니다).

클래스 소스

클래스 헤더

GObject 구조에는 GLib의 동적 타이핑 시스템을위한 매직 넘버로 사용되는 GType 정수가 있습니다 (전체 구조체를 "GType"으로 캐스팅하여 유형을 찾을 수 있습니다).


불행히도, 읽기 / 튜토리얼 파일 (wiki 링크)이 작동하지 않으며 그에 대한 참조 매뉴얼 만 있습니다 (GTK가 아닌 GObject에 대해 이야기하고 있습니다). 동일한 튜토리얼 파일을 제공하십시오 ...
FL4SOF

링크가 수정되었습니다.
제임스 케이프

4
링크가 다시 끊어졌습니다.
SeanRamey

6

나는 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였습니다. 길이가 플랫폼마다 다를 수 있음을 상기시키기 위해 사용했습니다 (포팅).


7
거룩한 몰리, 이것은 난독 화 C 콘테스트를 이길 수 있습니다! 나는 그것을 좋아한다! :)
horseyguy

@horseyguy 아니에요. 출판되었습니다. 또한 iocccsize 도구에 대한 헤더 파일 남용을 고려합니다. 또한 완전한 프로그램이 아닙니다. 2009 년에는 컨테스트가 없었으므로 iocccsize를 비교할 수 없습니다. CPP는 여러 번 학대를 받았기 때문에 상당히 오래되었습니다. 기타 죄송합니다. 그러나 나는 현실적이기 때문에 부정적인 것이 아니다. 나는 당신의 의미를 얻습니다. 그리고 그것은 잘 읽었으며 나는 그것을 투표했습니다. (그리고 나는 그것에 참여하고 또한 나도 이긴다.)
Pryftan

6

약간의 주제는 아니지만 원래 C ++ 컴파일러 인 Cfront 는 C ++를 C로 컴파일 한 다음 어셈블러로 컴파일했습니다.

여기에 보존 됩니다 .


실제로 전에 본 적이 있습니다. 나는 그것이 좋은 작품이라고 생각합니다.

@Anthony Cuozzo : Stan Lippman은 'C ++-Inside the object model'이라는 훌륭한 책을 썼습니다. 그것은 여전히 ​​잘 읽었으며 몇 년 전 C에서 C ++로 전환 할 때 엄청나게 도움이되었습니다.
zebrabox

5

객체에서 호출 된 메소드를 this함수에 암시적인 ' '를 전달하는 정적 메소드 로 생각하면 C에서 OO를 쉽게 생각할 수 있습니다.

예를 들면 다음과 같습니다.

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

된다 :

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

아니면 그런 것.


6
@Artelius : 물론, 명백한 경우는 분명합니다. 이것을 위해 +1.
Lawrence Dol

1
더 나은 아직 것입니다string->length(s);
OozeMeister

4

ffmpeg (비디오 처리 용 툴킷)는 직선 C (및 어셈블리 언어)로 작성되지만 객체 지향 스타일을 사용합니다. 함수 포인터가있는 구조체로 가득합니다. 적절한 "메소드"포인터로 구조체를 초기화하는 팩토리 함수 세트가 있습니다.


나는 그것의 공장 기능을 보지 못하고 (ffmpeg), 다형성 / 상속을 사용하지 않는 것 같습니다 (위에서 제안 된 사소한 방법).
FL4SOF

avcodec_open은 하나의 팩토리 함수입니다. 함수 포인터를 AVCodecContext 구조체 (draw_horiz_band와 같은)에 넣습니다. avcodec.h에서 FF_COMMON_FRAME 매크로 사용법을 보면 데이터 멤버의 상속과 비슷한 것을 볼 수 있습니다. IMHO, ffmpeg는 OOP가 C가 아닌 C ++에서 가장 잘 수행됨을 증명합니다.
Mr Fooz

3

- 당신이 정말로 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))합니까?
AE Drew

1
바이너리가 메모리에로드 될 때 표시된 함수가 호출되도록하는 GCC 확장입니다. @AEDrew
Maxthon Chan

popen(3)FILE *또 다른 예를 위해를 반환합니다 .
Pryftan

3

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파일 에서 프로토 타입 만 정의 하고 있음을 알 수 있습니다 . 그런 다음 구현 부분 " .cfile"을 정적 또는 동적 라이브러리로 컴파일 할 수 있습니다 . 이렇게하면 캡슐화가 생성되며 원하는대로 구현을 변경할 수 있습니다. 객체의 사용자는 객체의 구현에 대해 거의 알 필요가 없습니다. 또한 객체의 전체 디자인에 중점을 둡니다.

oop은 코드 구조와 재사용 성을 개념화하는 방법이며 과부하 또는 템플릿과 같이 C ++에 추가 된 다른 것들과는 아무런 관련이 없다고 생각합니다. 그렇습니다. 이들은 매우 유용한 유용한 기능이지만 객체 지향 프로그래밍이 실제로 무엇인지 나타내는 것은 아닙니다.


구조체를 typedef struct Monkey {} Monkey; 생성 한 후 typedef 's point는 무엇입니까?를 사용 하여 구조체를 선언 할 수 있습니다 .
MarcusJ

1
@MarcusJ 이것은 struct _monkey단순히 프로토 타입입니다. 실제 유형 정의는 구현 파일 (.c 파일)에 정의되어 있습니다. 이렇게하면 캡슐화 효과가 만들어지고 API 개발자가 API를 수정하지 않고도 향후 원숭이 구조를 재정의 할 수 있습니다. API 사용자는 실제 메소드에만 관심이 있으면됩니다. API 디자이너는 객체 / 구조물 배치 방법을 포함하여 구현을 관리합니다. 따라서 객체 / 구조체의 세부 정보는 사용자 (불투명 한 유형)에서 숨겨집니다.

헤더에 구조체를 정의합니다. 표준이 아닙니까? 글쎄, 나는 때때로 그 라이브러리 외부의 구조체 멤버에 액세스해야하기 때문에 그렇게합니다.
MarcusJ

1
@MarcusJ 원하는 경우 헤더에 구조체를 정의 할 수 있습니다 (표준 없음). 그러나 내부 구조를 변경하려는 경우 코드가 손상 될 수 있습니다. 캡슐화는 코드를 손상시키지 않고 구현을 쉽게 변경할 수있는 코딩 스타일 일뿐입니다. int getCount(ObjectType obj)구현 파일에서 구조체를 정의하기로 선택한 경우 etc 등의 접근 자 메서드를 통해 멤버에 항상 액세스 할 수 있습니다 .

2

내 추천 : 단순하게 유지하십시오. 내가 가지고있는 가장 큰 문제 중 하나는 오래된 소프트웨어 (때로는 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);

열쇠는 다음과 같습니다.

  1. 간단한 건축과 디자인 패턴
  2. 기본 OOP 캡슐화를 달성합니다.
  3. 손쉬운 구현, 읽기, 이해 및 유지 보수

1

CI로 OOP를 쓰려고한다면 아마도 의사 Pimpl 디자인 이 될 것입니다 . 포인터를 구조체에 전달하는 대신 포인터를 구조체에 전달합니다. 이것은 내용을 불투명하게 만들고 다형성과 상속을 용이하게합니다.

C에서 OOP의 실제 문제는 변수가 범위를 벗어날 때 발생하는 것입니다. 컴파일러에서 생성 한 소멸자가 없으므로 문제가 발생할 수 있습니다. 매크로 가 도움이 될 수 있지만 항상보기에는 추악합니다.


1
C로 프로그래밍 할 때 if문 을 사용 하여 마지막에 릴리스 하여 범위를 처리 합니다. 예 :if ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }

1

C를 사용하여 객체 지향 스타일로 프로그래밍하는 또 다른 방법은 도메인 특정 언어를 C로 변환하는 코드 생성기를 사용하는 것입니다. TypeScript 및 JavaScript를 사용하여 OOP를 js로 가져옵니다.


0
#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 우리는 공통된 측면을 가지지 만 다른 구현 (다형성)을 갖는 상속 체인의 객체를 조작하는 데만 사용하기 때문에 인스턴스화되지 않습니다.


0

@Adam Rosenfield는 C로 OOP를 달성하는 방법에 대해 매우 잘 설명했습니다.

게다가, 나는 당신이 읽는 것이 좋습니다

1) pjsip

VoIP를위한 아주 좋은 C 라이브러리. 구조체와 함수 포인터 테이블을 통해 OOP를 달성하는 방법을 배울 수 있습니다

2) iOS 런타임

iOS 런타임이 Objective C를 강화하는 방법 알아보기 isa pointer, meta class를 통해 OOP 달성


0

나를 위해 C의 객체 방향에는 다음 기능이 있어야합니다.

  1. 캡슐화 및 데이터 숨기기 (구조체 / 불투명 포인터를 사용하여 수행 할 수 있음)

  2. 다형성에 대한 상속과 지원

  3. 생성자 및 소멸자 기능 (쉽게 달성 할 수 없음)

  4. 유형 검사 (C가 강제하지 않는 사용자 정의 유형의 경우)

  5. 참조 카운트 (또는 RAII 구현 )

  6. 예외 처리에 대한 제한된 지원 (setjmp 및 longjmp)

이 외에도 ANSI / ISO 사양을 사용해야하며 컴파일러 관련 기능에 의존해서는 안됩니다.


숫자 (5)의 경우-소멸자가없는 언어로 RAII를 구현할 수 없습니다 (RAII는 C 또는 Java에서 컴파일러 지원 기술이 아님).
Tom

생성자와 소멸자는 c 기반 객체로 작성할 수 있습니다-GObject 가하는 것 같습니다. 그리고 물론 RAAI (직접적이지 않고 추악하고 전혀 실용적이지 않을 수도 있음)-위의 내용을 달성하기 위해 C 기반 의미를 식별하는 것이 전부였습니다.
FL4SOF

C는 소멸자를 지원하지 않습니다. 작동하려면 무언가 를 입력 해야합니다. 그것은 그들이 스스로 청소하지 않는다는 것을 의미합니다. GObject는 언어를 변경하지 않습니다.
Tom

0

http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html을 보십시오 . 문서를 읽는 것이 아무것도 없다면 깨달은 경험입니다.


3
공유중인 링크에 대한 컨텍스트를 제공하십시오. 공유 한 링크가 실제로 도움이 될 수 있지만 질문에 응답하는 공유 기사의 주요 측면을 캡처하는 것이 좋습니다. 이렇게하면 링크가 제거 된 경우에도 답변이 관련성이 있고 도움이됩니다.
ishmaelMakitla

0

나는 파티에 조금 늦었지만 두 가지 매크로 극단 을 피하고 싶습니다. 너무 많은 코드를 모호하게 만듭니다.하지만 몇 가지 명백한 매크로로 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 옵션으로)가 혼란 대신 도움이됩니다. 요점은 프로그래머의 생산성을 향상시키는 것입니다.



0

또한 매크로 솔루션을 기반 으로이 작업을 수행하고 있습니다. 그래서 그것은 용감한 사람만을위한 것입니다. ;-) 그러나 그것은 이미 훌륭하고 이미 그 위에 몇 가지 프로젝트를 진행하고 있습니다. 먼저 각 클래스에 대해 별도의 헤더 파일을 정의하도록 작동합니다. 이처럼 :

#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 는 특정 요구에 쉽게 적응하거나 처음부터 다시 작성할 수있는 매우 간단한 정규식 기반 스캐너입니다.



0

C에서 OOP를위한 프로그래머 친화적 인 프레임 워크 인 COOP 를 시험해 볼 수 있으며 클래스, 예외, 다형성 및 메모리 관리 (임베디드 코드에 중요) 기능이 있습니다. 비교적 가벼운 구문 입니다. Wiki자습서를 참조하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.