Jak napisać kod obiektowy w C? [Zamknięte]


500

Jakie są sposoby pisania kodu obiektowego w C? Zwłaszcza w odniesieniu do polimorfizmu.


Patrz także przepełnieniem stosu tego zapytania obiektowego w C orientację .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Programowanie obiektowe w C </a> autor: Laurent Deniau


25
@Camilo Martin: I celowo zadawane może nie powinien . Właściwie nie jestem zainteresowany używaniem OOP w C. Jednak widząc rozwiązania OO w C, ja / my możemy dowiedzieć się więcej o limitach i / lub elastyczności C, a także o kreatywnych sposobach wdrażania i stosowania polimorfizmu.
Dinah

5
OO to tylko wzór. Sprawdź tutaj, można to zrobić nawet w plikach .bat: dirk.rave.org/chap9.txt ( myślę, że można zastosować dowolny wzór w dowolnym języku programowania, jeśli jest wystarczająco zainteresowany). Jest to jednak dobre jedzenie do namysłu. I prawdopodobnie wiele można się nauczyć z zastosowania takich wzorców, które uznajemy za oczywiste w językach, które ich nie mają.
Camilo Martin

6
GTK - 'scuse me, GObject - jest w rzeczywistości całkiem dobrym przykładem OOP (sorta) w C. Tak więc, aby odpowiedzieć @Camilo, dla interpolowalności w C.
new123456,

Odpowiedzi:


362

Tak. W rzeczywistości Axel Schreiner udostępnia swoją książkę „Programowanie obiektowe w ANSI-C” za darmo, która dość dokładnie omawia ten temat.


28
Podczas gdy koncepcje w tej książce są stałe, stracisz bezpieczeństwo typu.
diapir

22
Przed czymś, co znamy jako wzorce projektowe, był on znany jako „orientacja obiektowa”; to samo z odśmiecaniem i inne. Są teraz tak zakorzenione, że często zapominamy, że kiedy zostały opracowane po raz pierwszy, było tak samo, jak w przypadku dzisiejszych wzorców projektowych
Dexygen

11
Możesz go pobrać bezpośrednio ze strony autora: cs.rit.edu/~ats/books/ooc.pdf inne artykuły tego samego autora: cs.rit.edu/~ats/books/index.html
pakman

10
Właściwa kolekcja (przykłady + książka + kod źródłowy) jest dostępna z tego indeksu rit.edu Programowanie obiektowe z ANSI-C
David C. Rankin

3
Czy ta książka jest recenzowana? W pierwszym zdaniu pierwszego akapitu pierwszej strony jest literówka.
Dagrooms

343

Skoro mówisz o polimorfizmie, to tak, możesz, robiliśmy takie rzeczy wiele lat przed powstaniem C ++.

Zasadniczo używasz struct do przechowywania zarówno danych, jak i listy wskaźników funkcji, aby wskazać odpowiednie funkcje dla tych danych.

Tak więc w klasie komunikacyjnej miałbyś otwarte, do odczytu, zapisu i zamykania wywołanie, które byłyby utrzymywane jako cztery wskaźniki funkcji w strukturze, obok danych obiektu, coś w stylu:

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;

Oczywiście powyższe segmenty kodu byłyby w rzeczywistości „konstruktorem”, takim jak rs232Init().

Kiedy „dziedziczysz” po tej klasie, po prostu zmieniasz wskaźniki, aby wskazywały własne funkcje. Każdy, kto wywołał te funkcje, zrobiłby to poprzez wskaźniki funkcji, dając ci polimorfizm:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Coś jak ręczny vtable.

Możesz nawet mieć klasy wirtualne, ustawiając wskaźniki na NULL - zachowanie będzie nieco inne niż C ++ (zrzut rdzenia w czasie wykonywania, a nie błąd w czasie kompilacji).

Oto przykładowy kod, który to pokazuje. Najpierw struktura klasy najwyższego poziomu:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

