Як можна написати об'єктно-орієнтований код на C? [зачинено]


500

Які існують способи написання об'єктно-орієнтованого коду на C? Особливо щодо поліморфізму.


Дивіться також це питання переповнення стека об'єктно-орієнтоване програмування в C .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Об'єктно- орієнтоване програмування в C </a> автор Лоран Деняу


25
@Camilo Мартін: Я навмисно запитав можу НЕ повинен . Мені фактично не цікаво використовувати OOP в C. Однак, бачачи рішення OO в C, ми / ми можемо дізнатися більше про межі та / або гнучкість C, а також про творчі способи реалізації та використання поліморфізму.
Діна

5
ОО - це лише зразок. Перевірте це, це можна зробити навіть у файлах .bat: dirk.rave.org/chap9.txt (будь-який шаблон можна застосувати до будь-якої мови програмування, якщо ви досить зацікавлені, я думаю). Хоча це гарна їжа для роздумів. І, напевно, можна багато чого навчитися застосовувати такі шаблони, які ми сприймаємо як належне для мов, які їх не мають.
Каміло Мартін

6
GTK - вибачте мене, GObject - насправді є досить хорошим прикладом OOP (сорта) у C. Отже, щоб відповісти @Camilo, на C-взаємоподатливість.
new123456

Відповіді:


362

Так. Насправді Аксель Шрайнер надає свою книгу "Об'єктно-орієнтоване програмування в ANSI-C" безкоштовно, яка досить ретельно висвітлює тему.


28
Хоча поняття в цій книзі є суцільними, ви втратите безпеку типу.
діапір

22
Перед тим, що ми знаємо як шаблони дизайну, чи була модель дизайну, відома як "орієнтація на об'єкт"; те саме зі збиранням сміття та ін. Вони настільки вроджені зараз, ми, як правило, забуваємо, коли вони були вперше розроблені, це було майже так само, як і з тим, що ми вважаємо сьогодні моделями дизайну
Dexygen

11
Ви можете отримати його безпосередньо з сайту автора: cs.rit.edu/~ats/books/ooc.pdf інші статті того ж автора: cs.rit.edu/~ats/books/index.html
pakman

10
Правильна колекція (книги + приклади вихідного коду) доступна з цього індексу rit.edu Об'єктно-орієнтоване програмування з ANSI-C
David C. Rankin

3
Чи перевірена ця книга? Є друкарська помилка в першому реченні першого абзацу першої сторінки.
Dagrooms

343

Оскільки ви говорите про поліморфізм, то так, ви можете, ми робили подібні речі багато років до появи С ++.

В основному ви використовуєте a, 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 - поведінка буде дещо відрізнятися від 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

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


52
Інкапсуляція досить проста, поліморфізм можливий - але успадкування складне
Мартін Бекетт

5
lwn.net нещодавно опублікував статтю під назвою " Об'єктно-орієнтовані шаблони дизайну" в ядрі на предмет стуктусів, подібних до вищезгаданої відповіді - тобто структури, що містить вказівники функції, або вказівник на структуру, яка має функції, які приймають покажчик на структура з даними, з якими ми працюємо як параметр.
радикалет

11
+1 Приємний приклад! Хоча якщо хтось дійсно хоче піти цією дорогою, було б більш доречно для "екземплярних" структур мати єдине поле, що вказує на їх екземпляр "віртуальної таблиці", що містить усі віртуальні функції для цього типу в одному місці. Тобто ваш tCommClassбуде перейменований в tCommVT, аtCommClass структура матиме лише поля даних і одне tCommVT vtполе, що вказує на "єдину" єдину віртуальну таблицю. Перенесення всіх покажчиків із кожним екземпляром додає зайвих накладних витрат і більше нагадує те, як ви робите речі в JavaScript, ніж C ++, IMHO.
Гроо

1
Отже, це демонструє реалізацію єдиного інтерфейсу, але що стосується реалізації декількох інтерфейсів? Або багаторазове успадкування?
weberc2

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

86

Простори імен часто роблять:

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);
Fabricio

@nategoose: АБО struct stack_type my_type; замістьstruct stack_type * my_type;
Fabricio

3
Мені подобається концепція наявності структури для класу. Але як щодо родової Classструктури? Це зробило б O C більш динамічним, ніж C ++. Як щодо цього? До речі, +1.
Linuxios

54

