Как написать объектно-ориентированный код на C? [закрыто]

500

Как можно написать объектно-ориентированный код на C? Особенно в отношении полиморфизма.


См. Также этот вопрос переполнения стека Объект-ориентация в Си .

Peter Mortensen
источник
1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Объектно- ориентированное программирование на C </a> Лорана Денио
Возможно, вы захотите взглянуть на ответы на этот вопрос: какие методы / стратегии люди используют для создания объектов в C (не в C ++)?
Дейл Хэгглунд
25
@Camilo Мартин: Я намеренно спросил могу не должен . На самом деле я не заинтересован в использовании ООП в C. Однако, увидев ОО-решения в C, я / мы узнаем больше об ограничениях и / или гибкости C, а также о творческих способах реализации и использования полиморфизма.
Дина
5
ОО это просто шаблон. Проверьте здесь, это можно сделать даже в .bat Files: dirk.rave.org/chap9.txt (я думаю, что любой шаблон может быть применен к любому языку программирования, если вы достаточно заинтересованы). Это хорошая пища для размышлений. И, вероятно, можно многому научиться, применяя такие шаблоны, которые мы считаем само собой разумеющимся в тех языках, где их нет.
Камило Мартин
6
GTK - «простите меня, GObject - на самом деле довольно хороший пример ООП (сорта) в Си. Так что, отвечая @Camilo, для C интерполируемости.
new123456 14.12.10

Ответы:

362

Да. На самом деле Аксель Шрайнер бесплатно предоставляет свою книгу «Объектно-ориентированное программирование в ANSI-C», которая довольно подробно освещает эту тему.

mepcotterell
источник
28
В то время как понятия в этой книге являются твердыми, вы потеряете безопасность типов.
Diapir
22
До того, что мы знаем как шаблоны проектирования, был шаблон дизайна, известный как «ориентация объекта»; то же самое со сборкой мусора и прочее такое. Сейчас они настолько укоренились, что мы склонны забывать, что когда они только создавались, это было во многом таким же образом, как и с тем, что мы сегодня
называем
11
Вы можете получить его прямо с сайта автора: cs.rit.edu/~ats/books/ooc.pdf других работ от одного автора: cs.rit.edu/~ats/books/index.html
PakMan
10
Соответствующий сборник (Примеры книг + исходного кода) доступен из этого индекса rit.edu. Объектно-ориентированное программирование с использованием ANSI-C
Дэвид С. Ранкин,
3
Эта книга рецензируется? В первом предложении первого абзаца первой страницы есть опечатка.
Dagrooms
343

Поскольку вы говорите о полиморфизме, то да, вы можете, мы занимались такими вещами за годы до появления 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

так что вы можете видеть, что различные функции вызываются в зависимости от подкласса.

paxdiablo
источник
52
Инкапсуляция довольно проста, полиморфизм выполним - но наследование сложно
Мартин Беккет
5
lwn.net недавно опубликовал статью под названием Object-Oriented design Patterns в ядре на тему структур, аналогичную приведенному выше ответу, то есть структуру, содержащую указатели на функции, или указатель на структуру, имеющую функции, которые принимают указатель на структура с данными, с которыми мы работаем в качестве параметра.
Радикальный
11
+1 Хороший пример! Хотя, если кто-то действительно хочет пойти по этому пути, было бы более целесообразно, чтобы структуры «экземпляра» имели одно поле, указывающее на экземпляр «виртуальной таблицы», содержащий все виртуальные функции для этого типа в одном месте. Т.е. ваш tCommClassбудет переименован tCommVT, а tCommClassструктура будет иметь только поля данных и одно tCommVT vtполе, указывающее на «одну-единственную» виртуальную таблицу. Перенос всех указателей с каждым экземпляром добавляет ненужные накладные расходы и напоминает больше о том, как вы будете делать вещи в JavaScript, чем в C ++, ИМХО.
Groo
1
Итак, это демонстрирует реализацию единого интерфейса, но как насчет реализации нескольких интерфейсов? Или множественное наследование?
weberc2
Вебер, если вам нужна вся функциональность C ++, вам, вероятно, следует использовать C ++. Вопрос задан конкретно о полиморфизме, способности объектов принимать различные «формы». Конечно, вы можете создавать интерфейсы и множественное наследование в C, но это довольно трудоемкая работа, и вы должны сами управлять смартами, а не использовать встроенные средства C ++.
paxdiablo
86

Пространства имен часто создаются следующим образом:

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 *

