C로 객체 지향 코드를 작성하는 방법은 무엇입니까? [닫은]


500

C로 객체 지향 코드를 작성하는 몇 가지 방법은 무엇입니까? 특히 다형성과 관련하여.


이 스택 오버플로 질문 Object-orientation in C 도 참조하십시오 .


1
Laurent Deniau가 <a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> C에서 객체 지향 프로그래밍 </a>


25
@Camilo 마틴 : 나는 의도적으로 질문 할 수 없습니다 해야 . 실제로 C에서 OOP를 사용하는 데 관심이 없습니다. 그러나 C에서 OO 솔루션을 보면 C의 한계 및 / 또는 유연성과 다형성을 구현하고 사용하는 창의적인 방법에 대해 더 많이 배우게됩니다.
Dinah

5
OO는 단지 패턴입니다. 여기에서 확인하십시오. .bat 파일 에서 수행 할 수도 있습니다 : dirk.rave.org/chap9.txt (모든 패턴은 관심이 있다면 프로그래밍 언어에 적용 할 수 있습니다). 그러나 이것은 좋은 음식입니다. 그리고 그러한 패턴이없는 언어에 당연한 패턴을 적용함으로써 많은 것을 배울 수있을 것입니다.
Camilo Martin

6
GTK- '나를 GObject라고 부르십시오-실제로 C에서 OOP (Sorta)의 좋은 예입니다.
new123456

답변:


362

예. 실제로 Axel Schreiner는 자신의 저서 "ANSI-C의 객체 지향 프로그래밍"을 무료로 제공하며이 주제를 매우 철저히 다루고 있습니다.


28
이 책의 개념은 견고하지만 유형 안전성을 잃게됩니다.
diapir 2016 년

22
우리가 디자인 패턴으로 알려진 것은 "객체 방향"으로 알려진 디자인 패턴이었습니다. 가비지 수집 및 기타와 동일합니다. 그것들은 지금 너무 깊이 뿌리 내리고 있습니다. 우리는 그들이 처음 고안되었을 때, 오늘날 우리가 디자인 패턴으로 생각하는 것과 거의 같은 방식
이었습니다

11
저자의 사이트에서 직접 얻을 수 있습니다 : cs.rit.edu/~ats/books/ooc.pdf 같은 저자의 다른 논문 : cs.rit.edu/~ats/books/index.html
pakman

10
적절한 콜렉션 (도서 + 소스 코드 예제)이 rit.edu 인덱스에서 사용할 수 ANSI-C와 객체 지향 프로그래밍
데이비드 C. 순위

3
이 책은 동료 검토입니까? 첫 페이지 첫 번째 단락의 첫 문장에는 오타가 있습니다.
Dagrooms

343

다형성에 대해 이야기하고 있기 때문에 C ++이 나오기 몇 년 전에 그런 일을하고있었습니다.

기본적으로 a struct를 사용 하여 데이터와 함수 포인터 목록을 모두 보유하여 해당 데이터의 관련 기능을 가리 킵니다.

따라서 통신 클래스에서는 객체에 대한 데이터와 함께 구조에서 4 개의 함수 포인터로 유지되는 공개, 읽기, 쓰기 및 닫기 호출이 있습니다.

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

물론, 위의 코드 세그먼트는 실제로 다음과 같은 "생성자"에 있습니다 rs232Init().

해당 클래스에서 '상속'할 때 포인터를 자신의 함수를 가리 키도록 변경하면됩니다. 이러한 함수를 호출 한 사람은 모두 함수 포인터를 통해 다형성을 얻을 수 있습니다.

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

수동 vtable과 같습니다.

포인터를 NULL로 설정하여 가상 클래스를 가질 수도 있습니다. 동작은 C ++ (컴파일 타임 오류가 아니라 런타임시 코어 덤프)과 약간 다릅니다.

다음은이를 보여주는 샘플 코드입니다. 먼저 최상위 클래스 구조 :

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

그런 다음 TCP 'subclass'에 대한 함수가 있습니다.

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

그리고 HTTP도 마찬가지입니다 :

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

마지막으로 테스트 프로그램이 실제로 작동하는지 보여줍니다.

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

출력이 생성됩니다.

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

하위 클래스에 따라 다른 함수가 호출되고 있음을 알 수 있습니다.


