Как вы реализуете класс в C? - PullRequest
129 голосов
/ 10 сентября 2009

Предполагая, что мне нужно использовать C (без C ++ или объектно-ориентированных компиляторов), и у меня нет динамического распределения памяти, какие методы я могу использовать для реализации класса или хорошего приближения класса? Всегда ли полезно выделить «класс» в отдельный файл? Предположим, что мы можем предварительно выделить память, предполагая фиксированное количество экземпляров или даже определяя ссылку на каждый объект как константу перед временем компиляции. Не стесняйтесь делать предположения о том, какую концепцию ООП мне нужно будет реализовать (она будет отличаться) и предлагать лучший метод для каждого из них.

Ограничения:

  • Я должен использовать C, а не ООП потому что я пишу код для встроенная система, и компилятор и существующая база кода находится в C.
  • Нет динамического выделения памяти потому что у нас недостаточно памяти разумно предположить, что мы не закончим если мы начнем динамически распределять это.
  • У компиляторов, с которыми мы работаем, нет проблем с указателями на функции

Ответы [ 18 ]

80 голосов
/ 10 сентября 2009

Это зависит от того, какой именно объектно-ориентированный набор функций вы хотите иметь. Если вам нужны такие вещи, как перегрузка и / или виртуальные методы, вам, вероятно, нужно включить указатели на функции в структурах:

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, и придумать способ для каждого экземпляра объекта ссылаться на класс.

23 голосов
/ 10 сентября 2009

Я тоже должен был сделать это однажды для домашней работы. Я придерживался этого подхода:

  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.
/// 
12 голосов
/ 10 сентября 2009

Если вам нужен только один класс, используйте массив struct s в качестве данных "объектов" и передавайте указатели на них функциям "member". Вы можете использовать typedef struct _whatever Whatever перед объявлением struct _whatever, чтобы скрыть реализацию от клиентского кода. Нет никакой разницы между таким «объектом» и стандартной библиотекой C FILE object.

Если вам требуется более одного класса с наследованием и виртуальными функциями, то обычно есть указатели на функции как члены структуры или общий указатель на таблицу виртуальных функций. Библиотека GObject использует как этот, так и трюк с typedef и широко используется.

Есть также книга о методах для этого, доступная онлайн - Объектно-ориентированное программирование с ANSI C .

7 голосов
/ 12 сентября 2009

Интерфейсы и реализации C: методы создания программного обеспечения многократного использования , Дэвид Р. Хансон

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);
7 голосов
/ 10 сентября 2009

вы можете взглянуть на GOBject. это библиотека ОС, которая дает вам подробный способ создания объекта.

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

4 голосов
/ 10 сентября 2009

Используйте struct для имитации данных-членов класса. С точки зрения области действия метода вы можете моделировать закрытые методы, поместив прототипы функций private в файл .c и функции public в файл .h.

4 голосов
/ 21 мая 2016

Я приведу простой пример того, как ООП должно быть сделано в C. Я понимаю, что это thead с 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 * или тип «class» с той функциональностью, которая вас интересует. (например, int GetUUID (Object * self); GetUUID (& p);)

3 голосов
/ 23 января 2015
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>

/**
 * Define Shape class
 */
typedef struct Shape Shape;
struct Shape {
    /**
     * Variables header...
     */
    double width, height;

    /**
     * Functions header...
     */
    double (*area)(Shape *shape);
};

/**
 * Functions
 */
double calc(Shape *shape) {
        return shape->width * shape->height;
}

/**
 * Constructor
 */
Shape _Shape() {
    Shape s;

    s.width = 1;
    s.height = 1;

    s.area = calc;

    return s;
}

/********************************************/

int main() {
    Shape s1 = _Shape();
    s1.width = 5.35;
    s1.height = 12.5462;

    printf("Hello World\n\n");

    printf("User.width = %f\n", s1.width);
    printf("User.height = %f\n", s1.height);
    printf("User.area = %f\n\n", s1.area(&s1));

    printf("Made with \xe2\x99\xa5 \n");

    return 0;
};
3 голосов
/ 03 июля 2012

Может быть эта онлайн-книга может помочь вам решить вашу проблему.

3 голосов
/ 10 сентября 2009

Также см. этот ответ и этот

Это возможно. Это всегда кажется хорошей идеей в то время, но потом это становится кошмаром обслуживания. Ваш код стал завален кусками кода, связывающими все воедино. У нового программиста будет много проблем с чтением и пониманием кода, если вы будете использовать указатели на функции, поскольку не будет очевидно, какие функции вызываются.

Сокрытие данных с помощью функций get / set легко реализовать в C, но на этом остановимся. Я видел несколько попыток сделать это во встроенной среде, и, в конце концов, это всегда проблема обслуживания.

Поскольку у вас всех есть проблемы с техническим обслуживанием, я бы прояснил.

...