Я вважаю, що, крім того, що сама по собі є корисною, реалізація OOP в C - це відмінний спосіб навчитись ООП та зрозуміти його внутрішню роботу. Досвід багатьох програмістів показав, що для використання методики ефективно та впевнено, програміст повинен розуміти, як в кінцевому рахунку реалізуються основні концепції. Емуляція класів, успадкування та поліморфізм у С вчить саме цьому.

Щоб відповісти на початкове запитання, ось декілька ресурсів, які вчать робити OOP в 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 ++.


5
"Врешті-решт, ви закінчите переписувати C ++", я запитав, чи / боюся, що так буде.
Діна

39
Або, можливо, ви перезапишете ціль C, що було б набагато привабливішим результатом.
Контракт професора Фолкена порушив

3
Існує класний аромат OOP, наприклад, у Javascript , де гуру каже: "Нам не потрібні класи для створення багатьох подібних об'єктів". Але я побоююсь, що цього нелегко досягти за C. Не вдається ще сказати. (Чи є програма клонування () клонування структури?)
Люмі

1
Інші розумні хлопці, яким довелося реально реалізувати це та швидко зробити це впровадження (Google, V8 engine), зробили все, щоб додати (приховані) класи до JavaScript назад.
cubuspl42

Чи не glibнаписано на С об'єктивно?
kravemir

26

Звичайно, це можливо. Це те, що робить GObject , основу, на якій базуються всі GTK + та GNOME .


Які плюси / мінуси такого підходу? Тобто набагато простіше написати це за допомогою C ++.
kravemir

@kravemir Ну, C ++ не настільки портативний, як C, і зв’язати C ++ з кодом трохи складніше, який може бути скомпільований іншим компілятором C ++. Але так, простіше писати заняття на C ++, хоча GObject теж не такий складний (якщо припустити, що ви не заперечуєте проти невеликої тарілки котла).
Едвін Бак

17

Підбібліотека C stdio FILE є прекрасним прикладом того, як створити абстракцію, інкапсуляцію та модульність у непорушеному С.

Спадщина та поліморфізм - інші аспекти, які часто вважаються важливими для ООП, - не обов'язково забезпечують підвищення продуктивності, яку вони обіцяють, і були зроблені обґрунтовані аргументи , які можуть насправді перешкоджати розвитку та роздуму над проблемою.


Хіба stdio не абстрагується на шарі ядра? Якщо я не помиляюся, C-бібліотека розглядає їх як файли / пристрої символів, а драйвери ядра роблять роботу, ...
kravemir

15

Тривіальний приклад із твариною та собакою: ви відображаєте механізм V + (в основному все-таки). Ви також розділяєте розподіл та інстанції (Animal_Alloc, Animal_New), щоб ми не називали malloc () кілька разів. Ми також повинні явно пропустити thisвказівник навколо.

Якщо ви мали би виконувати невіртуальну функцію, це дрібниця. Ви просто не додаєте їх до vtable, а статичні функції не потребують thisвказівника. Для усунення неоднозначностей багатократне успадкування вимагає декількох таблиць.

Крім того, ви повинні мати можливість використовувати setjmp / longjmp для обробки виключень.

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PS. Це тестується на компіляторі C ++, але це може бути легко змусити його працювати на компіляторі C.


typedefУсередині structНЕ представляється можливим в С.
Масуда

13

Перевірте GObject . Це покликане бути OO в C і одна реалізація того, що ви шукаєте. Якщо ви дійсно хочете OO, перейдіть з C ++ або іншою мовою OOP. GObject може бути дуже важким для роботи часом, якщо ви звикли мати справу з мовами OO, але, як що завгодно, ви звикнете до конвенцій і потоків.


12

Це було цікаво читати. Я сам розмірковував над тим самим питанням, і переваги роздуму над цим:

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

  • Під час перегляду Інтернету для отримання інформації та думок з цього приводу я знайшов автора, який писав код для вбудованого процесора і мав доступний лише компілятор C: http://www.easures.com/discussion/other/4024626/Object-Oriented -С-Створення-Фундація-Класи-Частина-1

У його випадку аналіз та адаптація концепцій ООП у простому С було вагомим пошуком. Здається, він був готовий пожертвувати деякими концепціями ООП через накладний удар, спричинений результатом спроби їх впровадження у C.

Урок, який я взяв, - так, це можна зробити певною мірою, і так, є кілька вагомих причин для цього.