nategoose
источник
1
@nategoose - st->my_type->push(st, thing2);вместоst->my_type.push(st, thing2);
Fabricio
@nategoose: ИЛИ struct stack_type my_type; вместоstruct stack_type * my_type;
Fabricio
3
Мне нравится концепция наличия структуры для класса. Но как насчет общей Classструктуры? Это сделало бы OO C более динамичным, чем C ++. Как насчет этого? Кстати, +1.
Linuxios
54

Я считаю, что помимо того, что ООП в Си полезен сам по себе, он является отличным способом изучения ООП и понимания его внутренней работы. Опыт многих программистов показал, что для эффективного и уверенного использования техники программист должен понимать, как в конечном итоге реализуются базовые концепции. Эмуляция классов, наследования и полиморфизма в 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

Миро
источник
4
Новый URL для руководства C +: state-machine.com/doc/cplus_3.0_manual.pdf
Лян
32

Я видел это сделано. Я бы не рекомендовал это. C ++ изначально начинал этот путь как препроцессор, который создавал код на C как промежуточный этап.

По сути, в конечном итоге вы создаете таблицу диспетчеризации для всех ваших методов, где храните ссылки на функции. Извлечение класса повлечет за собой копирование этой таблицы диспетчеризации и замену записей, которые вы хотите переопределить, с вашими новыми «методами», вызывающими исходный метод, если он хочет вызвать базовый метод. В конечном итоге вы переписываете C ++.

tvanfosson
источник
5
«В конце концов, вы в конечном итоге переписываете C ++», - подумал я, боится ли это.
Дина
39
Или вы можете переписать Objective C, что будет гораздо более привлекательным результатом.
Проф. Фалькен нарушил контракт
3
В OOP существует бесклассовая версия, такая как в Javascript , где гуру говорит: «Нам не нужны классы для создания множества похожих объектов». Но я боюсь, что это нелегко достичь в C. Но (пока) не в состоянии сказать, хотя. (Существует ли подпрограмма clone () для клонирования структуры?)
Lumi
1
Другие умные ребята, которые должны были на самом деле реализовать это и сделать эту реализацию быстрой (Google, движок V8), сделали все, чтобы добавить (скрыть) классы обратно в JavaScript.
cubuspl42
Не glibнаписано в C в объективном смысле?
Кравемир
26

Конечно, это возможно. Это то , что делает GObject , фреймворк, на котором основаны все GTK + и GNOME .

Peter Mortensen
источник
Каковы плюсы / минусы такого подхода? То есть. это гораздо проще написать с помощью C ++.
Кравемир
@kravemir Ну, C ++ не так переносим, ​​как C, и немного сложнее связать C ++ с кодом, который может быть скомпилирован другим компилятором C ++. Но да, легче писать классы на C ++, хотя GObject на самом деле не так уж и сложен (если вы не возражаете против небольшой основы).
Эдвин Бак
17

Подбиблиотека FILE C stdio - отличный пример того, как создать абстракцию, инкапсуляцию и модульность в чистом C.

Наследование и полиморфизм - другие аспекты, которые часто считаются важными для ООП, - не обязательно обеспечивают повышение производительности, которое они обещают, и были выдвинуты разумные аргументы , что они могут фактически препятствовать развитию и размышлениям о проблемной области.

мсв
источник
Разве stdio не абстрагируется на уровне ядра? Если я не ошибаюсь, C-библиотека обрабатывает их как символьные файлы / устройства, а драйверы ядра выполняют свою работу, ...
kravemir
15

Тривиальный пример с животными и собаками: вы отражаете механизм 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.

Jasper Bekkers
источник
typedefВнутри это structневозможно в C.
Масуд
13

Проверьте GObject . Это должно быть ОО в С и одна реализация того, что вы ищете. Если вы действительно хотите использовать OO, используйте C ++ или другой язык OOP. Иногда с GObject может быть очень сложно работать, если вы привыкли иметь дело с ОО-языками, но, как и все остальное, вы привыкнете к соглашениям и потоку.

NG.
источник
12

Это было интересно читать. Я сам размышлял над тем же вопросом, и польза от размышлений заключается в следующем:

  • Попытка представить, как реализовать концепции ООП на языке, отличном от ООП, помогает мне понять сильные стороны языка ООП (в моем случае, C ++). Это помогает мне лучше понять, использовать ли C или C ++ для приложения определенного типа - где преимущества одного перевешивают другое.

  • Просматривая в Интернете информацию и мнения по этому вопросу, я обнаружил автора, который писал код для встроенного процессора и имел только доступный компилятор C: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -С-Создание-Foundation-классы-Part-1