52
캡슐화는 매우 쉽고, 다형성은 가능하지만 상속은 까다 롭습니다
Martin Beckett

5
lwn.net은 최근 위의 답변과 유사한 구조, 즉 함수 포인터를 포함하는 구조체 또는 포인터를 가져 오는 함수를 가진 구조체에 대한 포인터 에 대해 커널Object Oriented design Patterns 라는 제목의 기사를 게시했습니다 . 우리가 작업하는 데이터를 매개 변수로 사용하십시오.
radicalmatt

11
+1 좋은 예! 누군가가 실제로이 길을 가고 싶어하지만 "인스턴스"구조체에는 "가상 테이블"인스턴스를 가리키는 단일 필드 를 갖는 것이 더 적합 할 것 입니다. 여기에는 해당 유형의 모든 가상 기능이 한 곳에 있습니다. 즉 , tCommClass이름이로 바뀌고 구조체에는 데이터 필드와 단일 필드가 " 단일 "가상 테이블을 가리키는 것만 있습니다. 각 인스턴스와 함께 모든 포인터를 수행하면 불필요한 오버 헤드가 추가되고 C ++, IMHO보다 JavaScript에서 작업하는 방법과 더 유사합니다. tCommVTtCommClasstCommVT vt
Groo

1
그래서 이것은 단일 인터페이스의 구현을 보여 주지만 여러 인터페이스를 구현하는 것은 어떻습니까? 아니면 다중 상속?
weberc2

Weber, C ++의 모든 기능을 원한다면 아마도 C ++을 사용해야합니다. 이 질문은 다형성, 물체가 다른 "형태"를 취하는 능력에 대해 구체적으로 물었습니다. C에서 인터페이스와 다중 상속을 확실히 할 수는 있지만 상당한 추가 작업이 필요하며 C ++ 내장 기능을 사용하는 대신 스마트를 직접 관리해야합니다.
paxdiablo

86

네임 스페이스는 종종 다음을 수행하여 수행됩니다.

stack_push(thing *)

대신에

stack::push(thing *)

C 구조체를 C ++ 클래스 와 같은 것으로 만들려면 다음을 수행하십시오.

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

으로

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

그리고 :

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

소멸자 또는 삭제하지 않았지만 동일한 패턴을 따릅니다.

this_is_here_as_an_example_only는 유형의 모든 인스턴스에서 공유되는 정적 클래스 변수와 같습니다. 일부는 이것을 취하는 것을 제외하고는 모든 메소드는 실제로 정적입니다 *


1
@nategoose- st->my_type->push(st, thing2);대신st->my_type.push(st, thing2);
Fabricio

@nategoose : 대신 OR struct stack_type my_type;struct stack_type * my_type;
Fabricio

3
나는 수업을위한 구조체를 갖는 개념을 좋아한다. 그러나 일반적인 Class구조체는 어떻습니까? OO C를 C ++보다 역동적으로 만들 수 있습니다. 어떻게에 대한? 그건 그렇고, +1.
Linuxios

54

C에서 OOP를 구현하는 것은 OOP를 구현하는 것이 OOP를 배우고 내부 작업을 이해 하는 훌륭한 방법이라고 생각합니다 . 많은 프로그래머의 경험에 따르면 기술을 효율적이고 자신있게 사용하려면 프로그래머가 기본 개념이 어떻게 구현되는지 이해해야합니다. C에서 클래스, 상속 및 다형성을 에뮬레이트하면 바로 이것을 가르칩니다.

원래 질문에 대답하기 위해 C에서 OOP를 수행하는 방법을 가르치는 몇 가지 자료가 있습니다.

EmbeddedGurus.com 블로그 게시물 "C의 객체 기반 프로그래밍"은 이식 가능한 C에서 클래스 및 단일 상속을 구현하는 방법을 보여줍니다. http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