Врешті-решт, машина перекручує біти вказівника стека, завдяки чому лічильник програм стрибає навколо та обчислює операції доступу до пам'яті. З точки зору ефективності, чим менше цих розрахунків зроблено вашою програмою, тим краще ... але іноді нам доводиться сплачувати цей податок просто, щоб ми могли організувати нашу програму таким чином, щоб зробити її найменш схильною до людських помилок. Компілятор мови OOP прагне оптимізувати обидва аспекти. Програміст повинен бути набагато обережнішим, реалізовуючи ці концепції такою мовою, як C.


10

Вам може бути корисним переглянути документацію Apple щодо її набору API API фундаментального характеру. Це чистий API API, але багато типів пов'язані з еквівалентами об'єктів Objective-C.

Також вам може виявитися корисним подивитися на саму конструкцію Objective-C. Це трохи відрізняється від C ++ тим, що об'єктна система визначається в термінах функцій C, наприклад, objc_msg_sendдля виклику методу на об'єкті. Компілятор переводить синтаксис квадратної дужки в ці виклики функції, тому вам не доведеться цього знати, але, враховуючи своє запитання, вам може бути корисним дізнатися, як він працює під кришкою.


10

Є кілька прийомів, які можна використовувати. Найголовніше - це більше, як розділити проект. У нашому проекті ми використовуємо інтерфейс, який оголошується у файлі .h та реалізацію об’єкта у файлі .c. Важлива частина полягає в тому, що всі модулі, що включають .h файл, бачать лише об'єкт якvoid * , а .c файл - єдиний модуль, який знає внутрішню структуру.

Щось подібне для класу, який ми називаємо FOO як приклад:

У файлі .h

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

Файл реалізації C буде чимось подібним.

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

Тому я вказую вказівник явно на об'єкт на кожну функцію цього модуля. Компілятор C ++ робить це неявно, а в C - це явно виписуємо.

Я дуже використовую this в своїх програмах, щоб переконатися, що моя програма не компілюється в C ++, і вона має прекрасну властивість бути іншим кольором у моєму редакторі виділення синтаксису.

Поля FOO_struct можуть бути змінені в одному модулі, а інший модуль навіть не потрібно перекомпілювати, щоб використовувати його.

З цим стилем я вже вирішую значну частину переваг OOP (інкапсуляція даних). Використовуючи функціональні покажчики, навіть легко реалізувати щось на кшталт спадкування, але, чесно кажучи, це дійсно корисно лише рідко.


6
Якщо ви typedef struct FOO_type FOO_typeзамість typedef скасуєте недійсність у заголовку, ви отримаєте додаткову перевагу перевірки типу, при цьому все ще не наражаючи на вашу структуру.
Скотт Уельс

8

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

Однак, рідко є сенс накладати парадигму на мову, а не вибирати мову, яка використовує парадигму.


5
Саме перший компілятор C ++ зробив саме це - він перетворив код C ++ у еквівалентний (але некрасивий і не читабельний для людини) код C, який потім склав компілятор C.
Адам Розенфілд

2
EDG, Cfront та деякі інші досі здатні це робити. З дуже вагомою причиною: не кожна платформа має компілятор C ++.
Джаспер Беккерс

Я чомусь подумав, що C-front підтримує лише певні розширення C ++ (наприклад, посилання), але не повну емуляцію OOP / динамічної диспетчеризації.
Урі

2
Ви також можете зробити те ж саме з LLVM та сервісом C.
Зіфре

7

Об'єктно-орієнтований C, можна зробити, я бачив цей тип коду у виробництві в Кореї, і це було найжахливіше монстр, який я бачив за роки (це було як у минулому році (2007), коли я бачив код). Так так, це можна зробити, і так люди робили це раніше, і досі це роблять навіть у цей день та вік. Але я б рекомендував C ++ або Objective-C, обидві - це мови, які народилися з C, з метою надання орієнтації на об'єкт з різними парадигмами.


3
якщо Лінус побачить ваш коментар. Він напевно або посміє або проклинає тебе
Андерс Лінд

7

Якщо ви переконані, що підхід OOP є кращим за проблему, яку ви намагаєтеся вирішити, чому б ви намагалися вирішити її мовою, яка не є OOP? Схоже, ви використовуєте неправильний інструмент для роботи. Використовуйте C ++ або якусь іншу об'єктно-орієнтовану мову C-варіанту.

