Подтвердить что ты не робот

Как реализовать класс в C?

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

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

  • Мне нужно использовать C, а не OOP потому что я пишу код для встроенной системы, а также компилятора и существующая база кода находится в C.
  • Динамическое распределение памяти отсутствует потому что у нас недостаточно памяти разумно предположить, что мы не закончим если мы начнем динамическое распределение он.
  • У компиляторов, с которыми мы работаем, нет проблем с указателями функций
4b9b3361

Ответ 1

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

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 area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

Надеюсь, это даст вам некоторые идеи, по крайней мере. Для успешной и богатой объектно-ориентированной структуры в C просмотрите glib GObject библиотека.

Также обратите внимание, что нет явного "класса", который моделируется выше, у каждого объекта есть свои указатели на методы, которые немного более гибкие, чем вы обычно находите на С++. Кроме того, он стоит памяти. Вы можете уйти от этого, наполнив указатели методов в структуре class и придумайте способ для каждого экземпляра объекта ссылаться на класс.

Ответ 2

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

  • Определите данные в структура.
  • Определите своих членов, которые возьмите указатель на свою структуру как первый аргумент.
  • Сделайте это в одном заголовке и в одном c. Заголовок для определения структуры и amp; объявления функций, c для реализации.

Простым примером может быть следующее:

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 6

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

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

Ответ 8

Я приведу простой пример того, как ООП следует делать в 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 * или введите тип класса с помощью только тех функций, которые вам интересны. (например, int GetUUID (Object * self), GetUUID (& p);)

Ответ 9

В вашем случае хорошей аппроксимацией класса может быть ADT. Но все равно это будет не то же самое.

Ответ 10

Моя стратегия:

  • Определить весь код для класса в отдельном файле
  • Определить все интерфейсы для класса в отдельном файле заголовка
  • Все функции-члены принимают "ClassHandle", который стоит для имени экземпляра (вместо o.foo(), вызывает foo (oHandle)
  • Конструктор заменяется на функцию void ClassInit (ClassHandle h, int x, int y,...) ИЛИ ClassHandle ClassInit (int x, int y,...) в зависимости от стратегии выделения памяти
  • Все переменные-члены хранятся как член статической структуры в файле класса, инкапсулируя его в файл, предотвращая доступ к нему внешних файлов.
  • Объекты хранятся в массиве статической структуры выше, с предопределенными дескрипторами (видимыми в интерфейсе) или фиксированным пределом объектов, которые могут быть созданы
  • Если это полезно, класс может содержать общедоступные функции, которые будут циклически проходить через массив и вызывать функции всех созданных объектов (RunAll() вызывает каждый Run (oHandle)
  • Функция Deinit (ClassHandle h) освобождает выделенную память (индекс массива) в стратегии динамического распределения

Кто-нибудь видит какие-либо проблемы, дыры, потенциальные подводные камни или скрытые преимущества/недостатки в любом варианте этого подхода? Если я изобретаю метод дизайна (и я предполагаю, что должен быть), можете ли вы указать мне его имя?

Ответ 12

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

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

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

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

Ответ 14

#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;
};

Ответ 15

Мой подход состоял в том, чтобы переместить struct и все связанные с ним функции в отдельный исходный файл (ы), чтобы его можно было использовать "переносимо".

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

Ответ 16

Первый компилятор С++ фактически был препроцессором, который перевел код С++ на C.

Таким образом, очень возможно иметь классы в C. Вы можете попытаться выкопать старый препроцессор С++ и посмотреть, какие решения он создает.

Ответ 17

Вам нужны виртуальные методы?

Если нет, вы просто определяете набор указателей на функции в самой структуре. Если вы назначите все указатели на стандартные функции C, вы сможете вызывать функции из C в очень похожем синтаксисе так, как вы бы в С++.

Если вы хотите иметь виртуальные методы, он становится более сложным. В основном вам нужно будет реализовать собственный VTable для каждой структуры и назначить указатели на функцию VTable в зависимости от того, какая функция вызывается. Тогда вам понадобится набор указателей функций в самой структуре, которые, в свою очередь, вызовут указатель функции в VTable. Это, по сути, то, что делает С++.

TBH, хотя... если вы хотите последнего, вам, вероятно, лучше всего найти компилятор С++, который вы можете использовать и перекомпилировать проект. Я никогда не понимал, что навязчивая идея с С++ не используется во встроенных. Я использовал его много раз, и он работает быстро и не имеет проблем с памятью. Конечно, вы должны быть немного более осторожны в том, что вы делаете, но на самом деле это не так сложно.

Ответ 18

GTK полностью построен на C, и он использует множество концепций ООП. Я прочитал исходный код GTK, и это довольно впечатляет и, безусловно, легче читать. Основная концепция заключается в том, что каждый "класс" представляет собой просто структуру и связанные с ней статические функции. Статические функции все принимают структуру "экземпляр" как параметр, делают все, что нужно, и при необходимости возвращают результаты. Например, у вас может быть функция "GetPosition (CircleStruct obj)". Функция просто прорывает структуру, извлекает номера позиций, возможно, создает новый объект PositionStruct, вставляет x и y в новый PositionStruct и возвращает его. GTK даже реализует наследование таким образом, встраивая структуры внутри структур. довольно умный.

Ответ 19

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

Кроме того, если вы можете использовать C, вы можете с уверенностью использовать С++ и получить классы.