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

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

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

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

19 ответов


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

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


мне пришлось сделать это один раз для домашнего задания. Я последовал этому подходу:--2-->

  1. определите ваши члены данных в a структура.
  2. определить члены-функции взять указатель на struct как первый аргумент.
  3. сделайте это в одном заголовке и одном c. Заголовок для struct определение & объявления функций, 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 "объект".

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

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


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

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


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);

Миро Самек разработана объектно-ориентированного программирования C для его каркаса машины: http://sourceforge.net/projects/qpc/. И он также написал книгу об этом: http://www.state-machine.com/psicc2/.


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


Я приведу простой пример того, как ООП должен быть сделан в 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 байтам в "унаследованном классе" (Asuming non-crazy optimalisations). Теперь, когда указатель структуры приведен к "унаследованному классу", "унаследованный класс" может получить доступ "унаследованные значения" таким же образом, как он обычно обращается к своим членам.

это и некоторые соглашения об именах для конструкторов, деструкторов, функций распределения и освобождения (я рекомендую init, clean, new, free) помогут вам пройти долгий путь.

Что касается виртуальных функций, используйте указатели функций в структуре, возможно, с Class_func(...); обертка тоже. Что касается (простых) шаблонов, добавьте параметр size_t для определения размера, требуйте указателя void* или требуйте "класса" введите только функциональность, о которой вы заботитесь. (напр. int GetUUID(объект *самовыдвижение); GetUUID(&п);)


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


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

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

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


есть очень обширная книга по этому вопросу, которую, возможно, стоит проверить:

объектно-ориентированное программирование в ANSI-C


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

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

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

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


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

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


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

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


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

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


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


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

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

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

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


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

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