В его случае анализ и адаптация ООП-концепций в простом С были вполне обоснованными. Похоже, он был готов пожертвовать некоторыми концепциями ООП из-за снижения производительности, вызванного попыткой реализовать их в C.

Урок, который я извлек, заключается в том, что да, это можно сделать в определенной степени, и да, есть несколько веских причин, чтобы попробовать это.

В конце концов, машина перебирает биты указателя стека, заставляя счетчик программ перемещаться и вычисляя операции доступа к памяти. С точки зрения эффективности, чем меньше таких вычислений выполняется вашей программой, тем лучше ... но иногда нам приходится платить этот налог просто, чтобы мы могли организовать нашу программу таким образом, чтобы она была менее подвержена человеческим ошибкам. Компилятор языка ООП стремится оптимизировать оба аспекта. Программист должен быть намного осторожнее при реализации этих концепций на языке, подобном C.

RJB
источник
10

Возможно, вам будет полезно взглянуть на документацию Apple, касающуюся набора API Core Foundation. Это чистый API C, но многие типы связаны с объектными эквивалентами Objective C.

Вам также может быть полезно взглянуть на дизайн самой Objective-C. Он немного отличается от C ++ в том, что объектная система определяется в терминах функций C, например, objc_msg_sendдля вызова метода объекта. Компилятор переводит синтаксис в квадратные скобки в эти вызовы функций, так что вам не нужно это знать, но, учитывая ваш вопрос, вам может быть полезно узнать, как он работает под капотом.

benzado
источник
10

Есть несколько методов, которые можно использовать. Самый важный из них - как разделить проект. В нашем проекте мы используем интерфейс, который объявлен в файле .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 могут быть изменены в одном модуле, и другой модуль даже не нужно перекомпилировать, чтобы все еще использовать.

С этим стилем я уже рассмотрел большую часть преимуществ ООП (инкапсуляция данных). Используя указатели функций, даже легко реализовать что-то вроде наследования, но, честно говоря, это действительно редко используется.

Patrick Schlüter
источник
6
Если вы сделаете typedef struct FOO_type FOO_typeвместо typedef значение void в заголовке, вы получите дополнительное преимущество проверки типов, но при этом не будете подвергать свою структуру.
Скотт Уэльс
8

Вы можете подделать его, используя указатели функций, и фактически, я думаю, теоретически возможно скомпилировать программы на C ++ в C.

Однако редко имеет смысл навязывать парадигму языку, а не выбирать язык, использующий эту парадигму.

Uri
источник
5
Самый первый компилятор C ++ сделал именно это - он преобразовал код C ++ в эквивалентный (но уродливый и нечитаемый человеком) код C, который затем был скомпилирован компилятором C.
Адам Розенфилд
2
EDG, Cfront и некоторые другие все еще способны сделать это. По очень веской причине: не на каждой платформе есть компилятор C ++.
Джаспер Беккерс
По какой-то причине я подумал, что C-front поддерживает только определенные расширения C ++ (например, ссылки), но не полную эмуляцию ООП / динамической диспетчеризации.
Ури
2
Вы также можете сделать то же самое с LLVM и бэкэндом C.
Zifre
7

Можно сделать объектно-ориентированный C, я видел такой код в производстве в Корее, и это был самый ужасный монстр, которого я видел за последние годы (это было как в прошлом году (2007), когда я видел код). Так что да, это может быть сделано, и да, люди делали это раньше, и до сих пор делают это даже в наши дни. Но я бы порекомендовал C ++ или Objective-C, оба языка родились из C, с целью обеспечения объектной ориентации с различными парадигмами.

Роберт Гулд
источник
3
если Линус увидит твой комментарий. Он определенно либо посмеется, либо проклянет вас
Андерс Линд
7

Если вы убеждены в том, что подход ООП лучше, чем проблема, которую вы пытаетесь решить, почему вы пытаетесь решить ее с помощью языка не-ООП? Кажется, вы используете не тот инструмент для работы. Используйте C ++ или другой объектно-ориентированный вариант языка C.

Если вы спрашиваете, потому что вы начинаете кодировать уже существующий большой проект, написанный на C, то вам не следует пытаться навязывать свои собственные (или чьи-либо) парадигмы ООП в инфраструктуре проекта. Следуйте инструкциям, которые уже присутствуют в проекте. В целом, чистые интерфейсы и изолированные библиотеки и модули будут идти длинный путь к имея чистую OOP- иш дизайн.