Następnie mamy funkcje dla „podklasy” 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;
}

I również 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;
}

I na koniec program testowy pokazujący go w akcji:

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

To daje wynik:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

dzięki czemu można zobaczyć, że wywoływane są różne funkcje, w zależności od podklasy.


52
Hermetyzacja jest dość łatwa, polimorfizm jest wykonalny - ale dziedziczenie jest trudne
Martin Beckett

5
lwn.net opublikował ostatnio artykuł zatytułowany Object Objected Designss w jądrze na temat kanałów podobnych do powyższej odpowiedzi - to znaczy struktury zawierającej wskaźniki funkcji lub wskaźnika do struktury, która ma funkcje, które przenoszą wskaźnik do struct z danymi, z którymi pracujemy jako parametr.
radykalny

11
+1 Dobry przykład! Chociaż jeśli ktoś naprawdę chce pójść tą drogą, bardziej odpowiednie byłoby, aby struktury „instancji” miały jedno pole wskazujące na instancję „tabeli wirtualnej”, zawierającą wszystkie funkcje wirtualne dla tego typu w jednym miejscu. Tj. tCommClassZmieniono by nazwę na tCommVT, a tCommClassstruct miałby tylko pola danych i jedno tCommVT vtpole wskazujące na „jedną i jedyną” wirtualną tabelę. Przenoszenie wszystkich wskaźników z każdą instancją powoduje niepotrzebne obciążenie i przypomina bardziej sposób wykonywania czynności w JavaScript niż C ++, IMHO.
Groo

1
To pokazuje implementację jednego interfejsu, ale co z implementacją wielu interfejsów? Czy wielokrotne dziedziczenie?
weberc2

Weber, jeśli chcesz mieć wszystkie funkcje C ++, prawdopodobnie powinieneś używać C ++. Pytanie zadane konkretnie na temat polimorfizmu, zdolności obiektów do przyjmowania innej „formy”. Z pewnością możesz robić interfejsy i wielokrotne dziedziczenie w C, ale jest to trochę dodatkowej pracy i musisz sam zarządzać inteligentnymi systemami zamiast używać wbudowanych C ++.
paxdiablo

86

Przestrzenie nazw są często wykonywane przez:

stack_push(thing *)

zamiast

stack::push(thing *)

Aby zmienić strukturę C w coś w rodzaju klasy C ++ , możesz zmienić:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

W

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

I robić:

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
}

Nie zrobiłem destruktora ani nie usunąłem, ale ma ten sam wzór.

this_is_here_as_an_example_only jest jak zmienna klasy statycznej - wspólna dla wszystkich instancji typu. Wszystkie metody są naprawdę statyczne, z wyjątkiem niektórych, że biorą to *


1
@nategoose - st->my_type->push(st, thing2);zamiastst->my_type.push(st, thing2);
Fabricio

@nategoose: OR struct stack_type my_type; zamiaststruct stack_type * my_type;
Fabricio

3
Podoba mi się koncepcja posiadania struktury dla klasy. Ale co powiesz na ogólną Classstrukturę? To sprawiłoby, że OO C byłby bardziej dynamiczny niż C ++. Co ty na to? Nawiasem mówiąc, +1.
Linuxios,

54

Uważam, że oprócz tego, że jest użyteczne samo w sobie, wdrożenie OOP w C jest doskonałym sposobem na naukę OOP i zrozumienie jego wewnętrznego działania. Doświadczenie wielu programistów wykazało, że aby zastosować technikę skutecznie i pewnie, programista musi zrozumieć, w jaki sposób ostatecznie wdrażane są podstawowe koncepcje. Uczą tego właśnie emulowanie klas, dziedziczenie i polimorfizm w C.

Aby odpowiedzieć na pierwotne pytanie, oto kilka zasobów, które uczą, jak wykonywać OOP w C:

