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ę .
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ę .
Odpowiedzi:
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.
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.
tCommClass
Zmieniono by nazwę na tCommVT
, a tCommClass
struct miałby tylko pola danych i jedno tCommVT vt
pole 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.
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 *
st->my_type->push(st, thing2);
zamiastst->my_type.push(st, thing2);
struct stack_type my_type;
zamiaststruct stack_type * my_type;
Class
strukturę? To sprawiłoby, że OO C byłby bardziej dynamiczny niż C ++. Co ty na to? Nawiasem mówiąc, +1.
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
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 ++.
glib
napisane w C w sposób obiektywny?
Jasne, że to możliwe. To właśnie robi GObject , struktura, na której opiera się cała GTK + i GNOME .
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.
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ć this
wskaź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ą this
wskaź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.
typedef
w środku struct
nie jest możliwe w C.
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.
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.
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_send
Do 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ą.
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.
typedef struct FOO_type FOO_type
zamiast typedef, aby unieważnić nagłówek, zyskasz dodatkową zaletę sprawdzania typu, wciąż nie ujawniając swojej struktury.
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.
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.
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).
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;
}
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;
}
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).
Jest przykładem dziedziczenia przy użyciu C w 1996 Dyskusja Jim Larson podano w sekcji 312 Programowanie Lunchtime Seminarium tutaj: wysokiego i niskiego poziom C .
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.
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.
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/ .
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.
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;
}
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:
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.
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.
Proponuję użyć Objective-C, który jest nadzbiorem C.
Chociaż Objective-C ma 30 lat, pozwala pisać elegancki kod.
Tak, ale nigdy nie widziałem, aby ktokolwiek próbował wdrożyć jakikolwiek polimorfizm za pomocą C.