[c] C 클래스를 어떻게 구현합니까? [닫은]

C (C ++ 또는 객체 지향 컴파일러 없음)를 사용해야하고 동적 메모리 할당이 없다고 가정하면 클래스를 구현하는 데 사용할 수있는 기술이나 클래스의 근사값은 무엇입니까? “클래스”를 별도의 파일로 분리하는 것이 항상 좋은 생각입니까? 고정 된 수의 인스턴스를 가정하거나 컴파일 시간 전에 각 객체에 대한 참조를 상수로 정의하여 메모리를 사전 할당 할 수 있다고 가정하십시오. 구현해야 할 OOP 개념 (다양)에 대해 자유롭게 가정하고 각각에 가장 적합한 방법을 제안하십시오.

제한 사항 :

  • 임베디드 시스템을위한 코드를 작성하고 컴파일러와 기존 코드베이스가 C에 있기 때문에 OOP가 아닌 C를 사용해야합니다.
  • 동적 할당을 시작하면 메모리가 부족하다고 합리적으로 추정 할 충분한 메모리가 없기 때문에 동적 메모리 할당이 없습니다.
  • 우리가 작업하는 컴파일러는 함수 포인터에 문제가 없습니다.


답변

이는 원하는 “개체 지향”기능 세트에 따라 다릅니다. 오버로드 및 / 또는 가상 메소드와 같은 것이 필요한 경우 구조에 함수 포인터를 포함해야합니다.

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

이를 통해 기본 클래스를 “상속”하고 적절한 함수를 구현하여 클래스를 구현할 수 있습니다.

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

물론 함수 포인터가 올바르게 설정되도록 생성자를 구현해야합니다. 일반적으로 인스턴스에 메모리를 동적으로 할당하지만 호출자에게도 그렇게 할 수 있습니다.

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

여러 개의 다른 생성자를 원하면 함수 이름을 “장식”해야하며 둘 이상의 rectangle_new()함수를 가질 수 없습니다 .

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

사용법을 보여주는 기본 예는 다음과 같습니다.

int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lengths(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

나는 이것이 적어도 당신에게 아이디어를 줄 수 있기를 바랍니다. C에서 성공적이고 풍부한 객체 지향 프레임 워크를 보려면 glib의 GObject 라이브러리를 살펴보십시오 .

또한 위에서 모델링 한 명시적인 “클래스”가 없으며 각 객체에는 일반적으로 C ++에서 찾을 수있는 것보다 약간 더 유연한 고유 한 메서드 포인터가 있습니다. 또한 메모리 비용이 듭니다. class구조체 에서 메서드 포인터를 채워서 각 개체 인스턴스가 클래스를 참조 할 수있는 방법을 만들어서 그로부터 벗어날 수 있습니다 .


답변

숙제도 한 번해야 했어요. 나는이 접근법을 따랐다.

  1. 구조체에서 데이터 멤버를 정의하십시오.
  2. 구조체에 대한 포인터를 첫 번째 인수로 사용하는 함수 멤버를 정의하십시오.
  3. 하나의 헤더와 하나의 c 에서이 작업을 수행하십시오. 구조체 정의 및 함수 선언을위한 헤더, 구현을위한 c

간단한 예는 다음과 같습니다.

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
/// 


답변

하나의 클래스 만 원하면 structs 배열을 “객체”데이터로 사용하고 포인터를 “멤버”함수에 전달하십시오. 클라이언트 코드에서 구현을 숨기도록 typedef struct _whatever Whatever선언 struct _whatever하기 전에 사용할 수 있습니다 . 이러한 “객체”와 C 표준 라이브러리 사이에는 차이가 없습니다FILE 객체 .

상속과 가상 함수를 가진 클래스를 두 개 이상 원한다면 구조체의 멤버로 함수에 대한 포인터 또는 가상 함수 테이블에 대한 공유 포인터를 갖는 것이 일반적입니다. G 객체의 라이브러리는 모두 이것과 타입 정의의 트릭을 사용하고 널리 사용된다.

ANSI C를 이용한 온라인 객체 지향 프로그래밍 에 대한 기술에 관한 책도 있습니다 .


답변

GOBject를 살펴볼 수 있습니다. 객체를 수행하는 자세한 방법을 제공하는 OS 라이브러리입니다.

http://library.gnome.org/devel/gobject/stable/


답변

C 인터페이스 및 구현 : 재사용 가능한 소프트웨어 작성 기법 , David R. Hanson

http://www.informit.com/store/product.aspx?isbn=0201498413

이 책은 당신의 질문을 다루는 훌륭한 일을합니다. Addison Wesley Professional Computing 시리즈에 있습니다.

기본 패러다임은 다음과 같습니다.

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);


답변

나는 C에서 OOP가 어떻게 수행되어야하는지에 대한 간단한 예를 제시 할 것이다. 나는이 주제가 2009 년의 것이지만 어쨌든 이것을 추가하고 싶다는 것을 알고있다.

/// Object.h
typedef struct Object {
    uuid_t uuid;
} Object;

int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);

/// Person.h
typedef struct Person {
    Object obj;
    char *name;
} Person;

int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);

/// Object.c
#include "object.h"

int Object_init(Object *self)
{
    self->uuid = uuid_new();

    return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
    return self->uuid;
}
int Object_clean(Object *self)
{
    uuid_free(self->uuid);

    return 0;
}

/// Person.c
#include "person.h"

int Person_init(Person *self, char *name)
{
    Object_init(&self->obj); // Or just Object_init(&self);
    self->name = strdup(name);

    return 0;
}
int Person_greet(Person *self)
{
    printf("Hello, %s", self->name);

    return 0;
}
int Person_clean(Person *self)
{
    free(self->name);
    Object_clean(self);

    return 0;
}

/// main.c
int main(void)
{
    Person p;

    Person_init(&p, "John");
    Person_greet(&p);
    Object_get_uuid(&p); // Inherited function
    Person_clean(&p);

    return 0;
}

기본 개념은 ‘상속 된 클래스’를 구조체의 맨 위에 배치하는 것입니다. 이런 식으로, 구조체에서 처음 4 바이트에 액세스하면 ‘상속 된 클래스’의 처음 4 바이트에도 액세스합니다 (미친 최적화 가정). 이제 구조체의 포인터가 ‘상속 된 클래스’로 캐스트되면 ‘상속 된 클래스’는 정상적으로 멤버에 액세스하는 것과 같은 방식으로 ‘상속 된 값’에 액세스 할 수 있습니다.

생성자, 소멸자, 할당 및 할당 해제 함수 (init, clean, new, free 권장)에 대한 이것과 일부 명명 규칙은 먼 길을 갈 것입니다.

가상 함수의 경우 구조체에서 함수 포인터를 사용하십시오 (Class_func (…); 래퍼도. (간단한) 템플릿의 경우 size_t 매개 변수를 추가하여 크기를 결정하거나 void * 포인터가 필요하거나 관심있는 기능 만있는 ‘클래스’유형이 필요합니다. (예 : int GetUUID (Object * self); GetUUID (& p);)


답변

a struct를 사용하여 클래스의 데이터 멤버를 시뮬레이션하십시오. 메소드 범위와 관련하여 개인 함수 프로토 타입을 .c 파일 에 배치 하고 공용 함수를 .h 파일 에 배치하여 개인 메소드를 시뮬레이션 할 수 있습니다 .