Wpis na blogu EmbeddedGurus.com „Programowanie obiektowe w C” pokazuje, jak zaimplementować klasy i pojedyncze dziedziczenie w przenośnym C: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

Nota aplikacyjna „„ C + ”- programowanie obiektowe w C” pokazuje, jak zaimplementować klasy, pojedyncze dziedziczenie i późne wiązanie (polimorfizm) w C przy użyciu makr preprocesora: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , przykładowy kod jest dostępny na stronie http://www.state-machine.com/resources/cplus_3.0.zip


4
Nowy adres URL podręcznika C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

Widziałem to zrobione. Nie poleciłbym tego. C ++ początkowo zaczął w ten sposób jako preprocesor, który wytworzył kod C jako etap pośredni.

Zasadniczo to, co ostatecznie robisz, to utworzenie tabeli wysyłania dla wszystkich metod, w których przechowujesz odwołania do funkcji. Wyprowadzenie klasy wymagałoby skopiowania tej tabeli wysyłania i zastąpienia pozycji, które chcesz zastąpić, a nowe „metody” musiałyby wywoływać metodę oryginalną, jeśli chce wywołać metodę podstawową. Ostatecznie przepisujesz C ++.


5
„W końcu przepisujesz C ++”. Zastanawiałem się, czy tak się stanie.
Dinah

39
Możesz też przepisać Cel C, co byłoby znacznie bardziej atrakcyjnym rezultatem.
Umowa prof. Falkena została naruszona

3
Jest taki bezklasowy smak OOP, na przykład w JavaScript , gdzie guru mówi: „Nie potrzebujemy klas, aby tworzyć wiele podobnych obiektów”. Obawiam się jednak, że nie jest to łatwe do osiągnięcia w C. Nie (jeszcze) w stanie powiedzieć. (Czy istnieje procedura clone () do klonowania struktury?)
Lumi

1
Kolejni sprytni faceci, którzy musieli to zaimplementować i sprawić, by ta implementacja była szybka (Google, silnik V8), zrobili wszystko, aby dodać (ukryte) klasy do JavaScript z powrotem.
cubuspl42

Czy nie jest glibnapisane w C w sposób obiektywny?
kravemir

26

Jasne, że to możliwe. To właśnie robi GObject , struktura, na której opiera się cała GTK + i GNOME .


Jakie są zalety / wady takiego podejścia? To znaczy. dużo łatwiej jest napisać go za pomocą C ++.
kravemir

@kravemir C ++ nie jest tak przenośny jak C, a nieco trudniej jest połączyć C ++ z kodem, który może być skompilowany przez inny kompilator C ++. Ale tak, łatwiej jest pisać klasy w C ++, chociaż GObject nie jest tak naprawdę trudny (zakładając, że nie przeszkadza ci mała płyta kotła).
Edwin Buck

17

Podbiblioteka C stdio FILE jest doskonałym przykładem tworzenia abstrakcji, enkapsulacji i modułowości w nieskażonym C.

Dziedziczenie i polimorfizm - inne aspekty często uważane za niezbędne do OOP - nie koniecznie zapewnić wzrost wydajności obiecują i rozsądne argumenty nie zostały wykonane , że może faktycznie utrudniać rozwoju i myślenia o domenie problemu.


Czy Stdio nie jest abstrakcyjne na warstwie jądra? Jeśli się nie mylę, biblioteka C traktuje je jako pliki znaków / urządzenia, a sterowniki jądra wykonują zadanie, ...
kravemir

15

Trywialny przykład ze zwierzęciem i psem: odzwierciedlasz mechanizm vtable C ++ (w dużej mierze i tak). Oddzielasz także alokację i tworzenie instancji (Animal_Alloc, Animal_New), abyśmy nie wywoływali malloc () wiele razy. Musimy także wyraźnie przekazać thiswskaźnik.

