Предполагая, что мне нужно использовать C (без C ++ или объектно-ориентированных компиляторов) и у меня нет динамического распределения памяти, какие методы я могу использовать для реализации класса или хорошее приближение класса? Всегда ли стоит выделить «класс» в отдельный файл? Предположим, что мы можем предварительно выделить память, приняв фиксированное количество экземпляров или даже определив ссылку на каждый объект как константу до времени компиляции. Не стесняйтесь делать предположения о том, какую концепцию ООП мне нужно будет реализовать (она будет отличаться), и предложить лучший метод для каждой из них.
Ограничения:
- Я должен использовать C, а не ООП, потому что я пишу код для встроенной системы, а компилятор и уже существующая база кода находятся на C.
- Не существует динамического распределения памяти, потому что у нас недостаточно памяти, чтобы разумно предположить, что у нас не закончится, если мы начнем динамическое распределение.
- У компиляторов, с которыми мы работаем, нет проблем с указателями на функции
apr_
префикса GLibg_
для создания пространства имен) и другие организационные факторы без ООП. Если вы все равно собираетесь реструктурировать приложение, я бы подумал о том, чтобы потратить некоторое время, пытаясь придумать более удобную в обслуживании процедурную структуру.Ответы:
Это зависит от того, какой именно «объектно-ориентированный» набор функций вы хотите иметь. Если вам нужны такие вещи, как перегрузка и / или виртуальные методы, вам, вероятно, нужно включить указатели на функции в структуры:
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 загляните в библиотеку GObject glib .
Также обратите внимание, что здесь не моделируется явный «класс», каждый объект имеет свои собственные указатели на методы, что немного более гибко, чем вы обычно найдете в C ++. Кроме того, это стоит памяти. Вы можете уйти от этого, вставив указатели методов в
class
структуру и изобрести способ для каждого экземпляра объекта ссылаться на класс.источник
const ShapeClass *
или вconst void *
качестве аргументов? Казалось бы, последнее может быть немного лучше в отношении наследования, но я вижу аргументы в обоих направлениях ...float (*computeArea)(const ShapeClass *shape);
говорю, чтоShapeClass
это неизвестный тип.struct
ссылка требует объявления перед определением. Это объясняется на примере здесь, в ответе Лундина . Изменение примера для включения форвардного объявления должно решить вашу проблему;typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };
Мне тоже однажды пришлось сделать это в качестве домашней работы. Я последовал такому подходу:
Вот простой пример:
/// Queue.h struct Queue { /// members } typedef struct Queue Queue; void push(Queue* q, int element); void pop(Queue* q); // etc. ///
источник
typedef struct Queue Queue;
.Если вам нужен только один класс, используйте массив
struct
s в качестве данных «объектов» и передайте указатели на них функциям-членам. Вы можете использоватьtypedef struct _whatever Whatever
перед объявлением,struct _whatever
чтобы скрыть реализацию от клиентского кода. Нет разницы между таким «объектом» иFILE
объектом стандартной библиотеки C.Если вам нужно несколько классов с наследованием и виртуальными функциями, то обычно указатели на функции используются в качестве членов структуры или общий указатель на таблицу виртуальных функций. Библиотека GObject использует и этот прием, и трюк с определением типов , и широко используется.
Там также книга о методах для этого доступны в Интернете - объектно - ориентированное программирование с ANSI C .
источник
вы можете взглянуть на GOBject. это библиотека ОС, которая дает вам подробный способ создания объекта.
http://library.gnome.org/devel/gobject/stable/
источник
Интерфейсы и реализации C: методы создания программного обеспечения многократного использования , Дэвид Р. Хэнсон
Эта книга отлично справляется с вашим вопросом. Это из серии профессиональных вычислений Addison Wesley.
Основная парадигма выглядит примерно так:
/* for data structure foo */ FOO *myfoo; myfoo = foo_create(...); foo_something(myfoo, ...); myfoo = foo_append(myfoo, ...); foo_delete(myfoo);
источник
Я приведу простой пример того, как ООП должно быть реализовано в C. Я понимаю, что это тезис 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);)
источник
Используйте
struct
для имитации членов данных класса. Что касается области действия метода, вы можете моделировать частные методы, помещая прототипы частных функций в файл .c, а общедоступные функции - в файл .h.источник
#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; };
источник
_Shape
. Это было бы неопределенное поведение. Имя, начинающееся с подчеркивания, за которым следует заглавная буква, являются зарезервированными идентификаторами .В вашем случае хорошим приближением класса может быть ADT . Но все равно все будет не так.
источник
Моя стратегия:
Кто-нибудь видит какие-либо проблемы, дыры, потенциальные ловушки или скрытые преимущества / недостатки любого варианта этого подхода? Если я заново изобретаю метод дизайна (а я полагаю, что должен), можете ли вы указать мне на его название?
источник
Также см. Этот ответ и этот
Это возможно. В то время это всегда кажется хорошей идеей, но потом становится кошмаром для обслуживания. Ваш код становится завален кусками кода, связывающими все вместе. У нового программиста будет много проблем с чтением и пониманием кода, если вы используете указатели на функции, поскольку не будет очевидно, какие функции вызываются.
Скрытие данных с помощью функций get / set легко реализовать в C, но остановимся на этом. Я видел несколько попыток сделать это во встроенной среде, и в конце концов это всегда проблема обслуживания.
Поскольку у всех вас есть проблемы с обслуживанием, я бы держался подальше.
источник
Мой подход заключался бы в том, чтобы переместить
struct
все основные функции и все связанные с ними функции в отдельный исходный файл (ы), чтобы его можно было использовать «переносимо».В зависимости от вашего компилятора, вы могли бы включать функции в
struct
, но это очень специфичное для компилятора расширение и не имеет ничего общего с последней версией стандарта, который я обычно использовал :)источник
Первый компилятор C ++ на самом деле был препроцессором, который переводил код C ++ в C.
Так что вполне возможно иметь классы на C. Вы можете попробовать откопать старый препроцессор C ++ и посмотреть, какие решения он создает.
источник
cfront
; он столкнулся с проблемами при добавлении исключений в C ++ - обработка исключений не является тривиальной задачей.GTK полностью построен на C и использует многие концепции ООП. Я прочитал исходный код GTK, он впечатляет и определенно легче читается. Основная концепция состоит в том, что каждый «класс» - это просто структура и связанные с ней статические функции. Все статические функции принимают структуру «экземпляр» в качестве параметра, делают все, что нужно, и при необходимости возвращают результаты. Например, у вас может быть функция «GetPosition (CircleStruct obj)». Функция просто копается в структуре, извлекает номера позиций, возможно, строит новый объект PositionStruct, вставляет x и y в новый PositionStruct и возвращает его. GTK даже реализует наследование таким образом, встраивая структуры внутрь структур. довольно умно.
источник
Вам нужны виртуальные методы?
В противном случае вы просто определяете набор указателей на функции в самой структуре. Если вы назначите все указатели на функции стандартным функциям C, вы сможете вызывать функции из C в синтаксисе, очень похожем на то, как вы это делали бы в C ++.
Если вы хотите иметь виртуальные методы, все становится сложнее. В основном вам нужно будет реализовать свой собственный VTable для каждой структуры и назначить указатели функций для VTable в зависимости от того, какая функция вызывается. Затем вам понадобится набор указателей на функции в самой структуре, которые, в свою очередь, вызывают указатель функции в VTable. По сути, это то, что делает C ++.
TBH, хотя ... если вы хотите последнее, вам, вероятно, лучше просто найти компилятор C ++, который вы можете использовать, и перекомпилировать проект. Я никогда не понимал, что одержимость C ++ неприменима во встроенном. Я использовал его много раз, он работает быстро и не имеет проблем с памятью. Конечно, вам нужно быть немного более осторожным в том, что вы делаете, но на самом деле это не так сложно.
источник
C не является языком ООП, как вы правильно заметили, поэтому нет встроенного способа написать настоящий класс. Лучше всего взглянуть на структуры и указатели на функции , они позволят вам построить приближение класса. Однако, поскольку C является процедурным, вы можете подумать о написании большего количества кода, подобного C (то есть без попытки использовать классы).
Кроме того, если вы можете использовать C, вы, вероятно, можете использовать C ++ и получать классы.
источник