Якщо ви запитуєте, оскільки ви починаєте кодувати вже існуючий великий проект, написаний на C, тоді вам не слід намагатися примушувати власні (чи чужі) парадигми OOP до інфраструктури проекту. Дотримуйтесь вказівок, які вже є у проекті. В цілому, чисті інтерфейси і ізольовані бібліотеки і модулі будуть йти довгий шлях до маючи чисту OOP- іш дизайн.

Якщо після всього цього ви дійсно налаштовані на виконання OOP C, прочитайте це (PDF).


36
Не дуже відповідаючи на питання ...
Брайан Постув,

2
@Brian, схоже, посилання на PDF відповідатиме на питання безпосередньо, хоча я не встиг перевірити себе.
Марк Рансом

5
Здається, посилання на PDF є цілим підручником з цієї теми ... Прекрасний доказ, але він не вкладається у поле ...
Брайан Постув,

5
так, дайте відповідь на запитання. цілком справедливо запитати, як користуватися мовою певним чином. не було запиту на думку щодо інших мов ....
Тім Рінг,

9
@Brian & Tim Ring: у запитанні було запропоновано рекомендації щодо книги щодо теми; Я дав йому посилання на книгу, яка конкретно стосується цієї теми. Я також висловив свою думку щодо того, чому підхід до проблеми може бути не оптимальним (що, на мою думку, багато людей тут, мабуть, погоджуються на основі голосів та інших коментарів / відповідей). Чи є якісь пропозиції щодо покращення моєї відповіді?
RarrRarrRarr

6

Так, ти можеш. Люди писали об'єктно-орієнтований C перед C ++ або Objective-C на місце події вийшов . І 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'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;
}

6

Звичайно, це просто не буде так красиво, як використання мови із вбудованою підтримкою. Я навіть написав «об’єктно-орієнтований асемблер».


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

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


CLASS STATIC ASM NEW ВИДАЛИТИ ST ... макроси в МНК-GCC
dameng


4

У яких статтях чи книгах добре використовувати поняття OOP в C?

Інтерфейси та реалізація Дейва Хансона - це чудовий спосіб інкапсуляції та іменування та дуже хороший у використанні функціональних покажчиків. Дейв не намагається імітувати спадщину.


4

OOP - лише парадигма, яка розміщує дані як важливіші за код у програмах. OOP - це не мова. Отже, як звичайна C - це проста мова, OOP у звичайній C також є простим.


3
Добре сказано, але це слід коментувати.
pqsk

4

Одне, що ви можете зробити, - це вивчити реалізацію інструментарію Xt для X Window . Звичайно, що зуб стає довгим, але багато використовуваних конструкцій були розроблені для роботи в рамках OO в рамках традиційного С. Взагалі це означає додавання зайвого шару опосередкування тут і там і проектування конструкцій, що лежать одна над одною.

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


4

Відповідь на питання: "Так, можна".

Об'єктно-орієнтований набір C (OOC) призначений для тих, хто хоче програмувати об'єктно-орієнтованим способом, але дотримується і старого доброго С. OOC реалізує класи, одинарне та багаторазове успадкування, обробку винятків.

Особливості

• Використовує лише C макроси та функції, не потрібні розширення мови! (ANSI-C)

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

• Однократне успадкування класів

• Багаторазове успадкування за допомогою інтерфейсів та комбінацій (починаючи з версії 1.3)

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

• Віртуальні функції для занять

• Зовнішній інструмент для легкої реалізації класу

Для отримання більш детальної інформації відвідайте http://ooc-coding.sourceforge.net/ .


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.


Для успадкування зазвичай базовий клас або структура екземпляра вбудовується у похідне, не виділяється окремо і посилається за допомогою покажчиків. Таким чином, найвища база завжди знаходиться на початку будь-якої структури її похідних типів, тому їх можна легко передавати один одному, чого не можна робити з покажчиками, які можуть бути при будь-якому зміщенні.
підкреслюй_

2

Дивіться http://slkpg.byethost7.com/instan.html для ще одного повороту на OOP в 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;
}

2

Я трохи запізнююся на вечірку, але хочу поділитися своїм досвідом з теми: Я працюю з вбудованими речами в наші дні, і єдиний (надійний) компілятор у мене є C, так що я хочу застосувати об'єктно-орієнтований підхід до моїх вбудованих проектів, написаних на С.