Если после всего этого вы действительно настроены на выполнение ООП C, прочитайте это (PDF).

оборота РаррРаррРарр
источник
36
Не совсем отвечая на вопрос ...
Брайан Постоу
2
@ Брайан, ссылка на PDF, похоже, ответит прямо на вопрос, хотя у меня не было времени проверить это самостоятельно.
Марк Рэнсом
5
Ссылка на PDF, похоже, представляет собой целый учебник по этому вопросу ... Прекрасное доказательство, но оно не вписывается в рамки ...
Брайан Постоу
5
да, ответь на вопрос совершенно правильно спросить, как использовать язык определенным образом. не было запроса о мнении на других языках ....
Тим Ринг
9
@Brian & Tim Ring: вопрос, заданный для рекомендаций книги по теме; Я дал ему ссылку на книгу , специально посвященную этой теме. Я также высказал свое мнение о том, почему подход к проблеме может быть неоптимальным (с чем, я думаю, многие здесь согласны, основываясь на голосах и других комментариях / ответах). У вас есть предложения по улучшению моего ответа?
RarrRarrRarr
6

Да, ты можешь. Люди писали объектно-ориентированный 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;
}
Alan Storm
источник
6

Конечно, это будет не так красиво, как использование языка со встроенной поддержкой. Я даже написал «объектно-ориентированный ассемблер».

Darron
источник
6

Небольшой код 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;
}
user922475
источник
5

Я копал это в течение одного года:

Поскольку систему 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, однако документация на китайском намного лучше).

OOC-GCC

dameng
источник
CLASS STATIC ASM NEW УДАЛИТЬ ST ... макросы в МНК-GCC
dameng
4

Какие статьи или книги хороши для использования концепций ООП в C?

Дейв Хэнсон C Интерфейсы и Реализация является отличными на инкапсуляцию и обозначении и очень хорошо на использовании указателей на функцию. Дейв не пытается имитировать наследование.

Норман Рэмси
источник
4

ООП - это всего лишь парадигма, которая ставит данные как более важные, чем код в программах. ООП это не язык. Итак, как простой C является простым языком, ООП в простом C также прост.

Anonyme
источник
3
Хорошо сказано, но это должен быть комментарий.
pqsk
4

Одна вещь, которую вы, возможно, захотите сделать, это изучить реализацию инструментария Xt для X Window . Конечно, он становится длиннее в зубе, но многие из используемых структур были разработаны для работы в ОО-стиле в рамках традиционного С. Как правило, это означает добавление дополнительного слоя косвенности тут и там и конструирование структур для наложения друг на друга.

Таким образом, вы действительно можете многое сделать, если OO находится в C, хотя иногда кажется, что концепции OO не возникли полностью из разума #include<favorite_OO_Guru.h>. Они действительно составляли многие из признанных лучших практик того времени. ОО языки и системы только дистиллированные и усиленные части программирования времени.

Peter Mortensen
источник
4

Ответ на вопрос «Да, вы можете».

Объектно-ориентированный C (OOC) комплект предназначен для тех, кто хочет программировать объектно-ориентированным образом, но также придерживается старого доброго C. OOC реализует классы, одиночное и множественное наследование, обработку исключений.

особенности

• Использует только макросы и функции Си, языковые расширения не требуются! (ANSI-C),

• Легко читаемый исходный код для вашего приложения. Забота была сделана, чтобы сделать вещи максимально простыми.

• Одиночное наследование классов

• Множественное наследование по интерфейсам и миксинам (начиная с версии 1.3)

• Реализация исключений (в чистом C!)

• Виртуальные функции для занятий

• Внешний инструмент для легкой реализации класса

Для получения более подробной информации посетите http://ooc-coding.sourceforge.net/ .

Sachin Mhetre
источник
4

Кажется, что люди пытаются эмулировать стиль 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.

user2074102
источник
Для наследования, как правило, базовый класс или структура экземпляра встроены в производный, не выделяются отдельно и передаются с использованием указателей. Таким образом, самая верхняя база всегда находится в начале любой из структур своих производных типов, поэтому их можно легко приводить друг к другу, чего нельзя сделать с указателями, которые могут быть с любым смещением.
underscore_d
2

См. 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;
}
оборота slkpg
источник
2

Я немного опаздываю на вечеринку, но я хочу поделиться своим опытом по этой теме: я работаю со встроенными компонентами в настоящее время, и единственный (надежный) компилятор, который у меня есть, это C, так что я хочу применить объектно-ориентированный подход во встроенных проектах, написанных на C.