Jeśli miałbyś wykonywać funkcje inne niż wirtualne, to banalne. Po prostu nie dodajesz ich do funkcji vtable, a funkcje statyczne nie wymagają thiswskaźnika. Wielokrotne dziedziczenie zwykle wymaga wielu tabel vt, aby rozwiązać niejednoznaczności.

Ponadto powinieneś być w stanie używać setjmp / longjmp do obsługi wyjątków.

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. Jest to testowane na kompilatorze C ++, ale powinno być łatwe, aby działało na kompilatorze C.


typedefw środku structnie jest możliwe w C.
masoud

13

Sprawdź GObject . To ma być OO w C i jedna implementacja tego, czego szukasz. Jeśli jednak naprawdę chcesz OO, wybierz C ++ lub inny język OOP. GObject może być naprawdę trudny do pracy, jeśli jesteś przyzwyczajony do pracy z językami OO, ale jak wszystko, przyzwyczaisz się do konwencji i przepływu.


12

To było interesujące do przeczytania. Zastanawiam się nad tym samym pytaniem, a korzyści z myślenia o tym są następujące:

  • Próba wyobrażenia sobie, jak wdrożyć koncepcje OOP w języku innym niż OOP, pomaga mi zrozumieć mocne strony języka OOp (w moim przypadku C ++). Pomaga mi to lepiej ocenić, czy używać C czy C ++ dla danego typu aplikacji - gdy korzyści jednej z nich przewyższają drugą.

  • Podczas przeglądania stron internetowych w poszukiwaniu informacji i opinii na ten temat znalazłem autora, który pisał kod dla wbudowanego procesora i miał dostępny tylko kompilator C: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Creating-Foundation-Classes-Part-1

W jego przypadku analiza i adaptacja koncepcji OOP w zwykłym C była ważnym celem. Wygląda na to, że był otwarty na poświęcenie niektórych koncepcji OOP ze względu na ogólne obciążenie wydajności wynikające z próby ich wdrożenia w C.

Lekcja, którą podjąłem, jest taka, że ​​można to zrobić do pewnego stopnia i tak, istnieje kilka dobrych powodów, aby ją podjąć.

Na koniec maszyna kręci bitami wskaźnika stosu, co powoduje, że licznik programu przeskakuje i oblicza operacje dostępu do pamięci. Z punktu widzenia wydajności, im mniej obliczeń wykonanych przez twój program, tym lepiej ... ale czasami musimy płacić ten podatek po prostu, abyśmy mogli zorganizować nasz program w taki sposób, aby był najmniej podatny na błędy ludzkie. Kompilator języka OOP stara się zoptymalizować oba aspekty. Programista musi być znacznie bardziej ostrożny przy wdrażaniu tych pojęć w języku takim jak C.


10

Pomocne może okazać się przejrzenie dokumentacji Apple dotyczącej zestawu interfejsów API Core Foundation. Jest to interfejs API w czystym języku C, ale wiele typów jest zmostkowanych z odpowiednikami obiektów Objective-C.

Pomocne może być również przyjrzenie się projektowi samego Celu-C. Różni się nieco od C ++ tym, że system obiektowy jest zdefiniowany pod względem funkcji C, np. objc_msg_sendDo wywołania metody na obiekcie. Kompilator tłumaczy składnię nawiasów kwadratowych na te wywołania funkcji, więc nie musisz tego znać, ale biorąc pod uwagę twoje pytanie, może okazać się przydatne, aby dowiedzieć się, jak działa pod maską.


10

Istnieje kilka technik, które można zastosować. Najważniejsze jest to, jak podzielić projekt. W naszym projekcie używamy interfejsu zadeklarowanego w pliku .h oraz implementacji obiektu w pliku .c. Ważną częścią jest to, że wszystkie moduły zawierające plik .h widzą tylko obiekt jakovoid * , a plik .c jest jedynym modułem, który zna wewnętrzne struktury.

Coś takiego w przypadku klasy nazywamy FOO jako przykład:

