2009-02-07 20 views
55

Czy istnieje sposób pisania kodu podobnego do OO w języku programowania C?Jak mogę symulować polimorfizm w stylu OO w C?


Zobacz także:

znaleźć, szukając na "[c] oo".

+0

dzięki za podpowiedź z nawiasami kwadratowymi. Tego nie wiedziałem. – prinzdezibel

+3

Tak, to musi być bardziej odkrywalne czy coś. Próbowałem sformułować dobrą sugestię dla uservoice, ale jej nie łączę. – dmckee

Odpowiedz

48

Pierwszy kompilator C++ („C z klasami”) faktycznie wygenerować kod w C, więc to na pewno wykonalne.

Zasadniczo twoją klasą podstawową jest struct; pochodne struktury muszą zawierać podstawową strukturę na pierwszej pozycji, tak aby wskaźnik do "pochodnej" struktury również był prawidłowym wskaźnikiem do podstawowej struktury.

typedef struct { 
    data member_x; 
} base; 

typedef struct { 
    struct base; 
    data member_y; 
} derived; 

void function_on_base(struct base * a); // here I can pass both pointers to derived and to base 

void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class 

Funkcje te mogą być częścią struktury jako wskaźników funkcji, tak, że składnia jak p> Rozmowy (P) staje się możliwe, ale trzeba jeszcze wyraźnie przekazać wskaźnik do struktury do samej funkcji .

+6

Nie wyjaśnia to, w jaki sposób nadpisanie metody działałoby w C. Jak można przesłonić function_on_base, aby uzyskać dostęp do pochodnego memeber_y, jak to możliwe w wywołaniach polimorficznych C++? –

+0

Nadpisywanie nie jest możliwe w C. –

+6

Ta odpowiedź jest niepoprawna. Przekazywanie 'wyprowadzonej struktury *' do 'function_on_base' nie będzie się kompilować; 'wyprowadzenie struct *' jest innym typem niż 'struct base *' nawet jeśli adres jest poprawny; jednak jeśli rzucisz wskaźnik z "wyprowadzonego *" na "podstawowy *", to zadziała (ale stracisz kontrolę nad kompilacją, a zamiast tego dostaniesz awarię w środowisku wykonawczym). @PatrickCollins Przesłanianie _jest_ możliwe w C: http://pastebin.com/W5xEytbv – weberc2

47

Powszechnym podejściem jest zdefiniowanie struktury ze wskaźnikami do funkcji. To definiuje "metody", które można wywoływać w dowolnym typie. Podtypy następnie ustawiają własne funkcje w tej wspólnej strukturze i zwracają je.

Na przykład w jądra linux, istnieje struct:

struct inode_operations { 
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *); 
    struct dentry * (*lookup) (struct inode *,struct dentry *, 
           struct nameidata *); 
    ... 
}; 

Każdy zarejestrowany typ systemu plików, a następnie rejestruje swoje funkcje create, lookup, a pozostałe funkcje. Reszta kodu można użyć inode_operations niż ogólna:

struct inode_operations *i_op; 
i_op -> create(...); 
+1

Zasadniczo to w jaki sposób cfront (oryginalny kompilator C++) przekonwertował C++ na C, który został skompilowany przy pomocy PCC. Wiele się dowiedziałem, jak to działało, radząc sobie z plikami bazowymi z tego bałaganu. –

+4

Brzmi jak zabawa :-) –

23

C++ nie jest tak daleko od C

Klasy są konstrukcje z ukrytą wskaźnik do tablicy wskaźników funkcji zwanej vtable. Sam Vtable jest statyczny. Kiedy typy wskazują na Vtables o tej samej strukturze, ale tam, gdzie wskaźniki wskazują na inną implementację, pojawia się polimorfizm.

Zaleca się enkapsulację logiki połączeń w funkcji, która przyjmuje parametr struct jako, aby uniknąć bałaganu w kodzie.

