Как можно написать объектно-ориентированный код на C? Особенно в отношении полиморфизма.
См. Также этот вопрос переполнения стека Объект-ориентация в Си .
Как можно написать объектно-ориентированный код на C? Особенно в отношении полиморфизма.
См. Также этот вопрос переполнения стека Объект-ориентация в Си .
Ответы:
Да. На самом деле Аксель Шрайнер бесплатно предоставляет свою книгу «Объектно-ориентированное программирование в ANSI-C», которая довольно подробно освещает эту тему.
Поскольку вы говорите о полиморфизме, то да, вы можете, мы занимались такими вещами за годы до появления C ++.
В основном вы используете a struct
для хранения данных и списка указателей функций для указания на соответствующие функции для этих данных.
Итак, в классе связи у вас будет вызов open, read, write и close, который будет поддерживаться в виде четырех указателей на функции в структуре наряду с данными для объекта, что-то вроде:
typedef struct {
int (*open)(void *self, char *fspec);
int (*close)(void *self);
int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
// And data goes here.
} tCommClass;
tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;
tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;
Конечно, те сегменты кода, приведенные выше, на самом деле будут в «конструкторе», таком как rs232Init()
.
Когда вы «наследуете» от этого класса, вы просто меняете указатели, чтобы они указывали на ваши собственные функции. Каждый, кто вызывал эти функции, делал бы это с помощью указателей на функции, давая вам ваш полиморфизм:
int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");
Вроде как ручной vtable.
Вы могли бы даже иметь виртуальные классы, установив указатели на NULL - поведение будет немного отличаться от C ++ (дамп ядра во время выполнения, а не ошибка во время компиляции).
Вот пример кода, который демонстрирует это. Сначала структура класса верхнего уровня:
#include <stdio.h>
// The top-level class.
typedef struct sCommClass {
int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;
Тогда у нас есть функции для подкласса TCP:
// Function for the TCP 'class'.
static int tcpOpen (tCommClass *tcp, char *fspec) {
printf ("Opening TCP: %s\n", fspec);
return 0;
}
static int tcpInit (tCommClass *tcp) {
tcp->open = &tcpOpen;
return 0;
}
И HTTP один, а также:
// Function for the HTTP 'class'.
static int httpOpen (tCommClass *http, char *fspec) {
printf ("Opening HTTP: %s\n", fspec);
return 0;
}
static int httpInit (tCommClass *http) {
http->open = &httpOpen;
return 0;
}
И, наконец, тестовая программа, чтобы показать это в действии:
// Test program.
int main (void) {
int status;
tCommClass commTcp, commHttp;
// Same 'base' class but initialised to different sub-classes.
tcpInit (&commTcp);
httpInit (&commHttp);
// Called in exactly the same manner.
status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
status = (commHttp.open)(&commHttp, "http://www.microsoft.com");
return 0;
}
Это производит вывод:
Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com
так что вы можете видеть, что различные функции вызываются в зависимости от подкласса.
tCommClass
будет переименован tCommVT
, а tCommClass
структура будет иметь только поля данных и одно tCommVT vt
поле, указывающее на «одну-единственную» виртуальную таблицу. Перенос всех указателей с каждым экземпляром добавляет ненужные накладные расходы и напоминает больше о том, как вы будете делать вещи в JavaScript, чем в C ++, ИМХО.
Пространства имен часто создаются следующим образом:
stack_push(thing *)
вместо
stack::push(thing *)
Чтобы превратить структуру C в нечто вроде класса C ++, вы можете превратить:
class stack {
public:
stack();
void push(thing *);
thing * pop();
static int this_is_here_as_an_example_only;
private:
...
};
В
struct stack {
struct stack_type * my_type;
// Put the stuff that you put after private: here
};
struct stack_type {
void (* construct)(struct stack * this); // This takes uninitialized memory
struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
int this_is_here_as_an_example_only;
}Stack = {
.construct = stack_construct,
.operator_new = stack_operator_new,
.push = stack_push,
.pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else
И делать:
struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
// Do something about it
} else {
// You can use the stack
stack_push(st, thing0); // This is a non-virtual call
Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
st->my_type.push(st, thing2); // This is a virtual call
}
Я не делал деструктор и не удалял, но он следует той же схеме.
this_is_here_as_an_example_only похож на статическую переменную класса, которая используется всеми экземплярами типа. Все методы действительно статичны, за исключением того, что некоторые принимают this *
st->my_type->push(st, thing2);
вместоst->my_type.push(st, thing2);
struct stack_type my_type;
вместоstruct stack_type * my_type;
Class
структуры? Это сделало бы OO C более динамичным, чем C ++. Как насчет этого? Кстати, +1.
Я считаю, что помимо того, что ООП в Си полезен сам по себе, он является отличным способом изучения ООП и понимания его внутренней работы. Опыт многих программистов показал, что для эффективного и уверенного использования техники программист должен понимать, как в конечном итоге реализуются базовые концепции. Эмуляция классов, наследования и полиморфизма в C учит именно этому.
Чтобы ответить на оригинальный вопрос, вот пара ресурсов, которые учат, как сделать ООП в C:
В блоге EmbeddedGurus.com «Объектно-ориентированное программирование на C» показано, как реализовать классы и единичное наследование в переносимом C: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /
Замечание по применению "" C + "- объектно-ориентированное программирование на C" показывает, как реализовать классы, одиночное наследование и позднее связывание (полиморфизм) в C с использованием макросов препроцессора: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , пример кода доступен по адресу http://www.state-machine.com/resources/cplus_3.0.zip
Я видел это сделано. Я бы не рекомендовал это. C ++ изначально начинал этот путь как препроцессор, который создавал код на C как промежуточный этап.
По сути, в конечном итоге вы создаете таблицу диспетчеризации для всех ваших методов, где храните ссылки на функции. Извлечение класса повлечет за собой копирование этой таблицы диспетчеризации и замену записей, которые вы хотите переопределить, с вашими новыми «методами», вызывающими исходный метод, если он хочет вызвать базовый метод. В конечном итоге вы переписываете C ++.
glib
написано в C в объективном смысле?
Конечно, это возможно. Это то , что делает GObject , фреймворк, на котором основаны все GTK + и GNOME .
Подбиблиотека FILE C stdio - отличный пример того, как создать абстракцию, инкапсуляцию и модульность в чистом C.
Наследование и полиморфизм - другие аспекты, которые часто считаются важными для ООП, - не обязательно обеспечивают повышение производительности, которое они обещают, и были выдвинуты разумные аргументы , что они могут фактически препятствовать развитию и размышлениям о проблемной области.
Тривиальный пример с животными и собаками: вы отражаете механизм vtable C ++ (во всяком случае, в любом случае). Вы также разделяете распределение и создание экземпляров (Animal_Alloc, Animal_New), чтобы мы не вызывали malloc () несколько раз. Мы также должны явно передать this
указатель.
Если бы вы делали не виртуальные функции, это тривиально. Вы просто не добавляете их в vtable, а статические функции не требуют this
указателя. Множественное наследование обычно требует нескольких таблиц для устранения неоднозначностей.
Кроме того, вы должны иметь возможность использовать setjmp / longjmp для обработки исключений.
struct Animal_Vtable{
typedef void (*Walk_Fun)(struct Animal *a_This);
typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);
Walk_Fun Walk;
Dtor_Fun Dtor;
};
struct Animal{
Animal_Vtable vtable;
char *Name;
};
struct Dog{
Animal_Vtable vtable;
char *Name; // Mirror member variables for easy access
char *Type;
};
void Animal_Walk(struct Animal *a_This){
printf("Animal (%s) walking\n", a_This->Name);
}
struct Animal* Animal_Dtor(struct Animal *a_This){
printf("animal::dtor\n");
return a_This;
}
Animal *Animal_Alloc(){
return (Animal*)malloc(sizeof(Animal));
}
Animal *Animal_New(Animal *a_Animal){
a_Animal->vtable.Walk = Animal_Walk;
a_Animal->vtable.Dtor = Animal_Dtor;
a_Animal->Name = "Anonymous";
return a_Animal;
}
void Animal_Free(Animal *a_This){
a_This->vtable.Dtor(a_This);
free(a_This);
}
void Dog_Walk(struct Dog *a_This){
printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}
Dog* Dog_Dtor(struct Dog *a_This){
// Explicit call to parent destructor
Animal_Dtor((Animal*)a_This);
printf("dog::dtor\n");
return a_This;
}
Dog *Dog_Alloc(){
return (Dog*)malloc(sizeof(Dog));
}
Dog *Dog_New(Dog *a_Dog){
// Explict call to parent constructor
Animal_New((Animal*)a_Dog);
a_Dog->Type = "Dog type";
a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;
return a_Dog;
}
int main(int argc, char **argv){
/*
Base class:
Animal *a_Animal = Animal_New(Animal_Alloc());
*/
Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());
a_Animal->vtable.Walk(a_Animal);
Animal_Free(a_Animal);
}
PS. Это проверено на компиляторе C ++, но должно быть легко заставить его работать на компиляторе C.
typedef
Внутри это struct
невозможно в C.
Проверьте GObject . Это должно быть ОО в С и одна реализация того, что вы ищете. Если вы действительно хотите использовать OO, используйте C ++ или другой язык OOP. Иногда с GObject может быть очень сложно работать, если вы привыкли иметь дело с ОО-языками, но, как и все остальное, вы привыкнете к соглашениям и потоку.
Это было интересно читать. Я сам размышлял над тем же вопросом, и польза от размышлений заключается в следующем:
Попытка представить, как реализовать концепции ООП на языке, отличном от ООП, помогает мне понять сильные стороны языка ООП (в моем случае, C ++). Это помогает мне лучше понять, использовать ли C или C ++ для приложения определенного типа - где преимущества одного перевешивают другое.
Просматривая в Интернете информацию и мнения по этому вопросу, я обнаружил автора, который писал код для встроенного процессора и имел только доступный компилятор C: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -С-Создание-Foundation-классы-Part-1
В его случае анализ и адаптация ООП-концепций в простом С были вполне обоснованными. Похоже, он был готов пожертвовать некоторыми концепциями ООП из-за снижения производительности, вызванного попыткой реализовать их в C.
Урок, который я извлек, заключается в том, что да, это можно сделать в определенной степени, и да, есть несколько веских причин, чтобы попробовать это.
В конце концов, машина перебирает биты указателя стека, заставляя счетчик программ перемещаться и вычисляя операции доступа к памяти. С точки зрения эффективности, чем меньше таких вычислений выполняется вашей программой, тем лучше ... но иногда нам приходится платить этот налог просто, чтобы мы могли организовать нашу программу таким образом, чтобы она была менее подвержена человеческим ошибкам. Компилятор языка ООП стремится оптимизировать оба аспекта. Программист должен быть намного осторожнее при реализации этих концепций на языке, подобном C.
Возможно, вам будет полезно взглянуть на документацию Apple, касающуюся набора API Core Foundation. Это чистый API C, но многие типы связаны с объектными эквивалентами Objective C.
Вам также может быть полезно взглянуть на дизайн самой Objective-C. Он немного отличается от C ++ в том, что объектная система определяется в терминах функций C, например, objc_msg_send
для вызова метода объекта. Компилятор переводит синтаксис в квадратные скобки в эти вызовы функций, так что вам не нужно это знать, но, учитывая ваш вопрос, вам может быть полезно узнать, как он работает под капотом.
Есть несколько методов, которые можно использовать. Самый важный из них - как разделить проект. В нашем проекте мы используем интерфейс, который объявлен в файле .h, а реализацию объекта - в файле .c. Важной частью является то, что все модули, которые включают в себя файл .h, видят только объект как a void *
, и файл .c является единственным модулем, который знает внутреннюю часть структуры.
Примерно так для класса мы называем FOO в качестве примера:
В .h файле
#ifndef FOO_H_
#define FOO_H_
...
typedef struct FOO_type FOO_type; /* That's all the rest of the program knows about FOO */
/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif
Файл реализации C будет примерно таким.
#include <stdlib.h>
...
#include "FOO.h"
struct FOO_type {
whatever...
};
FOO_type *FOO_new(void)
{
FOO_type *this = calloc(1, sizeof (FOO_type));
...
FOO_dosomething(this, );
return this;
}
Поэтому я даю указатель явно на объект для каждой функции этого модуля. Компилятор C ++ делает это неявно, а в C мы записываем это явно.
Я действительно использую this
в своих программах, чтобы убедиться, что моя программа не компилируется в C ++, и у меня есть прекрасное свойство быть в другом цвете в моем редакторе подсветки синтаксиса.
Поля FOO_struct могут быть изменены в одном модуле, и другой модуль даже не нужно перекомпилировать, чтобы все еще использовать.
С этим стилем я уже рассмотрел большую часть преимуществ ООП (инкапсуляция данных). Используя указатели функций, даже легко реализовать что-то вроде наследования, но, честно говоря, это действительно редко используется.
typedef struct FOO_type FOO_type
вместо typedef значение void в заголовке, вы получите дополнительное преимущество проверки типов, но при этом не будете подвергать свою структуру.
Вы можете подделать его, используя указатели функций, и фактически, я думаю, теоретически возможно скомпилировать программы на C ++ в C.
Однако редко имеет смысл навязывать парадигму языку, а не выбирать язык, использующий эту парадигму.
Можно сделать объектно-ориентированный C, я видел такой код в производстве в Корее, и это был самый ужасный монстр, которого я видел за последние годы (это было как в прошлом году (2007), когда я видел код). Так что да, это может быть сделано, и да, люди делали это раньше, и до сих пор делают это даже в наши дни. Но я бы порекомендовал C ++ или Objective-C, оба языка родились из C, с целью обеспечения объектной ориентации с различными парадигмами.
Если вы убеждены в том, что подход ООП лучше, чем проблема, которую вы пытаетесь решить, почему вы пытаетесь решить ее с помощью языка не-ООП? Кажется, вы используете не тот инструмент для работы. Используйте C ++ или другой объектно-ориентированный вариант языка C.
Если вы спрашиваете, потому что вы начинаете кодировать уже существующий большой проект, написанный на C, то вам не следует пытаться навязывать свои собственные (или чьи-либо) парадигмы ООП в инфраструктуре проекта. Следуйте инструкциям, которые уже присутствуют в проекте. В целом, чистые интерфейсы и изолированные библиотеки и модули будут идти длинный путь к имея чистую OOP- иш дизайн.
Если после всего этого вы действительно настроены на выполнение ООП C, прочитайте это (PDF).
Да, ты можешь. Люди писали объектно-ориентированный C до появления C ++ или Objective-C . И C ++, и Objective-C частично пытались взять некоторые концепции ОО, используемые в C, и формализовать их как часть языка.
Вот действительно простая программа, которая показывает, как вы можете сделать что-то похожее на / вызов метода (есть лучшие способы сделать это. Это просто доказательство того, что язык поддерживает концепции):
#include<stdio.h>
struct foobarbaz{
int one;
int two;
int three;
int (*exampleMethod)(int, int);
};
int addTwoNumbers(int a, int b){
return a+b;
}
int main()
{
// Define the function pointer
int (*pointerToFunction)(int, int) = addTwoNumbers;
// Let's make sure we can call the pointer
int test = (*pointerToFunction)(12,12);
printf ("test: %u \n", test);
// Now, define an instance of our struct
// and add some default values.
struct foobarbaz fbb;
fbb.one = 1;
fbb.two = 2;
fbb.three = 3;
// Now add a "method"
fbb.exampleMethod = addTwoNumbers;
// Try calling the method
int test2 = fbb.exampleMethod(13,36);
printf ("test2: %u \n", test2);
printf("\nDone\n");
return 0;
}
Небольшой код OOC для добавления:
#include <stdio.h>
struct Node {
int somevar;
};
void print() {
printf("Hello from an object-oriented C method!");
};
struct Tree {
struct Node * NIL;
void (*FPprint)(void);
struct Node *root;
struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};
int main()
{
struct Tree TreeB;
TreeB = TreeA;
TreeB.FPprint();
return 0;
}
Я копал это в течение одного года:
Поскольку систему GObject сложно использовать с чистым C, я попытался написать несколько хороших макросов, чтобы облегчить стиль OO с помощью C.
#include "OOStd.h"
CLASS(Animal) {
char *name;
STATIC(Animal);
vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
THIS->name = name;
return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)
CLASS_EX(Cat,Animal) {
STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
printf("Meow!My name is %s!\n", THIS->name);
}
static int Cat_loadSt(StAnimal *THIS, void *PARAM){
THIS->talk = (void *)Meow;
return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)
CLASS_EX(Dog,Animal){
STATIC_EX(Dog, Animal);
};
static void Woof(Animal *THIS){
printf("Woof!My name is %s!\n", THIS->name);
}
static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
THIS->talk = (void *)Woof;
return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)
int main(){
Animal *animals[4000];
StAnimal *f;
int i = 0;
for (i=0; i<4000; i++)
{
if(i%2==0)
animals[i] = NEW(Dog,"Jack");
else
animals[i] = NEW(Cat,"Lily");
};
f = ST(animals[0]);
for(i=0; i<4000; ++i) {
f->talk(animals[i]);
}
for (i=0; i<4000; ++i) {
DELETE0(animals[i]);
}
return 0;
}
Вот мой сайт проекта (у меня нет достаточно времени, чтобы написать en. Doc, однако документация на китайском намного лучше).
Существует пример наследования с использованием C в 1996 году разговора Джима Ларсона приведен в Разделе 312 Программирования LunchTime семинара здесь: высокий и низкий уровень C .
Какие статьи или книги хороши для использования концепций ООП в C?
Дейв Хэнсон C Интерфейсы и Реализация является отличными на инкапсуляцию и обозначении и очень хорошо на использовании указателей на функцию. Дейв не пытается имитировать наследование.
Одна вещь, которую вы, возможно, захотите сделать, это изучить реализацию инструментария Xt для X Window . Конечно, он становится длиннее в зубе, но многие из используемых структур были разработаны для работы в ОО-стиле в рамках традиционного С. Как правило, это означает добавление дополнительного слоя косвенности тут и там и конструирование структур для наложения друг на друга.
Таким образом, вы действительно можете многое сделать, если OO находится в C, хотя иногда кажется, что концепции OO не возникли полностью из разума #include<favorite_OO_Guru.h>
. Они действительно составляли многие из признанных лучших практик того времени. ОО языки и системы только дистиллированные и усиленные части программирования времени.
Ответ на вопрос «Да, вы можете».
Объектно-ориентированный C (OOC) комплект предназначен для тех, кто хочет программировать объектно-ориентированным образом, но также придерживается старого доброго C. OOC реализует классы, одиночное и множественное наследование, обработку исключений.
особенности
• Использует только макросы и функции Си, языковые расширения не требуются! (ANSI-C),
• Легко читаемый исходный код для вашего приложения. Забота была сделана, чтобы сделать вещи максимально простыми.
• Одиночное наследование классов
• Множественное наследование по интерфейсам и миксинам (начиная с версии 1.3)
• Реализация исключений (в чистом C!)
• Виртуальные функции для занятий
• Внешний инструмент для легкой реализации класса
Для получения более подробной информации посетите http://ooc-coding.sourceforge.net/ .
Кажется, что люди пытаются эмулировать стиль C ++, используя C. Я предполагаю, что в объектно-ориентированном программировании C действительно есть структурно-ориентированное программирование. Тем не менее, вы можете достичь таких вещей, как позднее связывание, инкапсуляция и наследование. Для наследования вы явно определяете указатель на базовые структуры в вашей подструктуре, и это, очевидно, форма множественного наследования. Вам также необходимо определить, если ваш
//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);
//private_class.c
struct inherited_class_1;
struct inherited_class_2;
struct private_class {
int a;
int b;
struct inherited_class_1 *p1;
struct inherited_class_2 *p2;
};
struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();
struct private_class * new_private_class() {
struct private_class *p;
p = (struct private_class*) malloc(sizeof(struct private_class));
p->a = 0;
p->b = 0;
p->p1 = new_inherited_class_1();
p->p2 = new_inherited_class_2();
return p;
}
int ret_a_value(struct private_class *p, int a, int b) {
return p->a + p->b + a + b;
}
void delete_private_class(struct private_class *p) {
//release any resources
//call delete methods for inherited classes
free(p);
}
//main.c
struct private_class *p;
p = new_private_class();
late_bind_function = &implementation_function;
delete_private_class(p);
скомпилировать с c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj
.
Поэтому совет должен придерживаться чистого стиля C, а не пытаться форсировать стиль C ++. Также этот способ предоставляет очень чистый способ создания API.
См. Http://slkpg.byethost7.com/instance.html еще один поворот ООП в C. Он подчеркивает данные экземпляра для повторного входа с использованием только нативного C. Множественное наследование выполняется вручную с помощью функций-оболочек. Тип безопасности сохраняется. Вот небольшой образец:
typedef struct _peeker
{
log_t *log;
symbols_t *sym;
scanner_t scan; // inherited instance
peek_t pk;
int trace;
void (*push) ( SELF *d, symbol_t *symbol );
short (*peek) ( SELF *d, int level );
short (*get) ( SELF *d );
int (*get_line_number) ( SELF *d );
} peeker_t, SlkToken;
#define push(self,a) (*self).push(self, a)
#define peek(self,a) (*self).peek(self, a)
#define get(self) (*self).get(self)
#define get_line_number(self) (*self).get_line_number(self)
INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
return d->scan.line_number;
}
PUBLIC
void
InitializePeeker ( peeker_t *peeker,
int trace,
symbols_t *symbols,
log_t *log,
list_t *list )
{
InitializeScanner ( &peeker->scan, trace, symbols, log, list );
peeker->log = log;
peeker->sym = symbols;
peeker->pk.current = peeker->pk.buffer;
peeker->pk.count = 0;
peeker->trace = trace;
peeker->get_line_number = get_line_number;
peeker->push = push;
peeker->get = get;
peeker->peek = peek;
}
Я немного опаздываю на вечеринку, но я хочу поделиться своим опытом по этой теме: я работаю со встроенными компонентами в настоящее время, и единственный (надежный) компилятор, который у меня есть, это C, так что я хочу применить объектно-ориентированный подход во встроенных проектах, написанных на C.
Большинство решений, которые я видел до сих пор, интенсивно использует типы типов, поэтому мы теряем безопасность типов: компилятор не поможет вам, если вы допустите ошибку. Это совершенно недопустимо.
Требования, которые у меня есть:
Я подробно объяснил свой подход в этой статье: объектно-ориентированное программирование на C ; Кроме того, есть утилита для автогенерации шаблонного кода для базовых и производных классов.
Я построил небольшую библиотеку, где я попробовал это, и для меня это работает очень хорошо. Вот я и поделился опытом.
https://github.com/thomasfuhringer/oxygen
Одиночное наследование может быть реализовано довольно легко, используя структуру и расширяя ее для любого другого дочернего класса. Простое приведение к родительской структуре позволяет использовать родительские методы для всех потомков. Если вы знаете, что переменная указывает на структуру, содержащую объект такого типа, вы всегда можете привести ее к корневому классу и выполнить самоанализ.
Как уже упоминалось, виртуальные методы несколько сложнее. Но они выполнимы. Для простоты я просто использую массив функций в структуре описания класса, который каждый дочерний класс копирует и, при необходимости, заполняет отдельные слоты.
Многократное наследование будет довольно сложным для реализации и приведет к значительному снижению производительности. Так что я оставляю это. Я считаю желательным и полезным во многих случаях четко моделировать реальные жизненные обстоятельства, но, вероятно, в 90% случаев единственное наследование покрывает потребности. А одиночное наследование простое и ничего не стоит.
Также меня не волнует безопасность типов. Я думаю, что вы не должны зависеть от компилятора, чтобы предотвратить ошибки программирования. И это в любом случае ограждает вас от довольно небольшой части ошибок.
Как правило, в объектно-ориентированной среде вы также хотите реализовать подсчет ссылок для максимально возможной автоматизации управления памятью. Поэтому я также поместил счетчик ссылок в корневой класс «Объект» и некоторые функции для инкапсуляции выделения и освобождения памяти кучи.
Это все очень просто и скудно и дает мне основы ОО, не заставляя меня иметь дело с монстром, который является C ++. И я сохраняю гибкость пребывания на земле C, что, среди прочего, облегчает интеграцию сторонних библиотек.
Я предлагаю использовать Objective-C, который является надмножеством C.
Хотя Objective-C 30 лет, он позволяет писать элегантный код.
Да, но я никогда не видел, чтобы кто-нибудь пытался реализовать какой-либо полиморфизм с C.