W pliku .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

Plik implementacyjny w C będzie taki.

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

Daję więc wskaźnik wprost do obiektu każdej funkcji tego modułu. Kompilator C ++ robi to domyślnie, aw C wypisujemy go jawnie.

Naprawdę używam this w moich programach, aby upewnić się, że mój program nie kompiluje się w C ++ i że ma dobrą właściwość bycia w innym kolorze w moim edytorze podświetlania składni.

Pola FOO_struct można modyfikować w jednym module, a drugiego modułu nawet nie trzeba rekompilować, aby nadal można go było używać.

W tym stylu już korzystam z dużej części zalet OOP (enkapsulacji danych). Używając wskaźników funkcji, można nawet łatwo wdrożyć coś takiego jak dziedziczenie, ale szczerze mówiąc, to naprawdę bardzo rzadko przydatne.


6
Jeśli zrobisz to typedef struct FOO_type FOO_typezamiast typedef, aby unieważnić nagłówek, zyskasz dodatkową zaletę sprawdzania typu, wciąż nie ujawniając swojej struktury.
Scott Wales

8

Można go sfałszować za pomocą wskaźników funkcji, i faktycznie myślę, że teoretycznie możliwe jest skompilowanie programów C ++ w C.

Jednak rzadko ma sens narzucanie paradygmatu na język, a nie wybór języka, który używa paradygmatu.


5
Dokładnie to zrobił pierwszy kompilator C ++ - przekształcił kod C ++ w równoważny (ale brzydki i nieczytelny dla człowieka) kod C, który został następnie skompilowany przez kompilator C.
Adam Rosenfield,

2
EDG, Cfront i niektórzy inni są w stanie to zrobić. Z bardzo dobrego powodu: nie każda platforma ma kompilator C ++.
Jasper Bekkers,

Z jakiegoś powodu myślałem, że C-front obsługuje tylko niektóre rozszerzenia C ++ (np. Referencje), ale nie pełną emulację OOP / dynamicznej wysyłki.
Uri

2
Możesz również zrobić to samo z LLVM i backendem C.
Zifre

7

Zorientowane obiektowo C, można to zrobić, widziałem tego typu kod w produkcji w Korei i był to najstraszniejszy potwór, jaki widziałem od lat (tak jak w zeszłym roku (2007), widziałem kod). Tak, można to zrobić i tak, ludzie robili to wcześniej i nadal robią to nawet w dzisiejszych czasach. Ale polecam C ++ lub Objective-C, oba są językami pochodzącymi z C, w celu zapewnienia orientacji obiektowej z różnymi paradygmatami.


3
jeśli Linus zobaczy twój komentarz. Na pewno będzie się z ciebie śmiał lub przeklinał
Anders Lind

7

Jeśli jesteś przekonany, że podejście OOP jest lepsze od problemu, który próbujesz rozwiązać, dlaczego miałbyś próbować go rozwiązać za pomocą języka innego niż OOP? Wygląda na to, że używasz niewłaściwego narzędzia do pracy. Użyj C ++ lub innego zorientowanego obiektowo języka C.

Jeśli pytasz, ponieważ zaczynasz pisać na już istniejącym dużym projekcie napisanym w C, nie powinieneś próbować narzucać własnych (lub kogokolwiek innego) paradygmatów OOP do infrastruktury projektu. Postępuj zgodnie z wytycznymi, które są już obecne w projekcie. Na ogół czyste API i izolowane biblioteki i moduły będzie przejść długą drogę w kierunku posiadania czystego OOP- owski projekt.

Jeśli po tym wszystkim naprawdę zaczynasz robić OOP C, przeczytaj to (PDF).


36
Naprawdę nie odpowiadam na pytanie ...
Brian Postow

2
@Brian, link do pliku PDF wydaje się odpowiadać bezpośrednio na pytanie, chociaż nie miałem czasu na sprawdzenie.
Mark Ransom