애플리케이션 노트 ""C + "-C의 객체 지향 프로그래밍"은 전 처리기 매크로를 사용하여 C에서 클래스, 단일 상속 및 후기 바인딩 (다형성)을 구현하는 방법을 보여줍니다 ( http://www.state-machine.com/resources/cplus_3). 0_manual.pdf 의 예제 코드는 http://www.state-machine.com/resources/cplus_3.0.zip 에서 볼 수 있습니다 .


4
C + 매뉴얼의 새로운 URL : state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

나는 그것을 보았다. 나는 그것을 추천하지 않을 것입니다. C ++은 원래 C 코드를 중간 단계로 생성하는 전처리기로 시작되었습니다.

본질적으로 당신이하는 일은 함수 참조를 저장하는 모든 메소드에 대한 디스패치 테이블을 만드는 것입니다. 클래스를 파생 시키려면이 디스패치 테이블을 복사하고 재정의하려는 항목을 대체하고 새 "메소드"가 기본 메소드를 호출하려는 경우 원래 메소드를 호출해야합니다. 결국 C ++을 다시 작성하게됩니다.


5
"결국 C ++을 다시 작성하게되었습니다"라고 생각합니다.
Dinah

39
또는 Objective C를 다시 작성하면 훨씬 더 매력적인 결과를 얻을 수 있습니다.
Falken 교수 계약

3
전문가는 "우리는 비슷한 객체를 많이 만들 때 클래스가 필요하지 않습니다." 와 같이 Javascript 와 같이 클래스가없는 OOP의 풍미가 있습니다. 그러나 나는 이것이 C에서 달성하기 쉽지 않다는 것을 두려워한다. (struct를 복제하는 clone () 루틴이 있습니까?)
Lumi

1
실제로했던 또 다른 스마트 남자, 구현하고 그 구현을 그와 만드는 빠른 (구글, V8 엔진) 완료 모든 자바 스크립트 뒷면에 추가 (숨겨진) 수업을 할 수 있습니다.
cubuspl42

glib객관적인 방식으로 C로 작성 되지 않습니까?
kravemir

26

물론 가능합니다. 이것이 GTK +그놈 이 기반으로 하는 프레임 워크 인 GObject가하는 일 입니다.


그러한 접근 방식의 장단점은 무엇입니까? 즉. C ++를 사용하여 작성하는 것이 훨씬 쉽습니다.
kravemir

@kravemir 글쎄, C ++는 C만큼 이식성이 떨어지기 때문에 C ++을 다른 C ++ 컴파일러가 컴파일 할 수있는 코드에 연결하는 것이 조금 더 어렵다. 그러나 GObject가 실제로 그렇게 어렵지는 않지만 C ++로 클래스를 작성하는 것이 더 쉽습니다 (작은 보일러 플레이트를 신경 쓰지 않는다고 가정).
Edwin Buck

17

C stdio FILE 서브 라이브러리는 성인 C에서 추상화, 캡슐화 및 모듈화를 작성하는 방법에 대한 훌륭한 예입니다.

상속과 다형성 (OOP에 필수적으로 고려되는 다른 측면)은 반드시 생산성 향상을 제공 할 필요는 없으며 실제로 문제 영역에 대한 개발과 사고를 방해 할 수 있다는 합리적인 주장이루어졌습니다 .


커널 레이어에서 stdio가 추상화되지 않습니까? 내가 실수하지 않으면, C- 라이브러리는 그것들을 문자 파일 / 장치로 취급하고 커널 드라이버가 작업을 수행합니다.
kravemir

15

Animal and Dog의 간단한 예 : C ++의 vtable 메커니즘을 반영합니다 (대부분). 또한 malloc ()을 여러 번 호출하지 않도록 할당과 인스턴스화 (Animal_Alloc, Animal_New)를 분리합니다. 또한 this포인터를 명시 적으로 전달해야합니다 .

비가 상 기능을 수행한다면, 그것은 사소한 일입니다. vtable에 추가하지 않고 정적 함수에는 this포인터가 필요하지 않습니다 . 다중 상속에는 일반적으로 모호성을 해결하기 위해 다중 vtable이 필요합니다.

또한 setjmp / longjmp를 사용하여 예외 처리를 수행 할 수 있어야합니다.

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

추신. 이것은 C ++ 컴파일러에서 테스트되었지만 C 컴파일러에서 쉽게 작동하도록해야합니다.


typedefA는 내부에 structC로 할 수 없습니다
마수드

13

GObject를 확인하십시오 . 그것은 C에서 OO이며 당신이 찾고있는 것의 한 구현입니다. OO를 정말로 원한다면 C ++ 또는 다른 OOP 언어를 사용하십시오. OO 언어를 다루는 데 익숙하다면 GObject는 때때로 다루기가 어려울 수 있지만, 다른 방법과 마찬가지로 규칙과 흐름에 익숙해 질 것입니다.


12

이것은 흥미로웠다. 나는 같은 질문을 스스로 숙고 해 왔으며 그것에 대해 생각할 때의 이점은 다음과 같습니다.

  • 비 OOP 언어로 OOP 개념을 구현하는 방법을 상상하려고하면 OOp 언어 (제 경우 C ++)의 장점을 이해할 수 있습니다. 이를 통해 특정 유형의 응용 프로그램에 대해 C 또는 C ++를 사용할지 여부를 판단 할 수 있습니다. 한 응용 프로그램의 이점이 다른 응용 프로그램보다 중요합니다.

  • 이것에 대한 정보와 의견을 웹에서 탐색하면서 임베디드 프로세서 용 코드를 작성하고 C 컴파일러 만 사용할 수있는 저자를 발견했습니다. http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C- 창조-클래스-파트 -1

그의 경우에는 일반 C에서 OOP 개념을 분석하고 적용하는 것이 타당했습니다. C에서 구현하려고 시도한 결과 오버 헤드 히트로 인해 일부 OOP 개념을 희생 할 수있는 것으로 보입니다.

내가 얻은 교훈은, 어느 정도 할 수 있으며, 시도해야 할 몇 가지 이유가 있습니다.

결국, 머신은 스택 포인터 비트를 돌리면서 프로그램 카운터를 건너 뛰고 메모리 액세스 작업을 계산합니다. 효율성 측면에서 볼 때, 귀하의 프로그램이 수행하는 계산의 수가 적을수록 좋습니다. 그러나 때때로 우리는이 세금을 단순히 지불해야만 인적 오류에 가장 취약한 방식으로 프로그램을 구성 할 수 있습니다. OOP 언어 컴파일러는 두 가지 측면을 모두 최적화하려고 노력합니다. 프로그래머는 이러한 개념을 C와 같은 언어로 구현하는 데 훨씬 신중해야합니다.


10

Core Foundation API 세트에 대한 Apple의 설명서를 보는 것이 도움이 될 수 있습니다. 순수한 C API이지만 많은 유형이 Objective-C 객체와 동등합니다.

Objective-C 자체의 디자인을 보는 것이 도움이 될 수도 있습니다. 객체 시스템이 C 함수와 관련하여 정의된다는 점에서 C ++과는 조금 다릅니다. 예 objc_msg_send를 들어 객체에서 메소드를 호출하는 것입니다. 컴파일러는 대괄호 구문을 해당 함수 호출로 변환하므로 알 필요는 없지만 질문을 고려하면 후드에서 어떻게 작동하는지 배우는 것이 유용 할 수 있습니다.


10

사용할 수있는 몇 가지 기술이 있습니다. 가장 중요한 것은 프로젝트를 나누는 방법입니다. 프로젝트에서 .h 파일로 선언 된 인터페이스와 .c 파일에서 객체의 구현을 사용합니다. 중요한 부분은 .h 파일을 포함하는 모든 모듈이 객체 만으로 간주 void *하고 .c 파일은 구조의 내부를 아는 유일한 모듈이라는 것입니다.

예를 들어 FOO라는 클래스의 클래스는 다음과 같습니다.

.h 파일에서

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

C 구현 파일은 그런 식입니다.

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

따라서 해당 모듈의 모든 기능에 대한 객체에 대한 포인터를 명시 적으로 제공합니다. C ++ 컴파일러는 암시 적으로 수행하며 C에서는 명시 적으로 작성합니다.

this내 프로그램에서 C ++로 컴파일되지 않도록하고 구문 강조 편집기에서 다른 색상의 훌륭한 속성을 갖도록하기 위해 실제로 프로그램에서 사용 합니다.

FOO_struct의 필드는 한 모듈에서 수정 될 수 있으며 다른 모듈은 여전히 ​​사용 가능하도록 다시 컴파일 할 필요가 없습니다.

이러한 스타일로 OOP (데이터 캡슐화)의 이점 중 상당 부분을 이미 처리했습니다. 함수 포인터를 사용하면 상속과 같은 것을 구현하는 것이 쉽지만 솔직히 실제로는 거의 유용하지 않습니다.


6
당신이 할 경우 typedef struct FOO_type FOO_type헤더에 무효로 형식 정의 대신 여전히 구조를 노출하지 않으면 서 당신은 유형 검사의 추가 혜택을받을.
Scott Wales

8

함수 포인터를 사용하여 위조 할 수 있으며 실제로 이론적으로 C ++ 프로그램을 C로 컴파일하는 것이 가능하다고 생각합니다.

그러나 패러다임을 사용하는 언어를 선택하기보다는 언어에 대한 패러다임을 강요하는 것이 거의 이치에 맞지 않습니다.


5
첫 번째 C ++ 컴파일러는 정확히 그렇게했습니다 .C ++ 코드를 동등한 (그러나 추악하고 사람이 읽을 수없는) C 코드로 변환 한 다음 C 컴파일러가 컴파일했습니다.
Adam Rosenfield

2
EDG, Cfront 및 일부 다른 사람들은 여전히이 작업을 수행 할 수 있습니다. 모든 플랫폼에 C ++ 컴파일러가있는 것은 아닙니다.
Jasper Bekkers

어떤 이유로 C-front는 특정 C ++ 확장 (예 : 참조) 만 지원하지만 전체 OOP / 동적 디스패치 에뮬레이션은 지원하지 않는다고 생각했습니다.
Uri

2
LLVM 및 C 백엔드에서도 동일한 작업을 수행 할 수 있습니다.
Zifre

7

객체 지향 C를 수행 할 수 있습니다. 한국에서 생산되는 코드 유형을 보았습니다. 수년 동안 본 것 중 가장 끔찍한 괴물이었습니다 (코드를 본 작년 (2007)과 같았습니다). 그렇습니다. 그렇습니다. 사람들은 전에 그것을 해왔으며 오늘날에도 여전히 그렇게합니다. 그러나 C ++ 또는 Objective-C를 권장합니다. 둘 다 다른 패러다임으로 객체 방향을 제공하기 위해 C에서 태어난 언어입니다.


3
Linus가 귀하의 의견을 본다면. 그는 분명히 당신을 비웃거나 저주 할 것입니다
Anders Lind

7

OOP 접근 방식이 해결하려는 문제보다 우수하다고 확신하는 경우 왜 OOO 이외의 언어로 OOP 접근 방식을 해결하려고합니까? 작업에 잘못된 도구를 사용하고있는 것 같습니다. C ++ 또는 다른 객체 지향 C 변형 언어를 사용하십시오.

C로 작성된 기존의 대규모 프로젝트에서 코딩을 시작하기 때문에 묻는 경우, 자신 또는 다른 사람의 OOP 패러다임을 프로젝트 인프라에 강제로 적용해서는 안됩니다. 프로젝트에 이미 존재하는 지침을 따르십시오. 일반적으로, 깨끗한 API와 격리 라이브러리와 모듈은 깨끗한 OOP- 가진 향해 먼 길을 갈 것입니다 디자인.

이 모든 후, 당신은 정말 OOP C 일을 설정하는 경우, 읽기 (PDF)를.


36
실제로 질문에 대답하지 않는 ...
Brian Postow

2
@Brian, PDF에 대한 링크는 직접 확인해야 할 시간이 없지만 질문에 직접 대답하는 것처럼 보입니다.
Mark Ransom

5
PDF에 대한 링크는 주제에 대한 전체 교과서 인 것 같습니다 ... 아름다운 증거이지만 여백에 맞지 않습니다 ...
Brian Postow

5
예, 질문에 대답하십시오. 특정 방식으로 언어를 사용하는 방법을 묻는 것이 완벽하게 유효합니다. 다른 언어에 대한 의견은 요청하지 않았습니다 ....
Tim Ring

9
@Brian & Tim Ring : 질문 은 주제에 관한 책 추천 을 요청했습니다 . 나는 그 에게이 주제를 구체적으로 다루는 책에 대한 링크를 주었다 . 또한 문제에 대한 접근법이 최적이 아닌 이유에 대한 의견을 제시했습니다 (여기의 많은 사람들이 투표 및 기타 의견 / 답변에 따라 동의하는 것 같습니다). 내 답변을 개선하기위한 제안 사항이 있습니까?
RarrRarrRarr

6

그래 넌 할수있어. 사람들은 C ++ 또는 Objective-C 가 등장 하기 전에 객체 지향 C를 작성했습니다 . C ++과 Objective-C는 C에서 사용 된 일부 OO 개념을 언어의 일부로 공식화하려고 시도했습니다.

다음은 모양이 같거나 메소드 호출 인 것을 만드는 방법을 보여주는 정말 간단한 프로그램입니다 (이를 수행하는 더 좋은 방법이 있습니다. 이는 언어가 개념을 지원한다는 증거 일뿐입니다).

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}

