Как реализовать класс В 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-->
- определите ваши члены данных в a структура.
- определить члены-функции взять указатель на struct как первый аргумент.
- сделайте это в одном заголовке и одном c. Заголовок для struct определение & объявления функций, C для реализации.
простой пример:
/// Queue.h
struct Queue
{
/// members
}
typedef struct Queue Queue;
void push(Queue* q, int element);
void pop(Queue* q);
// etc.
///
если вы хотите только один класс, использовать массив struct
s как данные " объектов "и передают указатели на них функциям" члена". Вы можете использовать typedef struct _whatever Whatever
перед объявлением struct _whatever
чтобы скрыть реализацию из клиентского кода. Нет никакой разницы между таким "объектом" и стандартной библиотекой C FILE
"объект".
если вы хотите более одного класса с наследованием и виртуальными функциями, то обычно есть указатели на функции как члены структуры или общий указатель на таблицу виртуальных функций. The GObject библиотека использует как это, так и трюк typedef и широко используется.
есть также книга о методах для этого доступны в интернете -объектно-ориентированное программирование с ANSI C.
C интерфейсы и реализации: методы создания многоразового программного обеспечения, Дэвид Р. Хэнсон
эта книга отлично справляется с освещением вашего вопроса. Это в серии 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) освобождает выделенную память (индекс массива) в динамической стратегии выделения
кто-нибудь видит какие-либо проблемы, дыры, потенциальные ловушки или скрытые преимущества/недостатки в любом варианте этого подхода? Если я изобретаю метод проектирования (а я предполагаю, что должен), можете ли вы указать мне его название?
Это возможно. Это всегда кажется хорошей идеей в то время, но впоследствии это становится кошмаром обслуживания. Ваш код усеян фрагментами кода, связывающими все вместе. У нового программиста будет много проблем с чтением и пониманием кода, если вы используете указатели функций, поскольку не будет очевидно, какие функции называются.
скрытие данных с помощью функций 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++ и получить классы.