5
Link do pliku PDF wydaje się być całym podręcznikiem na ten temat ... Piękny dowód, ale nie pasuje do marginesu ...
Brian Postow

5
tak, odpowiedz na pytanie. całkowicie poprawne jest pytanie, jak używać języka w określony sposób. nie było prośby o opinie w innych językach ...
Tim Ring

9
@Brian & Tim Ring: Pytanie, które zawiera rekomendacje książek na dany temat; Dałem mu link do książki, która konkretnie dotyczy tego tematu. Wyraziłem również opinię, dlaczego podejście do problemu może nie być optymalne (z czym wydaje mi się, że wiele osób tutaj się zgadza na podstawie głosów i innych komentarzy / odpowiedzi). Czy masz jakieś sugestie dotyczące ulepszenia mojej odpowiedzi?
RarrRarrRarr

6

Tak, możesz. Ludzie pisali obiektowe C przed pojawieniem się C ++ lub Objective-C . Zarówno C ++, jak i Objective-C były częściowo próbami przejęcia niektórych koncepcji OO używanych w C i sformalizowania ich jako części języka.

Oto naprawdę prosty program, który pokazuje, jak zrobić coś, co wygląda jak / jest wywołaniem metody (istnieją lepsze sposoby na zrobienie tego. To tylko dowód, że język obsługuje te pojęcia):

#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

Oczywiście nie będzie tak ładna, jak używanie języka z wbudowaną obsługą. Napisałem nawet „asembler obiektowy”.


6

Mały kod OOC do dodania:

#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

Kopie to przez rok:

Ponieważ system GObject jest trudny w użyciu z czystym C, próbowałem napisać fajne makra, aby złagodzić styl OO za pomocą 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;
}

Oto moja strona projektu (nie mam wystarczająco dużo czasu na napisanie en. Doc, jednak dokument w języku chińskim jest znacznie lepszy).

OOC-GCC


klasy statyczne ASM NEW DELETE ST ... są makra w OOC-GCC
dameng


4

Które artykuły lub książki są przydatne do korzystania z koncepcji OOP w C?

Dave Hansona C Interfejsy i implementacje jest doskonała na hermetyzacji i nazewnictwa i bardzo dobrze na wykorzystaniu wskaźników funkcji. Dave nie próbuje symulować dziedziczenia.


4

OOP jest tylko paradygmatem, w którym dane są ważniejsze niż kod w programach. OOP nie jest językiem. Tak więc, podobnie jak zwykły C jest prostym językiem, OOP w zwykłym C jest również prosty.


3
Dobrze powiedziane, ale powinien to być komentarz.
pqsk

4

Jedną z rzeczy, które możesz chcieć zrobić, jest przyjrzenie się implementacji zestawu narzędzi Xt dla X Window . Pewnie, że robi się to długo w zębie, ale wiele użytych konstrukcji zaprojektowano tak, aby działały w sposób tradycyjny w tradycyjnym C.Ogólnie oznacza to dodanie dodatkowej warstwy pośredniej tu i tam i zaprojektowanie struktur do nakładania się na siebie.

W ten sposób można naprawdę wiele zrobić na drodze OO usytuowanej w C, chociaż czasami wydaje się, że tak się dzieje, koncepcje OO nie powstały w pełni z umysłu #include<favorite_OO_Guru.h>. Naprawdę stanowiły one wiele ustalonych najlepszych praktyk tamtych czasów. Języki i systemy OO tylko destylują i wzmacniają części programistycznego entuzjazmu dnia.


4

Odpowiedź na pytanie brzmi „tak, możesz”.

Obiektowy zestaw C (OOC) jest przeznaczony dla tych, którzy chcą programować w sposób zorientowany obiektowo, ale trzymają się również starego, dobrego C. OOC implementuje klasy, pojedyncze i wielokrotne dziedziczenie, obsługę wyjątków.

funkcje