6

물론, 지원 기능이 내장 된 언어를 사용하는 것만으로는 충분하지 않습니다. 심지어 "객체 지향 어셈블러"를 작성했습니다.


6

추가 할 작은 OOC 코드 :

#include <stdio.h>

struct Node {
    int somevar;
};

void print() {
    printf("Hello from an object-oriented C method!");
};

struct Tree {
    struct Node * NIL;
    void (*FPprint)(void);
    struct Node *root;
    struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};

int main()
{
    struct Tree TreeB;
    TreeB = TreeA;
    TreeB.FPprint();
    return 0;
}

5

나는 이것을 1 년 동안 파고 왔습니다.

GObject 시스템은 순수한 C와 함께 사용하기 어렵 기 때문에 C로 OO 스타일을 쉽게하기 위해 멋진 매크로를 작성하려고했습니다.

#include "OOStd.h"

CLASS(Animal) {
    char *name;
    STATIC(Animal);
    vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
    THIS->name = name;
    return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)

CLASS_EX(Cat,Animal) {
    STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
    printf("Meow!My name is %s!\n", THIS->name);
}

static int Cat_loadSt(StAnimal *THIS, void *PARAM){
    THIS->talk = (void *)Meow;
    return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)


CLASS_EX(Dog,Animal){
    STATIC_EX(Dog, Animal);
};