Powinieneś także encapsulcte struktur instancjonowanie i inicjalizacji w funkcjach (to jest równoważne z konstruktorem C++) i usuwanie (destruktor w C++). W każdym razie są to dobre praktyki.

typedef struct 
{ 
    int (*SomeFunction)(TheClass* this, int i); 
    void (*OtherFunction)(TheClass* this, char* c); 
} VTable; 

typedef struct 
{ 
    VTable* pVTable; 
    int member; 

} TheClass; 

Aby wywołać metodę:

int CallSomeFunction(TheClass* this, int i) 
{ 
    (this->pVTable->SomeFunction)(this, i); 
} 
1

Funkcje plików fopen, fclose, fread są przykładami kodu oo in C zamiast prywatnych danych w klasie, pracują na strukturze pliku, który służy do enkapsulacji danych, a funkcje języka C działają jako funkcje klasy elementu. http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016

+0

Tytuł brzmi teraz: "Podejście obiektowe z C++" – prinzdezibel

+3

Wskazana książka to tylko C++. Uwaga: mam kopię i nie polecam jej dzisiaj. Gdybym miał jedno zalecenie OO w C, aby dać dzisiaj, byłoby to interfejsy C i implementacje: Techniki tworzenia oprogramowania wielokrotnego użytku David Hanson (http://amzn.com/0201498413). Absolutnie genialna książka i większość programistów dobrze by to zrozumiała, większość przykładów pochodzi z kompilatorów, więc kod jest wzorowy. – Harry

12

Dodatek B wyrobu Open Reusable Object Models, Ian Piumarta Alessandro Warth z VPRI stanowi wykonanie modelu obiektu gnu C, około 140 linii kodu. To fascynująca lektura!

Oto niebuforowanych wersja makra, który wysyła wiadomości do obiektów, używając rozszerzenia GNU na C (oświadczenie ekspresji):

struct object; 

typedef struct object *oop; 
typedef oop *(*method_t)(oop receiver, ...); 

//... 

#define send(RCV, MSG, ARGS...) ({ \ 
    oop r = (oop)(RCV); \ 
    method_t method = _bind(r, (MSG)); \ 
    method(r, ##ARGS); \ 
}) 

W tym samym dokumencie, zajrzyj na object, vtable , vtable_delegated i symbol, funkcje i funkcje _bind i vtable_lookup.

Pozdrawiam!

12

Spojrzałem na wszystkich elses' odpowiedzi i podszedł z tym:

#include <stdio.h> 

typedef struct 
{ 
    int (*get)(void* this); 
    void (*set)(void* this, int i); 
    int member; 

} TheClass; 

int Get(void* this) 
{ 
    TheClass* This = (TheClass*)this; 
    return This->member; 
} 

void Set(void* this, int i) 
{ 
    TheClass* This = (TheClass*)this; 
    This->member = i; 
} 

void init(TheClass* this) 
{ 
    this->get = &Get; 
    this->set = &Set; 
} 

int main(int argc, char **argv) 
{ 
    TheClass name; 
    init(&name); 
    (name.set)(&name, 10); 
    printf("%d\n", (name.get)(&name)); 
    return 0; 
} 

Mam nadzieję, że odpowiedzi na kilka pytań.

+3