• Używa tylko makr C i funkcji, nie wymaga rozszerzeń językowych! (ANSI-C)

• Łatwy do odczytania kod źródłowy dla twojej aplikacji. Zadbano o to, aby sprawy były jak najprostsze.

• Pojedyncze dziedzictwo klas

• Wielokrotne dziedziczenie przez interfejsy i mixiny (od wersji 1.3)

• Wdrażanie wyjątków (w czystym C!)

• Funkcje wirtualne dla klas

• Zewnętrzne narzędzie do łatwej implementacji klasy

Aby uzyskać więcej informacji, odwiedź http://ooc-coding.sourceforge.net/ .


4

Wygląda na to, że ludzie próbują emulować styl C ++ za pomocą C. Uważam, że programowanie obiektowe C to tak naprawdę programowanie strukturalne. Można jednak osiągnąć takie cele, jak późne wiązanie, enkapsulacja i dziedziczenie. W przypadku dziedziczenia jawnie definiujesz wskaźnik do struktur podstawowych w podstruktury i jest to oczywiście forma wielokrotnego dziedziczenia. Musisz także ustalić, czy Twój

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

skompiluj z c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Dlatego radzę trzymać się czystego stylu C i nie próbować narzucać stylu C ++. Również w ten sposób można uzyskać bardzo czysty sposób budowania interfejsu API.


W przypadku dziedziczenia zazwyczaj klasa bazowa lub struktura instancji jest osadzona w pochodnej, nie jest przydzielana osobno i odnoszona za pomocą wskaźników. W ten sposób najwyższa podstawa zawsze znajduje się na początku struktur każdego z jej typów pochodnych, dzięki czemu można je łatwo rzutować na siebie, czego nie można zrobić ze wskaźnikami, które mogą mieć dowolne przesunięcie.
underscore_d

2

Zobacz http://slkpg.byethost7.com/instance.html, aby dowiedzieć się więcej o OOP w C. Podkreśla on dane instancji dla ponownego ustalenia przy użyciu tylko natywnego C. Wielokrotne dziedziczenie odbywa się ręcznie przy użyciu opakowań funkcji. Bezpieczeństwo typu jest zachowane. Oto mała próbka:

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

Jestem trochę spóźniony na imprezę, ale chcę podzielić się swoim doświadczeniem na ten temat: w tej chwili pracuję z osadzonymi rzeczami, a jedynym (niezawodnym) kompilatorem, który posiadam, jest C, więc chcę zastosować obiektowe podejście w moich osadzonych projektach napisanych w C.

Większość rozwiązań, które do tej pory widziałem, intensywnie wykorzystują rzutowania, więc tracimy bezpieczeństwo typu: kompilator nie pomoże ci, jeśli popełnisz błąd. To jest całkowicie niedopuszczalne.

Wymagania, które mam:

  • Unikaj typowania rzutów tak często, jak to możliwe, abyśmy nie stracili bezpieczeństwa typu;
  • Polimorfizm: powinniśmy być w stanie korzystać z metod wirtualnych, a użytkownik klasy nie powinien wiedzieć, czy dana metoda jest wirtualna, czy nie;
  • Wielokrotne dziedziczenie: nie używam go często, ale czasami naprawdę chcę, aby niektóre klasy zaimplementowały wiele interfejsów (lub rozszerzyły wiele nadklas).

Wyjaśniłem moje podejście szczegółowo w tym artykule: Programowanie obiektowe w C ; a ponadto istnieje narzędzie do automatycznego generowania kodu płyty bazowej dla klas podstawowych i pochodnych.


2

Zbudowałem małą bibliotekę, w której próbowałem i dla mnie działa naprawdę ładnie. Pomyślałem więc, że podzielę się tym doświadczeniem.

https://github.com/thomasfuhringer/oxygen