static void Woof(Animal *THIS){
    printf("Woof!My name is %s!\n", THIS->name);
}

static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
    THIS->talk = (void *)Woof;
    return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)

int main(){
    Animal *animals[4000];
    StAnimal *f;
    int i = 0;
    for (i=0; i<4000; i++)
    {
        if(i%2==0)
            animals[i] = NEW(Dog,"Jack");
        else
            animals[i] = NEW(Cat,"Lily");
    };
    f = ST(animals[0]);
    for(i=0; i<4000; ++i) {
        f->talk(animals[i]);
    }
    for (i=0; i<4000; ++i) {
        DELETE0(animals[i]);
    }
    return 0;
}

여기 내 프로젝트 사이트가 있습니다 (문서를 작성할 시간이 충분하지 않습니다. 그러나 중국어 문서는 훨씬 좋습니다).

OOC-GCC


CLASS STATIC ASM NEW는 ST 삭제 ... OOC-GCC의 매크로입니다
dameng은


4

C에서 OOP 개념을 사용하기에 적합한 기사 나 서적은 무엇입니까?

Dave Hanson의 C 인터페이스 및 구현 은 캡슐화 및 이름 지정 기능 이 뛰어나고 함수 포인터 사용 기능 이 우수 합니다. Dave는 상속을 시뮬레이션하지 않습니다.