Dobry przykład. Byłoby jeszcze lepiej, gdybyś miał 2 "pochodne" klasy z różnymi init/get/set. "prywatni" członkowie/funkcje mogą być wykonywane za pomocą [opaque structs] (http://stackoverflow.com/questions/3965279/opaque-c-structs-how-should-they-be-declared). Konwencja nazewnictwa jest również ważna: 'mylib_someClass_aMethod (this)' jest dobrą opcją. –

+0

To wygląda schludnie. Umieściłem to w pliku c i skompilowałem w porządku dla mikrokontrolera ARM. Nie docieram daleko, ale po prostu lubię upuszczać notatkę jako potwierdzenie do postu red_hax0r. – coarist

+0

gdzie jest polimorfizm? –

0

Z Wikipedii: W języku programowania i teorii typów, polimorfizm (z greckiego πολύς, polys, "wiele, dużo" i μορφή, morphē, "forma, kształt") jest zapewnieniem pojedynczego interfejsu dla podmiotów różnych typy.

Powiedziałbym więc, że jedynym sposobem wdrożenia go w C jest użycie argumentów variadic wraz z niektórymi (semi) automatycznymi typami zarządzania informacjami. Na przykład w C++ można pisać (przepraszam za trivialness):

void add(int& result, int a1, int a2); 
void add(float& result, float a1, float a2); 
void add(double& result, double a1, double a2); 

W C, między innymi rozwiązaniami, to najlepsze co możesz zrobić, to coś takiego:

int int_add(int a1, int a2); 
float float_add(float a1, fload a2); 
double double_add(double a1, double a2); 

void add(int typeinfo, void* result, ...); 

Następnie trzeba:

  1. do wdrożenia „typeinfo” z wyliczenia/makr
  2. do wdrożenia tej ostatniej funkcji z stdarg.h rzeczy
  3. pożegnać C typu statycznego sprawdzania

Jestem prawie pewien, że każda inna realizacja polimorfizmu powinien wyglądać podobnie jak ten sam jeden. Powyższe odpowiedzi wydają się raczej próbować adresować dziedziczenie bardziej niż polimorfizm!

+0

W C11 nowe słowo kluczowe [_Generic] (http://en.cppreference.com/w/c/language/generic) upraszcza ten wzorzec projektowania. Gorąco polecam dla osób wybierających to podejście. –

0
#include <stdio.h> 

typedef struct { 
    int x; 
    int z; 
} base; 

typedef struct { 
    base; 
    int y; 
    int x; 
} derived; 

void function_on_base(base * a) // here I can pass both pointers to derived and to base 
{ 
    printf("Class base [%d]\n",a->x); 
    printf("Class base [%d]\n",a->z); 
} 
void function_on_derived(derived * b) // here I must pass a pointer to the derived class 
{ 
    printf("Class derived [%d]\n",b->y); 
    printf("Class derived [%d]\n",b->x); 
} 

int main() 
{ 
    derived d; 
    base b; 
    printf("Teste de poliformismo\n"); 

    b.x = 2; 
    d.y = 1; 
    b.z = 3; 
    d.x = 4; 
    function_on_base(&b); 
    function_on_base(&d); 
    function_on_derived(&b); 
    function_on_derived(&d); 
    return 0; 
} 

Wyjście było:

Class base [3] 
Class base [1] 
Class base [4] 
Class derived [2] 
Class derived [3] 
Class derived [1] 
Class derived [4] 

tak to działa, jego kod polimorficzny.

UncleZeiv wyjaśnił o tym na samym początku.

0

Aby również zbudować funkcjonalność OO w C, można przejrzeć poprzednie odpowiedzi.

Ale, (jak zostało zadane w innych pytaniach przekierowanych na ten), jeśli chcesz zrozumieć, czym jest polimorfizm, na przykład w języku C.Może się mylę, ale nie mogę myśleć o niczym łatwym do zrozumienia, jak arytmetyczne wskaźniki C. Moim zdaniem, arytmetyka wskaźnika jest z natury polimorficzna w C. W poniższym przykładzie ta sama funkcja (metoda w OO), a mianowicie dodanie (+), spowoduje inne zachowanie w zależności od właściwości struktur wejściowych.

Przykład:

double a*; 
char str*; 

a=(double*)malloc(2*sizeof(double)); 
str=(char*)malloc(2*sizeof(char)); 

a=a+2; // make the pointer a, point 2*8 bytes ahead. 

str=str+2; // make the pointer str, point 2*1 bytes ahead. 

Disclaimer: Jestem bardzo nowa w C i bardzo cieszymy się być poprawione i uczyć się od Inni użytkownika, lub nawet całkowicie usunąć tę odpowiedź, powinno być źle. Wielkie dzięki,