Більшість рішень, які я бачив до цього часу, сильно використовують typecasts, тому ми втрачаємо безпеку типу: компілятор не допоможе вам, якщо ви помилитесь. Це абсолютно неприпустимо.

Вимоги, які у мене є:

  • Уникайте якомога більше типів, щоб ми не втрачали безпеку типу;
  • Поліморфізм: ми повинні мати можливість використовувати віртуальні методи, а користувач класу не повинен усвідомлювати, чи є якийсь конкретний метод віртуальним чи ні;
  • Багатократне успадкування: я не використовую його часто, але іноді мені дуже хочеться, щоб якийсь клас реалізував декілька інтерфейсів (або розширити декілька суперклассів).

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


2

Я побудував маленьку бібліотеку, де я спробував це, і для мене це працює чудово. Тому я подумав, що ділюсь досвідом.

https://github.com/thomasfuhringer/oxygen

Одинакове успадкування може бути реалізовано досить легко, використовуючи структуру та розширюючи її для кожного іншого дочірнього класу. Простий податок на батьківську структуру дає можливість використовувати батьківські методи для всіх нащадків. Поки ви знаєте, що змінна вказує на структуру, що містить такий тип об'єкта, ви завжди можете передавати кореневий клас і робити інтроспекцію.

Як вже було сказано, віртуальні методи дещо складніші. Але вони виконані. Щоб зробити все просто, я просто використовую масив функцій в структурі опису класу, який кожен дочірній клас копіює та перенаселяє окремі слоти, де це потрібно.

Багаторазове успадкування було б досить складним у здійсненні та має значний вплив на ефективність. Тому я залишаю це. Я вважаю бажаним і корисним у досить багатьох випадках чітко моделювати реальні життєві обставини, але, мабуть, у 90% випадків одне успадкування покриває потреби. А поодиноке успадкування просте і нічого не коштує.

Також я не дбаю про безпеку типу. Я думаю, вам не слід залежати від компілятора, щоб не допустити помилок програмування. І це все одно захищає вас від досить невеликої частини помилок.

Як правило, в об'єктно-орієнтованому середовищі ви також хочете реалізувати підрахунок посилань для автоматизації управління пам'яттю настільки, наскільки це можливо. Тож я також поклав підрахунок посилань до кореневого класу «Об’єкт» та деяку функціональність для інкапсуляції розподілу та розподілу пам’яті купи.

Це все дуже просто і худорляво і дає мені основи OO, не змушуючи мене мати справу з монстром, що є C ++. І я зберігаю гнучкість перебування на землі С, що, серед іншого, полегшує інтеграцію сторонніх бібліотек.


1

Я пропоную використовувати Objective-C, який є надмножиною C.

Хоча Objective-C 30 років, він дозволяє писати елегантний код.

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


У такому випадку я б рекомендував замість цього C ++, оскільки він фактично орієнтований на об’єкт ...
yyny

Це не відповідь. Але все одно, @YoYoYonnY: Я не використовую Objective-C і використовую C ++, але такі коментарі не мають жодної користі без підстав, і ви не надали жодного. Чому ви стверджуєте, що Objective-C не відповідає "фактично орієнтованому на об'єкт ..."? І чому C ++ досягає успіху, коли виходить з ладу Objective-C? Найсмішніше те, що в Objective-C, ну, буквально є назва « Об’єкт» у своїй назві, тоді як C ++ продає себе як багатопарадигмічну мову, а не ООП (тобто, перш за все, не OOP, і, в деякому досить екстремальному погляді людей, не OOP взагалі) ... так ви впевнені, що не зрозуміли тих імен неправильно?
підкреслюйте_12

0

Так, але я ніколи не бачив, щоб хтось намагався здійснити якийсь поліморфізм із С.


6
Вам потрібно оглянути більше :) Наприклад, Microsoft Direct Direct X має поліморфний інтерфейс C.
AShelly

8
Наприклад, подивіться на реалізацію ядра Linux. Це дуже поширена і широко застосовувана практика в C.
Ілля

3
також glib є поліморфним, або його можна використовувати таким чином, що дозволяє поліморфізм (це як C ++, ви повинні чітко сказати, які дзвінки є віртуальними)
Spudd86,

1
Поліморфізм не такий рідкісний для С, з іншого боку, множинне успадкування.
Йохан Б'єрехольт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.