4

OOP는 데이터를 프로그램의 코드보다 더 중요한 패러다임입니다. OOP는 언어가 아닙니다. 따라서 일반 C가 간단한 언어 인 것처럼 일반 C의 OOP도 간단합니다.


3
잘 말했지만 이것은 주석이어야합니다.
pqsk

4

X WindowXt 툴킷 의 구현을 살펴 보는 것이 좋습니다 . 물론 치아가 오래 걸리지 만 사용 된 많은 구조는 전통적인 C 내에서 OO 방식으로 작동하도록 설계되었습니다. 일반적으로 이것은 여기 저기에 간접 레이어를 추가하고 서로 겹쳐서 구조를 설계하는 것을 의미합니다.

OO와 같은 방식으로 C에 위치한 OO 방식으로 많은 것을 할 수 있습니다 #include<favorite_OO_Guru.h>. 그들은 실제로 당시의 많은 모범 사례를 구성했습니다. OO 언어와 시스템은 오늘날의 프로그래밍 자이 거스트의 일부만 증류하고 증폭했습니다.


4

질문에 대한 대답은 '예, 가능합니다'입니다.

객체 지향 C (OOC) 키트는 객체 지향 방식으로 프로그래밍하려는 사람을위한 것이지만 오래된 C에도 적용됩니다. OOC는 클래스, 단일 및 다중 상속, 예외 처리를 구현합니다.

풍모

