Можете ли вы написать объектно-ориентированный код на C?
Можете ли вы написать объектно-ориентированный код на C? Особенно в отношении полиморфизма.
См. Также вопрос переполнения стека. Объектная ориентация в Си.
32 ответа
Да. На самом деле Аксель Шрайнер бесплатно предоставляет свою книгу "Объектно-ориентированное программирование в ANSI-C", которая довольно подробно освещает эту тему.
Поскольку вы говорите о полиморфизме, то да, вы можете, мы занимались такими вещами за годы до появления C++.
В основном вы используете 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
так что вы можете видеть, что различные функции вызываются в зависимости от подкласса.
Пространства имен часто создаются следующим образом:
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 *
Я считаю, что помимо того, что ООП в C полезен сам по себе, он является отличным способом изучения ООП и понимания его внутренней работы. Опыт многих программистов показал, что для эффективного и уверенного использования техники программист должен понимать, как в конечном итоге реализуются базовые концепции. Эмуляция классов, наследования и полиморфизма в 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++.
Подбиблиотека FILE C stdio - отличный пример того, как создать абстракцию, инкапсуляцию и модульность в чистом C.
Наследование и полиморфизм - другие аспекты, которые часто считаются важными для ООП, - не обязательно обеспечивают повышение производительности, которое они обещают, и были выдвинуты разумные аргументы в пользу того, что они действительно могут препятствовать развитию и размышлениям о проблемной области.
Тривиальный пример с животными и собаками: вы отражаете механизм v ++ в 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.
Это было интересно читать. Я сам размышлял над тем же вопросом, и польза от размышлений заключается в следующем:
Попытка представить, как реализовать концепции ООП на языке, отличном от ООП, помогает мне понять сильные стороны языка ООП (в моем случае, C++). Это помогает мне лучше судить о том, использовать ли C или C++ для приложений определенного типа - где преимущества одного перевешивают другое.
Просматривая в Интернете информацию и мнения по этому вопросу, я обнаружил автора, который писал код для встроенного процессора и имел только доступный компилятор C: http://www.eetimes.com/discussion/other/4024626/Object-Oriented-C-Creating-Foundation-Classes-Part-1
В его случае анализ и адаптация ООП-концепций в простом С были обоснованным занятием. Похоже, он был готов пожертвовать некоторыми концепциями ООП из-за снижения производительности, вызванного попыткой реализовать их в C.
Урок, который я извлек, заключается в том, что да, это можно сделать в определенной степени, и да, есть несколько веских причин для его попытки.
В конце концов, машина перебирает биты указателя стека, заставляя счетчик программ перепрыгивать и вычисляя операции доступа к памяти. С точки зрения эффективности, чем меньше таких вычислений выполняется вашей программой, тем лучше... но иногда нам приходится платить этот налог просто, чтобы мы могли организовать нашу программу таким образом, чтобы она была менее подвержена человеческим ошибкам. Компилятор языка ООП стремится оптимизировать оба аспекта. Программист должен быть намного осторожнее при реализации этих концепций на языке, подобном C.
Проверьте GObject. Это должно быть ОО в С и одна реализация того, что вы ищете. Если вы действительно хотите использовать ОО, используйте C++ или другой язык ООП. Иногда с GObject может быть очень сложно работать, если вы привыкли иметь дело с ОО-языками, но, как и все остальное, вы привыкнете к соглашениям и потоку.
Есть несколько методов, которые можно использовать. Самый важный из них - как разделить проект. В нашем проекте мы используем интерфейс, который объявлен в файле.h, а реализацию объекта - в файле.c. Важной частью является то, что все модули, которые включают файл.h, видят только объект как 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 могут быть изменены в одном модуле, и другой модуль даже не нужно перекомпилировать, чтобы все еще использовать.
С этим стилем я уже рассмотрел большую часть преимуществ ООП (инкапсуляция данных). Используя указатели функций, даже легко реализовать что-то вроде наследования, но, честно говоря, это действительно редко используется.
Возможно, вам будет полезно взглянуть на документацию Apple по ее базовому набору API-интерфейсов. Это чистый API C, но многие типы связаны с объектными эквивалентами Objective-C.
Вам также может быть полезно взглянуть на конструкцию самой Objective-C. Он немного отличается от C++ тем, что объектная система определена в терминах функций C, например objc_msg_send
вызвать метод на объекте. Компилятор переводит синтаксис в квадратные скобки в эти вызовы функций, так что вам не нужно это знать, но, учитывая ваш вопрос, вам может быть полезно узнать, как он работает под капотом.
Вы можете подделать его, используя указатели функций, и фактически, я думаю, теоретически возможно скомпилировать программы на C++ в C.
Однако редко имеет смысл навязывать парадигму языку, а не выбирать язык, использующий эту парадигму.
Да, ты можешь. Люди писали объектно-ориентированный 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;
}
Конечно, это будет не так красиво, как использование языка со встроенной поддержкой. Я даже написал "объектно-ориентированный ассемблер".
Если вы убеждены в том, что подход ООП лучше, чем проблема, которую вы пытаетесь решить, зачем вам пытаться решить ее с помощью языка не-ООП? Кажется, вы используете не тот инструмент для работы. Используйте C++ или другой объектно-ориентированный вариантный язык C.
Если вы спрашиваете, потому что вы начинаете кодировать уже существующий большой проект, написанный на C, то вам не следует пытаться навязывать свои собственные (или чьи-либо) парадигмы ООП в инфраструктуру проекта. Следуйте инструкциям, которые уже присутствуют в проекте. В целом, чистые API и изолированные библиотеки и модули будут иметь большое значение для создания чистого дизайна.
Если после всего этого вы действительно настроены на выполнение ООП C, прочитайте это (PDF).
Можно сделать объектно-ориентированный C, я видел такой код в производстве в Корее, и это был самый ужасный монстр, которого я видел за последние годы (это было, как в прошлом году (2007), когда я видел код). Так что да, это может быть сделано, и да, люди делали это раньше, и до сих пор делают это даже в наши дни. Но я бы порекомендовал C++ или Objective-C, оба языка родились из C, с целью обеспечения объектной ориентации с различными парадигмами.
Небольшой код 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, однако документация на китайском намного лучше).
Одна вещь, которую вы, возможно, захотите сделать, - изучить реализацию инструментария Xt для X Window. Конечно, он становится длиннее в зубе, но многие из используемых структур были спроектированы так, чтобы работать в режиме ОО в рамках традиционного С. Как правило, это означает добавление дополнительного слоя косвенности здесь и там и конструирование структур для наложения друг на друга.
Таким образом, вы действительно можете многое сделать в ОО, расположенном в С, хотя иногда кажется, что концепции ОО не возникли полностью из разума #include<favorite_OO_Guru.h>
, Они действительно составляли многие из признанных лучших практик того времени. ОО языки и системы только дистиллированные и усиленные части программирования времени.
Есть пример наследования с использованием C в выступлении Джима Ларсона в 1996 году, которое было дано на Семинаре по программированию в разделе 312 здесь: Высокий и низкий уровень C.
Какие статьи или книги хороши для использования концепций ООП в C?
C-интерфейсы и реализации Дейва Хэнсона превосходны в отношении инкапсуляции и именования и очень хороши в использовании указателей на функции. Дейв не пытается имитировать наследование.
ООП - это всего лишь парадигма, которая ставит данные как более важные, чем код в программах. ООП это не язык. Итак, как простой C является простым языком, ООП в простом C также прост.
Ответ на вопрос "Да, вы можете".
Объектно-ориентированный 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;
}
Я построил небольшую библиотеку, где я попробовал это, и для меня это работает очень хорошо. Вот я и поделился опытом.
https://github.com/thomasfuhringer/oxygen
Одиночное наследование может быть реализовано довольно легко, используя структуру и расширяя ее для любого другого дочернего класса. Простое приведение к родительской структуре позволяет использовать родительские методы для всех потомков. Если вы знаете, что переменная указывает на структуру, содержащую объект такого типа, вы всегда можете привести ее к корневому классу и выполнить самоанализ.
Как уже упоминалось, виртуальные методы несколько сложнее. Но они выполнимы. Для простоты я просто использую массив функций в структуре описания класса, который каждый дочерний класс копирует и, при необходимости, заполняет отдельные слоты.
Многократное наследование будет довольно сложным для реализации и приведет к значительному снижению производительности. Поэтому я оставляю это. Я действительно считаю желательным и полезным во многих случаях четко моделировать реальные жизненные обстоятельства, но, вероятно, в 90% случаев единственное наследование покрывает потребности. А одиночное наследование простое и ничего не стоит.
Также меня не волнует безопасность типов. Я думаю, что вы не должны зависеть от компилятора, чтобы предотвратить ошибки программирования. И в любом случае это ограждает вас от довольно небольшой части ошибок.
Как правило, в объектно-ориентированной среде вы также хотите реализовать подсчет ссылок, чтобы максимально автоматизировать управление памятью. Поэтому я также поместил счетчик ссылок в корневой класс "Объект" и некоторые функциональные возможности для инкапсуляции выделения и освобождения памяти кучи.
Это все очень просто и скудно и дает мне основы ОО, не заставляя меня иметь дело с монстром, который является C++. И я сохраняю гибкость пребывания на земле C, что, среди прочего, облегчает интеграцию сторонних библиотек.
Я немного опаздываю на вечеринку, но я хочу поделиться своим опытом по этой теме: я работаю со встроенными компонентами в настоящее время, и единственный (надежный) компилятор, который у меня есть, это C, так что я хочу применить объектно-ориентированный подход во встроенных проектах, написанных на C.
Большинство решений, которые я видел до сих пор, интенсивно использует типы типов, поэтому мы теряем безопасность типов: компилятор не поможет вам, если вы допустите ошибку. Это совершенно недопустимо.
Требования, которые у меня есть:
- Как можно больше избегайте типов, чтобы не потерять безопасность типов;
- Полиморфизм: мы должны иметь возможность использовать виртуальные методы, а пользователь класса не должен знать, является ли какой-то конкретный метод виртуальным или нет;
- Множественное наследование: я не часто его использую, но иногда я действительно хочу, чтобы какой-то класс реализовывал несколько интерфейсов (или расширял несколько суперклассов).
Я подробно объяснил свой подход в этой статье: объектно-ориентированное программирование на C; Кроме того, есть утилита для автогенерации шаблонного кода для базовых и производных классов.
Я предлагаю использовать Objective-C, который является надмножеством C.
В то время как Objective-C 30 лет, он позволяет писать элегантный код.
Да, это возможно.
Это чистый C, без предварительной обработки макросов. Он имеет наследование, полиморфизм, инкапсуляцию данных (в том числе личных данных). Он не имеет эквивалентного защищенного квалификатора, что означает, что частные данные также являются частными в цепочке наследования.
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"
#include <stdio.h>
int main()
{
Triangle tr1= CTriangle->new();
Rectangle rc1= CRectangle->new();
tr1->width= rc1->width= 3.2;
tr1->height= rc1->height= 4.1;
CPolygon->printArea((Polygon)tr1);
printf("\n");
CPolygon->printArea((Polygon)rc1);
}
/*output:
6.56
13.12
*/