Pojedyncze dziedziczenie można dość łatwo zaimplementować za pomocą struktury i rozszerzając go dla każdej innej klasy potomnej. Prosty rzut na strukturę nadrzędną umożliwia stosowanie metod nadrzędnych na wszystkich potomkach. Tak długo, jak wiesz, że zmienna wskazuje na strukturę zawierającą tego rodzaju obiekt, zawsze możesz rzutować na klasę root i przeprowadzać introspekcję.

Jak już wspomniano, metody wirtualne są nieco trudniejsze. Ale są wykonalne. Aby uprościć sprawę, po prostu korzystam z tablicy funkcji w strukturze opisu klasy, którą każda klasa potomna kopiuje i zapełnia w razie potrzeby pojedyncze pola.

Wielokrotne dziedziczenie byłoby raczej skomplikowane do wdrożenia i ma znaczący wpływ na wydajność. Więc zostawiam to. Uważam, że w wielu przypadkach pożądane i użyteczne jest czyste modelowanie rzeczywistych okoliczności życiowych, ale prawdopodobnie w 90% przypadków pojedyncze dziedzictwo pokrywa potrzeby. Pojedyncze dziedzictwo jest proste i nic nie kosztuje.

Nie dbam też o bezpieczeństwo typu. Myślę, że nie powinieneś polegać na kompilatorze, aby zapobiec błędom programistycznym. I w każdym razie chroni cię tylko przed niewielką częścią błędów.

Zazwyczaj w środowisku obiektowym chcesz również zaimplementować liczenie referencji, aby zautomatyzować zarządzanie pamięcią w możliwym zakresie. Dlatego umieściłem również liczbę referencji w klasie root „Object” i niektóre funkcje, które zawierają enkapsulację alokacji i dezalokacji pamięci sterty.

Wszystko to jest bardzo proste i szczupłe i daje mi niezbędne elementy OO bez zmuszania mnie do radzenia sobie z potworem, którym jest C ++. Zachowuję elastyczność pozostawania w ziemi C, co między innymi ułatwia integrację bibliotek stron trzecich.


1

Proponuję użyć Objective-C, który jest nadzbiorem C.

Chociaż Objective-C ma 30 lat, pozwala pisać elegancki kod.

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


W takim przypadku poleciłbym zamiast tego C ++, ponieważ w rzeczywistości jest zorientowany obiektowo ...
yyny 1'15

To nie jest odpowiedź. Ale tak czy inaczej, @YoYoYonnY: Nie używam Objective-C i używam C ++, ale takie komentarze nie są użyteczne bez podstawy, a ty nie podałeś. Dlaczego twierdzisz, że Objective-C nie jest „faktycznie zorientowany obiektowo ...”? I dlaczego C ++ odnosi sukces, gdy nie działa Cel C? Zabawne jest to, że Objective-C, no cóż, dosłownie ma w nazwie słowo Object , podczas gdy C ++ sprzedaje się jako język wieloparadygmatowy, a nie OOP (tj. Nie przede wszystkim OOP, a w niektórych ekstremalnych poglądach ludowych nie OOP w ogóle) ... więc jesteś pewien, że nie podałeś tych nazw w niewłaściwy sposób?
underscore_d

0

Tak, ale nigdy nie widziałem, aby ktokolwiek próbował wdrożyć jakikolwiek polimorfizm za pomocą C.


6
Musisz się rozejrzeć :) Na przykład Microsoft Direct X ma polimorficzny interfejs C.
AShelly,

8
Spójrz na przykład na implementację jądra Linuxa. Jest to bardzo powszechna i szeroko stosowana praktyka w C.
Ilya,

3
również glib jest polimorficzny lub może być użyty w sposób, który pozwala na polimorfizm (podobnie jak C ++ musisz wyraźnie powiedzieć, które wywołania są wirtualne)
Spudd86,

1
Polimorfizm nie jest tak rzadki w C, z drugiej strony występuje wielokrotne dziedziczenie.
Johan Bjäreholt
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.