• 언어 확장이 필요없는 C 매크로와 함수 만 사용하십시오! (ANSI-C)

• 어플리케이션을위한 읽기 쉬운 소스 코드. 가능한 한 간단하게하기 위해주의를 기울였습니다.

• 클래스의 단일 상속

인터페이스와 믹스 인에 의한 다중 상속 (버전 1.3부터)

• 예외 구현 (순 C!)

• 클래스를위한 가상 함수

• 쉬운 수업 구현을위한 외부 도구

자세한 내용은 http://ooc-coding.sourceforge.net/을 방문하십시오 .


4

사람들이 C를 사용하여 C ++ 스타일을 에뮬레이트하려고하는 것처럼 보입니다. 객체 지향 프로그래밍을 수행하는 것은 C가 실제로 구조체 지향 프로그래밍을 수행한다는 것입니다. 그러나 늦게 바인딩, 캡슐화 및 상속 같은 것을 얻을 수 있습니다. 상속을 위해 하위 구조체에서 기본 구조체에 대한 포인터를 명시 적으로 정의하면 분명히 다중 상속 형태입니다. 또한 귀하의

//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);

//private_class.c
struct inherited_class_1;
struct inherited_class_2;

struct private_class {
  int a;
  int b;
  struct inherited_class_1 *p1;
  struct inherited_class_2 *p2;
};

struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();

struct private_class * new_private_class() {
  struct private_class *p;
  p = (struct private_class*) malloc(sizeof(struct private_class));
  p->a = 0;
  p->b = 0;
  p->p1 = new_inherited_class_1();
  p->p2 = new_inherited_class_2();
  return p;
}

    int ret_a_value(struct private_class *p, int a, int b) {
      return p->a + p->b + a + b;
    }

    void delete_private_class(struct private_class *p) {
      //release any resources
      //call delete methods for inherited classes
      free(p);
    }
    //main.c
    struct private_class *p;
    p = new_private_class();
    late_bind_function = &implementation_function;
    delete_private_class(p);

로 컴파일하십시오 c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

따라서 조언은 순수한 C 스타일을 고수하고 C ++ 스타일을 강요하지 않는 것입니다. 또한이 방법은 API를 작성하는 매우 깨끗한 방법입니다.


상속을 위해 일반적으로 기본 클래스 또는 인스턴스 구조는 파생 구조에 포함되며 별도로 할당되지 않으며 포인터를 사용하여 참조됩니다. 이렇게하면 최상위베이스는 항상 파생 된 유형의 구조 시작 부분에 있으므로 서로 쉽게 캐스트 할 수 있으므로 오프셋에있을 수있는 포인터로는 수행 할 수 없습니다.
underscore_d

2

C의 OOP에 대한 또 다른 변형 은 http://slkpg.byethost7.com/instance.html 을 참조 하십시오 . 네이티브 C 만 사용하여 재진입을위한 인스턴스 데이터를 강조합니다. 다중 상속은 함수 래퍼를 사용하여 수동으로 수행됩니다. 타입 안전이 유지됩니다. 다음은 작은 샘플입니다.

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define push(self,a)            (*self).push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->push = push;
    peeker->get = get;
    peeker->peek = peek;
}

2

나는 파티에 조금 늦었지만 주제에 대한 경험을 나누고 싶다 : 요즘 임베디드 것들로 작업하고 있고 내가 가지고있는 유일한 (신뢰할 수있는) 컴파일러는 C이므로 객체 지향을 적용하고 싶다 C로 작성된 내 임베디드 프로젝트의 접근 방식

지금까지 내가 본 대부분의 솔루션은 타입 캐스트를 많이 사용하므로 타입 안전성을 잃어 버립니다. 실패하면 컴파일러가 도움이되지 않습니다. 이것은 완전히 받아 들일 수 없습니다.

내가 가진 요구 사항 :

  • 타입 캐스트는 가능한 많이 피하십시오. 따라서 타입 안전성을 잃지 않습니다.
  • 다형성 : 가상 메소드를 사용할 수 있어야하며 클래스의 사용자는 특정 메소드가 가상인지 여부를 인식하지 않아야합니다.
  • 다중 상속 : 자주 사용하지 않지만 때로는 일부 클래스가 여러 인터페이스를 구현하거나 여러 개의 슈퍼 클래스를 확장하기를 원합니다.

