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

zamknięte . To pytanie musi być bardziej skoncentrowane . Obecnie nie przyjmuje odpowiedzi.

chcesz poprawić to pytanie? Update the question so it edytując ten post.

Zamknięte 4 lata temu .

Popraw to pytanie

Jakie są sposoby zapisu kodu obiektowego w C? Szczególnie w odniesieniu do polimorfizmu.


Zobacz też to pytanie o przepełnienie stosu orientacja obiektu w C.

 511
Author: Peter Mortensen, 2008-12-09

10 answers

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

 370
Author: mepcotterell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 15:52:13

Skoro mówisz o polimorfizmie to tak, możesz, robiliśmy takie rzeczy lata 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.

Więc, w klasie komunikacyjnej, masz otwarte, Odczyt, Zapis i zamknięcie wywołania, które byłyby utrzymywane jako cztery wskaźniki funkcji w strukturze, obok danych dla obiektu, coś like:

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 faktycznie byłyby w "konstruktorze" takim jak rs232Init().

Kiedy 'dziedziczysz' z tej klasy, po prostu zmieniasz wskaźniki, aby wskazywały na twoje własne funkcje. Każdy, kto wywołał te funkcje, zrobiłby to za pomocą wskaźników funkcji, dając Twój polimorfizm:

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

Coś w rodzaju ręcznego vtable.

Możesz mieć nawet wirtualne klasy ustawiając wskaźniki NA NULL -zachowanie byłoby nieco różni się od C++ (zrzut pamięci w czasie wykonywania, a nie błąd w czasie kompilacji).

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

#include <stdio.h>

// The top-level class.

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

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

Oraz 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 testowy program do pokazania 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 wyjście:

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

Więc widać, że różne funkcje są wywoływane, w zależności od podklasy.

 361
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-12 00:39:22

Przestrzenie nazw są często wykonywane przez wykonanie:

stack_push(thing *)

Zamiast

stack::push(thing *)

Aby utworzyć C strukturę w coś w rodzaju C++ klasy można przekształcić:

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

Do

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 do:

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 kasowania, ale to działa zgodnie z tym samym wzorem.

This_is_here_as_an_example_only jest jak statyczna zmienna klasy -- współdzielona pomiędzy wszystkimi instancjami danego typu. Wszystkie metody są naprawdę statyczne, z wyjątkiem tego, że niektóre biorą to *

 90
Author: nategoose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 16:14:13

Uważam, że implementacja OOP w C jest nie tylko użyteczna, ale także doskonałym sposobem na nauczenie się OOP i zrozumienie jego wewnętrznego działania. Doświadczenie wielu programistów pokazało, że aby efektywnie i pewnie wykorzystać technikę, programista musi zrozumieć, w jaki sposób podstawowe pojęcia są ostatecznie realizowane. Emulowanie klas, dziedziczenie i polimorfizm w C uczy właśnie tego.

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

EmbeddedGurus.com wpis na blogu "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/

Application Note "" C+" - Object Oriented Programming in C " pokazuje jak zaimplementować klasy, pojedyncze dziedziczenie i późne wiązanie (polimorfizm) w C za pomocą preprocesora makra: http://www.state-machine.com/resources/cplus_3.0_manual.pdf , przykładowy kod jest dostępny od http://www.state-machine.com/resources/cplus_3.0.zip

 57
Author: Miro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 01:37:14

Widziałem to. Nie polecam. C++ początkowo zaczynał w ten sposób jako preprocesor, który wytwarzał kod C jako etap pośredni.

Zasadniczo to, co robisz, to tworzenie tabeli wysyłkowej dla wszystkich metod, w której przechowujesz odwołania do funkcji. Wyprowadzenie klasy pociągałoby za sobą skopiowanie tej tabeli dyspozytorskiej i zastąpienie wpisów, które chcesz nadpisać, a twoje nowe "metody" muszą wywołać oryginalną metodę, jeśli chce ona wywołać metodę bazową. W końcu piszesz c++na nowo.

 31
Author: tvanfosson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-09 04:04:02

Jasne, że to możliwe. To jest to, co GObject , framework, na którym oparte są wszystkie GTK+ i GNOME.

 26
Author: Peter Mortensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 15:57:01

Biblioteka plików C stdio jest doskonałym przykładem tworzenia abstrakcji, enkapsulacji i modułowości w nieskomplikowanym C.

Dziedziczenie i polimorfizm-inne aspekty często uważane za istotne dla OOP - niekoniecznie zapewniają obiecujący przyrost wydajności i rozsądne pojawiły się argumenty , które mogą faktycznie utrudniać rozwój i myślenie o problemie.

 18
Author: msw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-24 11:58:49

Trywialny przykład ze zwierzęciem i psem: lustro mechanizmu vtable C++(w dużej mierze i tak). Oddzielasz również alokację i instancję (Animal_Alloc, Animal_New), więc nie wywołujemy malloc () wiele razy. Musimy również jawnie przekazać wskaźnik this.

Jeśli miałbyś wykonywać funkcje nie-wirtualne, to jest to trival. Po prostu nie dodajesz ich do vtable i funkcje statyczne nie wymagają wskaźnika this. Dziedziczenie wielokrotne zazwyczaj wymaga wielu vtables do rozwiązania niejasności.

Również powinieneś być w stanie użyć 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.

 15
Author: Jasper Bekkers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 16:02:51

Sprawdź GObject . Ma być OO W C i jedna implementacja tego czego szukasz. Jeśli naprawdę chcesz OO, przejdź do C++ lub innego języka OOP. GObject może być naprawdę trudne do pracy z czasami, jeśli jesteś przyzwyczajony do czynienia z językami OO, ale jak wszystko, przyzwyczaisz się do konwencji i przepływu.

 13
Author: NG.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-28 19:53:40

To było ciekawe do przeczytania. Sam zastanawiałem się nad tym samym pytaniem, a korzyści płynące z myślenia o nim są takie:

  • Próba wyobrażenia sobie, jak zaimplementować pojęcia OOP w języku Nie-OOP pomaga mi zrozumieć mocne strony języka OOp (w moim przypadku C++). To pomaga mi lepiej ocenić, czy używać C lub c++ dla danego typu aplikacji-gdzie korzyści z jednego out-waży drugi.

  • W moim przeglądaniu stron dla informacje i opinie 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 prostym C była słusznym dążeniem. Wydaje się, że był otwarty na rezygnację z niektórych koncepcji OOP ze względu na hit wydajności wynikający z próby ich wdrożenia w C.

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

W końcu maszyna krąży po bitach wskaźnika stosu, powodując, że licznik programu przeskakuje i oblicza operacje dostępu do pamięci. Z punktu widzenia wydajności, im mniej tych obliczeń wykonanych przez twój program, tym lepiej... ale czasami musimy zapłacić ten podatek po prostu, abyśmy mogli zorganizować nasz program w sposób, który czyni go najmniej podatnym za ludzki błąd. Kompilator języka OOP dąży do optymalizacji obu aspektów. Programista musi być znacznie ostrożniejszy w implementacji tych pojęć w języku takim jak C.

 13
Author: 3 revs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 16:28:40