Большинство решений, которые я видел до сих пор, интенсивно использует типы типов, поэтому мы теряем безопасность типов: компилятор не поможет вам, если вы допустите ошибку. Это совершенно недопустимо.

Требования, которые у меня есть:

  • Как можно больше избегайте типов, чтобы не потерять безопасность типов;
  • Полиморфизм: мы должны иметь возможность использовать виртуальные методы, а пользователь класса не должен знать, является ли какой-то конкретный метод виртуальным или нет;
  • Множественное наследование: я не часто его использую, но иногда я действительно хочу, чтобы какой-то класс реализовывал несколько интерфейсов (или расширял несколько суперклассов).

Я подробно объяснил свой подход в этой статье: объектно-ориентированное программирование на C ; Кроме того, есть утилита для автогенерации шаблонного кода для базовых и производных классов.

Дмитрий Франк
источник
2

Я построил небольшую библиотеку, где я попробовал это, и для меня это работает очень хорошо. Вот я и поделился опытом.

https://github.com/thomasfuhringer/oxygen

Одиночное наследование может быть реализовано довольно легко, используя структуру и расширяя ее для любого другого дочернего класса. Простое приведение к родительской структуре позволяет использовать родительские методы для всех потомков. Если вы знаете, что переменная указывает на структуру, содержащую объект такого типа, вы всегда можете привести ее к корневому классу и выполнить самоанализ.

Как уже упоминалось, виртуальные методы несколько сложнее. Но они выполнимы. Для простоты я просто использую массив функций в структуре описания класса, который каждый дочерний класс копирует и, при необходимости, заполняет отдельные слоты.

Многократное наследование будет довольно сложным для реализации и приведет к значительному снижению производительности. Так что я оставляю это. Я считаю желательным и полезным во многих случаях четко моделировать реальные жизненные обстоятельства, но, вероятно, в 90% случаев единственное наследование покрывает потребности. А одиночное наследование простое и ничего не стоит.

Также меня не волнует безопасность типов. Я думаю, что вы не должны зависеть от компилятора, чтобы предотвратить ошибки программирования. И это в любом случае ограждает вас от довольно небольшой части ошибок.

Как правило, в объектно-ориентированной среде вы также хотите реализовать подсчет ссылок для максимально возможной автоматизации управления памятью. Поэтому я также поместил счетчик ссылок в корневой класс «Объект» и некоторые функции для инкапсуляции выделения и освобождения памяти кучи.

Это все очень просто и скудно и дает мне основы ОО, не заставляя меня иметь дело с монстром, который является C ++. И я сохраняю гибкость пребывания на земле C, что, среди прочего, облегчает интеграцию сторонних библиотек.

Томас Ф.
источник
1

Я предлагаю использовать Objective-C, который является надмножеством C.

Хотя Objective-C 30 лет, он позволяет писать элегантный код.

http://en.wikipedia.org/wiki/Objective-C

SteAp
источник
В этом случае я бы порекомендовал С ++ вместо этого, поскольку он действительно объектно-ориентированный ...
yyny
Это не ответ. Но в любом случае, @YoYoYonnY: я не использую Objective-C и действительно использую C ++, но подобные комментарии бесполезны без основы, и вы их не предоставили. Почему вы утверждаете, что Objective-C не «на самом деле объектно-ориентированный ...»? И почему C ++ преуспевает там, где не удается Objective-C? Самое смешное, что Objective-C, в буквальном смысле, имеет слово Object в своем названии, тогда как C ++ позиционирует себя как многопарадигмальный язык, а не как ООП (т.е. не в основном ООП, а в некотором довольно экстремальном народном представлении, а не ООП). вообще) ... так вы уверены, что неправильно поняли эти имена?
underscore_d
0

Да, но я никогда не видел, чтобы кто-нибудь пытался реализовать какой-либо полиморфизм с C.

Пол Морель
источник
6
Вам нужно осмотреться побольше :) Например, Microsoft Direct X имеет полиморфный C-интерфейс.
AShelly
8
Посмотрите на реализацию ядра Linux, например. Это очень распространенная и широко используемая практика в C.
Илья
3
также glib полиморфен или может использоваться таким образом, чтобы разрешить полиморфизм (это как в C ++ вы должны явно сказать, какие вызовы являются виртуальными)
Spudd86
1
Полиморфизм не так уж редок в C, с другой стороны, множественное наследование.
Йохан Бьерехольт