이 기사에서 내 접근 방식에 대해 자세히 설명했습니다 . C의 객체 지향 프로그래밍 ; 또한 기본 클래스와 파생 클래스에 대한 상용구 코드 자동 생성 유틸리티가 있습니다.


2

나는 그것을 시도한 작은 라이브러리를 만들었고 나에게 정말 훌륭하게 작동합니다. 그래서 나는 경험을 공유한다고 생각했다.

https://github.com/thomasfuhringer/oxygen

단일 상속은 구조체를 사용하여 매우 쉽게 구현하고 다른 모든 자식 클래스에 대해 확장 할 수 있습니다. 부모 구조로 간단히 캐스트하면 모든 자손에서 부모 메서드를 사용할 수 있습니다. 변수가 이런 종류의 객체를 보유하는 구조체를 가리키는 한, 항상 루트 클래스로 캐스팅하고 내성을 수행 할 수 있습니다.

언급했듯이 가상 방법은 다소 까다 롭습니다. 그러나 그들은 할 수 있습니다. 일을 단순하게 유지하기 위해 클래스 설명 구조에 함수 배열을 사용하면 모든 자식 클래스가 필요한 경우 개별 슬롯을 복사하고 다시 채 웁니다.

다중 상속은 구현하기가 다소 복잡하며 성능에 상당한 영향을 미칩니다. 그래서 나는 떠납니다. 필자는 실제 상황을 깨끗하게 모델링하는 것이 바람직하고 유용한 경우를 고려하지만, 90 %의 경우 단일 상속이 요구를 충족시킵니다. 그리고 단일 상속은 간단하고 비용이 들지 않습니다.

또한 타입 안전에 관심이 없습니다. 프로그래밍 실수를 막기 위해 컴파일러에 의존해서는 안된다고 생각합니다. 그리고 어쨌든 약간의 오류로부터 당신을 보호합니다.

일반적으로 객체 지향 환경에서는 메모리 관리를 가능한 한 자동화하기 위해 참조 카운팅을 구현하려고합니다. 또한“개체”루트 클래스에 참조 카운트를 추가하고 힙 메모리의 할당 및 할당 해제를 캡슐화하는 일부 기능을 제공합니다.

그것은 모두 매우 간단하고 간결하며 C ++ 인 괴물을 다루지 않고도 OO의 필수 요소를 제공합니다. 저는 C 랜드에 머무르는 유연성을 유지하며, 이는 특히 타사 라이브러리를 쉽게 통합 할 수있게합니다.


1

C의 수퍼 세트 인 Objective-C를 사용할 것을 제안합니다.

Objective-C는 30 세이지 만 우아한 코드를 작성할 수 있습니다.

http://en.wikipedia.org/wiki/Objective-C


이 경우 실제로 객체 지향적이므로 C ++을 권장합니다.
yyny

이것은 답이 아닙니다. 그러나 어쨌든 @YoYoYonnY : Objective-C를 사용하지 않고 C ++을 사용하지만 그와 같은 주석은 기본적으로 사용되지 않으며 아무 것도 제공하지 않았습니다. Objective-C가 "실제로 객체 지향 ..."이 부족하다고 주장하는 이유는 무엇입니까? 그리고 Objective-C가 실패한 곳에서 C ++가 성공하는 이유는 무엇입니까? 재미있는 점은 Objective-C는 문자 그대로 Object 라는 단어를 가지고 있지만 C ++은 OOP 언어가 아닌 다중 패러다임 언어로 시장을 내놓는다는 것입니다. 전혀) .... 그래서 당신은 그 이름을 잘못 알고 있지 않습니까?
underscore_d

0

예, 그러나 C로 어떤 종류의 다형성을 구현하려는 사람은 없었습니다.


6
예를 들어 Microsoft의 Direct X에는 다형성 C 인터페이스가 있습니다.
AShelly

8
예를 들어 리눅스 커널 구현을 살펴보십시오. 그것은 C에서 매우 일반적이고 널리 사용되는 관행입니다.
Ilya

3
또한 glib는 다형성이거나 다형성을 허용하는 방식으로 사용될 수 있습니다 (C ++처럼 어떤 호출이 가상인지 명시 적으로 말해야 함)
Spudd86

1
다형성은 C에서 드물지 않지만, 다중 상속입니다.
Johan Bjäreholt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.