Можете ли вы написать объектно-ориентированный код на C?

404

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


См. также Вопрос Объектная ориентация в C.

  • 1
    <a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Объектно- ориентированное программирование на C </a>, Лоран Денио
  • 0
    Возможно, вы захотите взглянуть на ответы на этот вопрос: какие методы / стратегии люди используют для создания объектов в C (не в C ++)?
Показать ещё 5 комментариев
Теги:
object
oop

32 ответа

317
Лучший ответ

Да. Фактически Axel Schreiner бесплатно предоставляет

  • 5
    спасибо за ссылку ... похоже, какое-то упражнение для клеток мозга не за горами.
  • 28
    В то время как понятия в этой книге являются твердыми, вы потеряете безопасность типов.
Показать ещё 5 комментариев
290

Поскольку вы говорите о полиморфизме, тогда да, вы можете, мы делали такие вещи за несколько лет до появления С++.

В основном вы используете 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

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

  • 49
    Инкапсуляция довольно проста, полиморфизм выполним - но наследование сложно
  • 12
    +1 за "... немного другое ... (дамп ядра во время выполнения, а не ошибка во время компиляции)"
Показать ещё 6 комментариев
76

Пространства имен часто выполняются с помощью:

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 похож на статическую переменную класса - распределяется между всеми экземплярами типа. Все методы действительно статичны, за исключением того, что некоторые принимают этот *

  • 1
    @nategoose - st->my_type->push(st, thing2); вместо st->my_type.push(st, thing2);
  • 0
    @nategoose: ИЛИ struct stack_type my_type; вместо struct stack_type * my_type;
Показать ещё 1 комментарий
46

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

31

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

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

  • 5
    «В конце концов, вы в конечном итоге переписываете C ++», - подумал я, боится ли это.
  • 39
    Или вы можете переписать Objective C, что будет гораздо более привлекательным результатом.
Показать ещё 3 комментария
20

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

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

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

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

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

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

12

Это было интересно прочитать. Я сам обдумывал один и тот же вопрос, и о преимуществах думать об этом можно:

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

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

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

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

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

10

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

  • 6
    Если вы используете typedef struct FOO_type FOO_type вместо typedef для аннулирования в заголовке, вы получаете дополнительное преимущество проверки типов, но при этом не раскрываете свою структуру.
  • 0
    Спасибо, хороший совет, это действительно было моей проблемой.
10

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

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

10

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

7

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

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

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

  • 35
    Не совсем отвечая на вопрос ...
  • 2
    @ Брайан, ссылка на PDF, похоже, ответит на вопрос напрямую, хотя у меня не было времени проверить это самостоятельно.
Показать ещё 5 комментариев
7

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

  • 2
    если Линус увидит твой комментарий. Он определенно либо посмеется, либо проклянет вас
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;
}
6

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

6

Да, вы можете. Люди писали объектно-ориентированную 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;
}
5

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

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

  • 5
    Самый первый компилятор C ++ сделал именно это - он преобразовал код C ++ в эквивалентный (но некрасивый и нечитаемый человеком) код C, который затем был скомпилирован компилятором C.
  • 2
    EDG, Cfront и некоторые другие по-прежнему способны на это. По очень веской причине: не на каждой платформе есть компилятор C ++.
Показать ещё 2 комментария
4

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

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

Функции

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

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

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

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

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

• Виртуальные функции для классов

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

Подробнее см. http://ooc-coding.sourceforge.net/.

4

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

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

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

Поскольку система 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

  • 0
    CLASS STATIC ASM NEW DELETE ST ... являются макросами в OOC-GCC
4

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

Dave Hanson C Интерфейсы и реализации превосходны при инкапсуляции, именовании и очень хорошем использовании указателей на функции. Дейв не пытается имитировать наследование.

4

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

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

4

Существует пример наследования с использованием слова C в книге Джима Ларсона 1996 года, приведенного в разделе Раздел 312 Семинар по программированию на обед: Высокий и низкий уровень C.

3

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

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

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

https://github.com/thomasfuhringer/oxygen

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

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

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

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

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

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

2

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

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

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

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

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

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;
}
1

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

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

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

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

Да, это возможно.

Это чистая 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
*/
  • 0
    Вот пастин, где можно увидеть больше этого кода: pastebin.com/bLxTP8tA
0

Я думаю, что первое, что можно сказать, это то, что (по крайней мере, IMHO) C реализация указателей функций ДЕЙСТВИТЕЛЬНО трудна в использовании. Я бы прыгнул через целую кучу обручей, чтобы избежать указателей функций...

который сказал, я думаю, что другие люди сказали, что это очень хорошо. у вас есть структуры, у вас есть модули, а не foo->method(a,b,c), вы получаете method(foo,a,b,c) Если у вас есть несколько типов с методом "метод", вы можете префикс его с типом, поэтому FOO_method(foo,a,b,c), as другие сказали... с хорошим использованием файлов .h вы можете получить частные и публичные и т.д.

Теперь есть несколько вещей, которые эта техника НЕ ​​даст вам. Это не даст вам частных полей данных. что, я думаю, вы имеете дело с силой воли и хорошей гигиеной кодирования... Кроме того, нет простого способа наследования с этим.

Это самые легкие части, по крайней мере... остальное, я думаю, это ситуация 90/10. 10% пользы потребует 90% работы...

  • 1
    Одиночное наследование (без полиморфизма) также может быть довольно легко реализовано с помощью этой техники. Все, что вам нужно, это встроить суперкласс в качестве первого члена подкласса. В соответствии со стандартом C вся структура должна быть обязательно выровнена с первым членом, поэтому любой метод, разработанный для foo (метод (foo, a, b, c)), будет работать, когда вместо этого передается указатель на строку (bar является подклассом Foo). Это наследство.
  • 0
    @miro. Вот это да. это ... это серьезный клудж прямо здесь ...
Показать ещё 3 комментария
0

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

  • 4
    Вам нужно осмотреться побольше :) Например, Microsoft Direct X имеет полиморфный C-интерфейс.
  • 6
    Посмотрите на реализацию ядра Linux, например. Это очень распространенная и широко используемая практика в C.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню