Как написать объектно-ориентированный код на C?
Как можно написать объектно-ориентированный код на C? Особенно в отношении полиморфизма.
См. также вопрос Кару Объектная ориентация на C.
Ответы
Ответ 2
Поскольку вы говорите о полиморфизме, тогда да, вы можете, мы делали такие вещи за несколько лет до появления С++.
В основном вы используете struct
для хранения как данных, так и списка указателей функций, чтобы указать на соответствующие функции для этих данных.
Итак, в классе связи у вас будет открытый вызов, чтение, запись и закрытие, которое будет поддерживаться как четыре указателя функции в структуре вместе с данными для объекта, например:
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 - поведение будет немного отличаться от С++ (дамп ядра во время выполнения, а не во время компиляции).
Вот фрагмент кода примера, который демонстрирует его. Сначала структура класса верхнего уровня:
#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
чтобы вы могли видеть, что разные функции вызывают, в зависимости от подкласса.
Ответ 3
Пространства имен часто выполняются с помощью:
stack_push(thing *)
вместо
stack::push(thing *)
Чтобы сделать 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 похож на статическую переменную класса - распределяется между всеми экземплярами типа. Все методы действительно статичны, за исключением того, что некоторые принимают этот *
Ответ 4
Я считаю, что, помимо того, что он полезен сам по себе, реализация ООП на С - отличный способ узнать ООП и понять его внутреннюю работу. Опыт многих программистов показал, что для эффективного и уверенного использования метода программист должен понять, как основополагающие концепции в конечном итоге реализованы. Эмуляция классов, наследования и полиморфизма в 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
Ответ 5
Я видел это. Я бы не рекомендовал его. С++ первоначально начал этот путь в качестве препроцессора, который создал C-код в качестве промежуточного шага.
По сути дела, что вы делаете, создайте таблицу рассылки для всех ваших методов, где вы храните ссылки на функции. Получение класса приведет к копированию этой таблицы диспетчеризации и замене записей, которые вы хотите переопределить, а ваши новые "методы" должны вызвать исходный метод, если он хочет вызвать базовый метод. В конце концов вы закончите переписывание С++.
Ответ 6
Конечно, это возможно. Это то, что делает GObject, инфраструктура, на которой основаны все GTK+ и GNOME.
Ответ 7
Суб-библиотека C stdio FILE - отличный пример того, как создавать абстрагирование, инкапсуляцию и модульность в чистом виде C.
Наследование и полиморфизм - другие аспекты, которые часто считаются важными для ООП, - не обязательно обеспечивают прирост производительности, который они обещают, и разумный аргументы были сделаны , что они могут фактически препятствовать развитию и думать о проблемная область.
Ответ 8
Тривиальный пример с животными и собакой: вы зеркалируете механизм С++ vtable (в любом случае). Вы также разделяете выделение и создание экземпляра (Animal_Alloc, Animal_New), поэтому мы не вызываем malloc() несколько раз. Мы также должны явно передать указатель this
.
Если бы вы делали не виртуальные функции, это триволь. Вы просто не добавляете их в таблицу vtable, а для статических функций не требуется указатель this
. Для множественного наследования обычно требуется несколько vtables для устранения неоднозначностей.
Кроме того, вы должны иметь возможность использовать 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.
Ответ 9
Отъезд GObject. Это означает, что OO в C и одна реализация того, что вы ищете. Если вы действительно хотите OO, пойдите с С++ или другим языком OOP. GObject может быть очень сложно работать, если вы привыкли работать с языками OO, но, как и все, вы привыкнете к соглашениям и потоку.
Ответ 10
Это было интересно прочитать. Я сам обдумывал один и тот же вопрос, и о преимуществах думать об этом можно:
-
Попытка представить себе, как реализовать концепции ООП на языке, отличном от ООП, помогает мне понять сильные стороны языка OOp (в моем случае, С++). Это помогает мне лучше судить о том, использовать ли C или С++ для данного типа приложений - где преимущества одного взвешивания другого.
-
В моем браузере в Интернете для информации и мнений по этому вопросу я нашел автора, который писал код для встроенного процессора и имел только компилятор C: http://www.eetimes.com/discussion/other/4024626/Object-Oriented-C-Creating-Foundation-Classes-Part-1
В его случае анализ и адаптация концепций ООП на простом С был действительным преследованием. Похоже, что он был готов пожертвовать некоторыми концепциями ООП из-за высокого служебного удара, вызванного попыткой реализовать их на C.
Урок, который я взял, - это можно сделать в определенной степени, и да, есть некоторые веские причины для его попытки.
В конце концов, машина представляет собой биты указателя стека, что ускоряет сканирование программы и вычисляет операции доступа к памяти. С точки зрения эффективности, чем меньше этих вычислений выполняется вашей программой, тем лучше... но иногда мы должны платить этот налог просто, чтобы мы могли организовать нашу программу таким образом, чтобы она была наименее восприимчивой к человеческой ошибке. Компилятор языка ООП стремится оптимизировать оба аспекта. Программист должен быть гораздо более осторожным, реализуя эти понятия на языке, таком как C.
Ответ 11
Вам может быть полезно ознакомиться с документацией Apple для набора API-интерфейсов Core Foundation. Это чистый API C, но многие из этих типов соединяются с эквивалентами объектов Objective-C.
Вам также может показаться полезным взглянуть на дизайн самого Objective-C. Он немного отличается от С++ тем, что объектная система определена в терминах C-функций, например. objc_msg_send
, чтобы вызвать метод для объекта. Компилятор преобразует синтаксис квадратной скобки в эти вызовы функций, поэтому вам не обязательно это знать, но, учитывая ваш вопрос, вы можете найти его полезным, чтобы узнать, как он работает под капотом.
Ответ 12
Существует несколько методов, которые можно использовать. Наиболее важным является то, как разделить проект. Мы используем интерфейс в нашем проекте, который объявлен в файле .h и реализации объекта в .c файле. Важная часть состоит в том, что все модули, содержащие файл .h, видят только объект как void *
, а .c файл - единственный модуль, который знает внутренние структуры.
Что-то вроде этого для класса мы называем FOO в качестве примера:
В файле .h
#ifndef FOO_H_
#define FOO_H_
...
typedef struct FOO_type FOO_type; /* That 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 мы его явно пишем.
Я действительно использую this
в своих программах, чтобы убедиться, что моя программа не скомпилирована на С++, и обладает прекрасным свойством быть в другом цвете в редакторе выделения синтаксиса.
Поля FOO_struct могут быть изменены в одном модуле, а другой модуль даже не нужно перекомпилировать, чтобы быть еще пригодным для использования.
В этом стиле я уже обрабатываю большую часть преимуществ OOP (инкапсуляция данных). С помощью указателей функций он даже легко реализует что-то вроде наследования, но, честно говоря, он действительно редко бывает полезен.
Ответ 13
Вы можете подделать его с помощью указателей на функции, и на самом деле я думаю, что теоретически возможно скомпилировать С++-программы в C.
Однако редко бывает смысл заставить парадигму на языке, а не выбирать язык, который использует парадигму.
Ответ 14
Объектно-ориентированный C, можно сделать, я видел этот тип кода в производстве в Корее, и это был самый ужасный монстр, который я видел за эти годы (это было похоже на прошлогодний (2007), что я видел код). Так что да, это можно сделать, и да, люди сделали это раньше, и все еще делают это даже в этот день и в возрасте. Но я бы рекомендовал С++ или Objective-C, оба языка - это языки, родившиеся от C, с целью обеспечения объектной ориентации различными парадигмами.
Ответ 15
Если вы убеждены, что подход ООП превосходит проблему, которую вы пытаетесь решить, почему вы пытаетесь решить ее с помощью языка, отличного от OOP? Похоже, вы используете неправильный инструмент для работы. Используйте С++ или какой-либо другой объектно-ориентированный язык с вариантами C.
Если вы спрашиваете, потому что вы начинаете кодировать уже существующий большой проект, написанный на C, тогда вы не должны пытаться принудительно ввести свои собственные (или чужие) парадигмы ООП в инфраструктуру проекта. Следуйте рекомендациям, которые уже присутствуют в проекте. В общем, чистые API-интерфейсы и изолированные библиотеки и модули будут иметь большое значение для создания чистого OOP- ish.
Если после этого вы действительно настроились на выполнение OOP C, прочитайте this (PDF).
Ответ 16
Да, вы можете. Люди писали объектно-ориентированную C до С++ или Objective-C появился на сцене. И С++, и Objective-C были, по частям, попытками взять некоторые из концепций OO, используемые в 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 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;
}
Ответ 17
Конечно, это будет не так хорошо, как использование языка со встроенной поддержкой. Я даже написал "объектно-ориентированный ассемблер".
Ответ 18
Небольшой код 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;
}
Ответ 19
Я копаю это в течение одного года:
Поскольку система 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, однако документ на китайском языке намного лучше).
Ответ 20
Существует пример наследования с использованием слова C в книге Джима Ларсона 1996 года, приведенного в разделе Раздел 312 Семинар по программированию на обед: Высокий и низкий уровень C.
Ответ 21
Одна вещь, которую вы, возможно, захотите сделать, - это изучить Xt toolkit для X Window. Конечно, это длится долго, но многие из используемых конструкций были разработаны для работы в стиле OO в традиционном C. Обычно это означает добавление дополнительного слоя косвенности здесь и там и проектирование структур, чтобы наложить друг на друга.
Вы действительно можете делать много на пути OO, расположенного на C таким образом, хотя это несколько раз кажется, что понятия OO не были полностью сформированы из разума #include<favorite_OO_Guru.h>
. Они действительно составляли многие из сложившейся лучшей практики того времени. Языки OO и системы только дистиллировали и усиливали части программирующего духа времени.
Ответ 22
Какие статьи или книги полезны для использования концепций ООП в C?
Dave Hanson C Интерфейсы и реализации превосходны при инкапсуляции, именовании и очень хорошем использовании указателей на функции. Дейв не пытается имитировать наследование.
Ответ 23
ООП - это всего лишь парадигма, которая ставит данные как более важные, чем код в программах. ООП - это не язык. Таким образом, как простой C - простой язык, OOP в простой C тоже прост.
Ответ 24
Ответ на вопрос: "Да, вы можете".
Объектно-ориентированный набор C (OOC) предназначен для тех, кто хочет программировать объектно-ориентированным образом, но также придерживается старого старого C. OOC реализует классы, однократное и множественное наследование, обработку исключений.
Функции
• Использует только макросы и функции C, никаких языковых расширений не требуется! (ANSI-C)
• Легко читаемый исходный код для вашего приложения. Позаботились о том, чтобы сделать все как можно проще.
• Одиночное наследование классов
• Множественное наследование по интерфейсам и mixins (начиная с версии 1.3)
• Реализация исключений (в чистом C!)
• Виртуальные функции для классов
• Внешний инструмент для легкой реализации класса
Подробнее см. http://ooc-coding.sourceforge.net/.
Ответ 25
Похоже, что люди пытаются эмулировать стиль С++, используя 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 и не пытаться вставить в стиль С++. Также этот способ поддается очень чистому способу построения API.
Ответ 26
Смотрите 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;
}
Ответ 27
Я немного опаздываю на вечеринку, но хочу поделиться своим опытом по этой теме: в наши дни я работаю со встроенным материалом, и единственным (надежным) компилятором, который у меня есть, является C, поэтому я хочу применить объектно-ориентированный подход в моих встроенных проектах, написанных на C.
Большинство решений, которые я видел до сих пор, сильно влияют на моделизм, поэтому мы теряем безопасность типов: компилятор не поможет вам, если вы допустили ошибку. Это совершенно неприемлемо.
Требования, которые у меня есть:
- Избегайте приведения типов как можно больше, поэтому мы не теряем безопасность типов;
- Полиморфизм: мы должны иметь возможность использовать виртуальные методы, и пользователь класса не должен знать, является ли какой-либо конкретный метод виртуальным или нет;
- Множественное наследование: я не часто его использую, но иногда мне действительно нужен какой-то класс для реализации нескольких интерфейсов (или для расширения нескольких суперклассов).
Я подробно объяснил свой подход в этой статье: Объектно-ориентированное программирование в C; плюс, есть утилита для автогенерации кода шаблона для базового и производного классов.
Ответ 28
Я построил небольшую библиотеку, где я пробовал это, и для меня это работает очень хорошо. Поэтому я решил поделиться опытом.
https://github.com/thomasfuhringer/oxygen
Одиночное наследование можно легко реализовать с помощью структуры и расширить ее для каждого другого дочернего класса. Простое внесение в родительскую структуру позволяет использовать родительские методы для всех потомков. До тех пор, пока вы знаете, что переменная указывает на структуру, содержащую этот тип объекта, вы всегда можете отнести к корневому классу и сделать интроспекцию.
Как уже упоминалось, виртуальные методы несколько сложнее. Но они выполнимы. Чтобы все было просто, я просто использую массив функций в структуре описания класса, который каждый дочерний класс копирует и репопулирует отдельные слоты, где это необходимо.
Множественное наследование будет довольно сложно реализовать и будет иметь значительное влияние на производительность. Поэтому я оставляю это. Я считаю, что желательно и полезно в довольно многих случаях четко моделировать реальные жизненные обстоятельства, но, вероятно, в 90% случаев однонамерное наследование покрывает потребности. И одно наследование простое и ничего не стоит.
Также я не забочусь о безопасности типов. Я думаю, вы не должны зависеть от компилятора, чтобы вы не программировали ошибки. И в любом случае он защищает вас только от небольшой части ошибок.
Как правило, в объектно-ориентированной среде вы также хотите внедрить подсчет ссылок, чтобы максимально автоматизировать управление памятью. Поэтому я также поместил счетчик ссылок в корневой класс "Объект" и некоторые функции для инкапсуляции выделения и освобождения памяти кучи.
Все это очень просто и худощаво и дает мне основы OO, не заставляя меня иметь дело с монстром, который является С++. И я сохраняю гибкость пребывания на земле C, которая, среди прочего, упрощает интеграцию сторонних библиотек.
Ответ 29
Я предлагаю использовать Objective-C, который является надмножеством C.
Пока Objective-C исполнилось 30 лет, он позволяет писать элегантный код.
Ответ 30
Да, но я никогда не видел, чтобы кто-либо пытался реализовать какой-либо полиморфизм с C.