Quali sono alcuni modi per scrivere codice orientato agli oggetti in C? Soprattutto per quanto riguarda il polimorfismo.
Vedi anche questo Stack Overflow questione orientamento agli oggetti in C .
Quali sono alcuni modi per scrivere codice orientato agli oggetti in C? Soprattutto per quanto riguarda il polimorfismo.
Vedi anche questo Stack Overflow questione orientamento agli oggetti in C .
Risposte:
Sì. In realtà Axel Schreiner fornisce gratuitamente il suo libro "Programmazione orientata agli oggetti in ANSI-C" che tratta l'argomento in modo abbastanza completo.
Dal momento che stai parlando di polimorfismo, allora sì, puoi, stavamo facendo quel genere di cose anni prima che avvenisse il C ++.
Fondamentalmente si usa a struct
per contenere sia i dati che un elenco di puntatori di funzione per puntare alle funzioni rilevanti per quei dati.
Quindi, in una classe di comunicazione, avresti una chiamata di apertura, lettura, scrittura e chiusura che verrebbe mantenuta come quattro puntatori di funzione nella struttura, accanto ai dati di un oggetto, qualcosa del tipo:
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;
Naturalmente, quei segmenti di codice sopra sarebbero effettivamente in un "costruttore" come rs232Init()
.
Quando 'erediti' da quella classe, cambi semplicemente i puntatori per puntare alle tue funzioni. Chiunque avesse chiamato quelle funzioni lo avrebbe fatto attraverso i puntatori a funzione, dandoti il tuo polimorfismo:
int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");
Un po 'come una vtable manuale.
Potresti anche avere classi virtuali impostando i puntatori su NULL - il comportamento sarebbe leggermente diverso da C ++ (un dump principale in fase di esecuzione anziché un errore in fase di compilazione).
Ecco un pezzo di codice di esempio che lo dimostra. Innanzitutto la struttura di classe di livello superiore:
#include <stdio.h>
// The top-level class.
typedef struct sCommClass {
int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;
Quindi abbiamo le funzioni per la 'sottoclasse' 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;
}
E anche quello 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;
}
E infine un programma di test per mostrarlo in azione:
// 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;
}
Questo produce l'output:
Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com
così puoi vedere che vengono chiamate le diverse funzioni, a seconda della sottoclasse.
tCommClass
verrebbe rinominato tCommVT
, e una tCommClass
struttura avrebbe solo campi dati e un singolo tCommVT vt
campo che punta alla "sola e sola" tabella virtuale. Portare in giro tutti i puntatori con ogni istanza aggiunge un sovraccarico non necessario e ricorda più di come faresti cose in JavaScript rispetto a C ++, IMHO.
Gli spazi dei nomi vengono spesso eseguiti:
stack_push(thing *)
invece di
stack::push(thing *)
Per trasformare una struttura C in qualcosa di simile a una classe C ++ puoi trasformare:
class stack {
public:
stack();
void push(thing *);
thing * pop();
static int this_is_here_as_an_example_only;
private:
...
};
In
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
E fai:
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
}
Non ho fatto il distruttore o l'eliminazione, ma segue lo stesso schema.
this_is_here_as_an_example_only è come una variabile di classe statica, condivisa tra tutte le istanze di un tipo. Tutti i metodi sono davvero statici, tranne per il fatto che alcuni accettano questo *
st->my_type->push(st, thing2);
anzichést->my_type.push(st, thing2);
struct stack_type my_type;
anzichéstruct stack_type * my_type;
Class
struttura generica ? Ciò renderebbe OO C più dinamico di C ++. Che ne dici di quello? A proposito, +1.
Credo che oltre ad essere utile a sé stante, implementare OOP in C sia un modo eccellente per imparare OOP e comprenderne il funzionamento interno. L'esperienza di molti programmatori ha dimostrato che per utilizzare una tecnica in modo efficiente e sicuro, un programmatore deve capire come i concetti sottostanti vengono infine implementati. Emulare classi, eredità e polimorfismo in C insegna proprio questo.
Per rispondere alla domanda originale, ecco un paio di risorse che insegnano come fare OOP in C:
Il post sul blog di EmbeddedGurus.com "Programmazione basata su oggetti in C" mostra come implementare le classi e la singola eredità in C portatile: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /
Nota applicativa "" C + "- Programmazione orientata agli oggetti in C" mostra come implementare classi, ereditarietà singola e associazione tardiva (polimorfismo) in C usando macro preprocessore: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , il codice di esempio è disponibile da http://www.state-machine.com/resources/cplus_3.0.zip
L'ho visto fatto. Non lo consiglierei. Il C ++ inizialmente è iniziato in questo modo come preprocessore che ha prodotto il codice C come passaggio intermedio.
In sostanza ciò che si finisce per fare è creare una tabella di invio per tutti i metodi in cui si memorizzano i riferimenti di funzione. Derivare una classe comporterebbe la copia di questa tabella di invio e la sostituzione delle voci che si desidera sostituire, con i nuovi "metodi" che devono chiamare il metodo originale se si desidera richiamare il metodo di base. Alla fine, si finisce per riscrivere C ++.
glib
scritto in C in modo obiettivo?
Sicuro che è possibile. Questo è ciò su cui GObject , il framework su cui si basano tutti GTK + e GNOME .
La sotto-libreria C stdio FILE è un eccellente esempio di come creare astrazione, incapsulamento e modularità in C. non adulterata
Ereditarietà e polimorfismo - gli altri aspetti spesso considerati essenziali per la OOP - non forniscono necessariamente i guadagni di produttività che promettono e sono state avanzate argomentazioni ragionevoli che possono effettivamente ostacolare lo sviluppo e la riflessione sul dominio del problema.
Esempio fondamentale con un animale e un cane: rispecchi il meccanismo vtable di C ++ (in gran parte comunque). Separate anche allocazione e istanziazione (Animal_Alloc, Animal_New) in modo da non chiamare malloc () più volte. Dobbiamo anche passare esplicitamente il this
puntatore in giro.
Se dovessi svolgere funzioni non virtuali, è trival. Semplicemente non li aggiungi alla vtable e le funzioni statiche non richiedono un this
puntatore. L'ereditarietà multipla richiede generalmente più vtables per risolvere le ambiguità.
Inoltre, dovresti essere in grado di utilizzare setjmp / longjmp per gestire le eccezioni.
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. Questo è testato su un compilatore C ++, ma dovrebbe essere facile farlo funzionare su un compilatore C.
typedef
dentro a struct
non è possibile in C.
Dai un'occhiata a GObject . È pensato per essere OO in C e un'implementazione di ciò che stai cercando. Se vuoi davvero OO, vai con C ++ o qualche altro linguaggio OOP. GObject può essere davvero difficile da lavorare a volte se sei abituato a gestire i linguaggi OO, ma come qualsiasi cosa, ti abituerai alle convenzioni e al flusso.
Questo è stato interessante da leggere. Ho riflettuto sulla stessa domanda da solo, e i vantaggi di pensarci sono questi:
Cercare di immaginare come implementare concetti OOP in un linguaggio non OOP mi aiuta a capire i punti di forza del linguaggio OOp (nel mio caso, C ++). Questo mi aiuta a giudicare meglio se usare C o C ++ per un determinato tipo di applicazione, in cui i vantaggi di uno sono superiori all'altro.
Nella mia navigazione sul web per informazioni e opinioni su questo ho trovato un autore che stava scrivendo codice per un processore incorporato e aveva solo un compilatore C disponibile: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Creazione-Fondazione-Classes-Part-1
Nel suo caso, analizzare e adattare i concetti di OOP nella semplice C era una ricerca valida. Sembra che fosse aperto a sacrificare alcuni concetti di OOP a causa del colpo ambientale delle prestazioni derivante dal tentativo di implementarli in C.
La lezione che ho preso è, sì, si può fare fino a un certo punto, e sì, ci sono alcuni buoni motivi per provarlo.
Alla fine, la macchina ruota i bit del puntatore dello stack, facendo saltare il contatore del programma e calcolando le operazioni di accesso alla memoria. Dal punto di vista dell'efficienza, minore è il numero di calcoli eseguiti dal programma, meglio è ... ma a volte dobbiamo pagare questa imposta semplicemente in modo da poter organizzare il nostro programma in modo da renderlo meno suscettibile all'errore umano. Il compilatore del linguaggio OOP si impegna per ottimizzare entrambi gli aspetti. Il programmatore deve fare molta più attenzione nell'implementare questi concetti in un linguaggio come C.
Potresti trovare utile consultare la documentazione di Apple per il suo set di API Core Foundation. È un'API C pura, ma molti tipi sono collegati a equivalenti di oggetti Objective-C.
Potresti anche trovare utile esaminare il design di Objective-C stesso. È un po 'diverso dal C ++ in quanto il sistema a oggetti è definito in termini di funzioni C, ad es. objc_msg_send
Per chiamare un metodo su un oggetto. Il compilatore traduce la sintassi della parentesi quadra in quelle chiamate di funzione, quindi non devi conoscerla, ma considerando la tua domanda potresti trovare utile imparare come funziona sotto il cofano.
Esistono diverse tecniche che possono essere utilizzate. Il più importante è più come dividere il progetto. Nel nostro progetto utilizziamo un'interfaccia dichiarata in un file .h e l'implementazione dell'oggetto in un file .c. La parte importante è che tutti i moduli che includono il file .h vedono solo un oggetto come a void *
, e il file .c è l'unico modulo che conosce gli interni della struttura.
Qualcosa del genere per una classe che chiamiamo FOO come esempio:
Nel file .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
Il file di implementazione C sarà qualcosa del genere.
#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;
}
Quindi do il puntatore esplicitamente a un oggetto per ogni funzione di quel modulo. Un compilatore C ++ lo fa implicitamente, e in C lo scriviamo esplicitamente.
Uso davvero this
nei miei programmi, per assicurarmi che il mio programma non si compili in C ++ e abbia la proprietà di essere in un altro colore nel mio editor di evidenziazione della sintassi.
I campi di FOO_struct possono essere modificati in un modulo e non è nemmeno necessario ricompilare un altro modulo per essere ancora utilizzabili.
Con quello stile gestisco già gran parte dei vantaggi di OOP (incapsulamento dei dati). Utilizzando i puntatori a funzione, è persino facile implementare qualcosa come l'ereditarietà, ma onestamente, è davvero solo raramente utile.
typedef struct FOO_type FOO_type
invece di un typedef si annulla l'intestazione, si ottiene il vantaggio aggiuntivo del controllo del tipo, pur non esponendo la struttura.
Puoi simularlo usando i puntatori a funzione e, in effetti, penso che sia teoricamente possibile compilare programmi C ++ in C.
Tuttavia, raramente ha senso forzare un paradigma su una lingua piuttosto che scegliere una lingua che utilizza un paradigma.
C orientato agli oggetti, si può fare, ho visto quel tipo di codice in produzione in Corea, ed è stato il mostro più orribile che abbia mai visto negli anni (era come l'anno scorso (2007) che ho visto il codice). Quindi sì, si può fare, e sì, la gente l'ha già fatto, e lo fa ancora anche in questi giorni. Ma raccomanderei C ++ o Objective-C, entrambi sono linguaggi nati da C, con lo scopo di fornire l'orientamento agli oggetti con paradigmi diversi.
Se sei convinto che un approccio OOP sia superiore al problema che stai cercando di risolvere, perché dovresti provare a risolverlo con un linguaggio non OOP? Sembra che tu stia utilizzando lo strumento sbagliato per il lavoro. Usa C ++ o qualche altro linguaggio variante C orientato agli oggetti.
Se lo stai chiedendo perché stai iniziando a scrivere un codice su un grande progetto già esistente scritto in C, non dovresti provare a forzare i tuoi paradigmi OOP (o quelli di chiunque altro) nell'infrastruttura del progetto. Seguire le linee guida già presenti nel progetto. In generale, le API pulite e librerie e moduli isolati andrà un lungo cammino verso avere un OOP- pulito ish design.
Se, dopo tutto questo, sei davvero pronto a fare OOP C, leggi questo (PDF).
Si, puoi. Le persone scrivevano C orientato agli oggetti prima che C ++ o Objective-C arrivassero sulla scena. Sia C ++ che Objective-C erano, in parte, tentativi di prendere alcuni dei concetti OO usati in C e formalizzarli come parte del linguaggio.
Ecco un programma davvero semplice che mostra come si può fare qualcosa che assomiglia / è una chiamata di metodo (ci sono modi migliori per farlo. Questa è solo la prova che il linguaggio supporta i concetti):
#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;
}
Un piccolo codice OOC da aggiungere:
#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;
}
Lo sto scavando da un anno:
Dato che il sistema GObject è difficile da usare con C puro, ho provato a scrivere delle belle macro per facilitare lo stile OO con 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;
}
Ecco il sito del mio progetto (non ho abbastanza tempo per scrivere en. Doc, tuttavia il documento in cinese è molto meglio).
C'è un esempio di eredità che usa C nel discorso di Jim Larson del 1996 tenuto al Seminario sulla pausa pranzo di programmazione 312 qui: Livello C alto e basso .
Quali articoli o libri sono buoni da usare concetti OOP in C?
Le interfacce e le implementazioni C di Dave Hanson sono eccellenti in termini di incapsulamento e denominazione e ottime per l'uso dei puntatori a funzione. Dave non cerca di simulare l'eredità.
Una cosa che potresti voler fare è esaminare l'implementazione del toolkit Xt per X Window . Sicuramente si sta allungando a lungo nel dente, ma molte delle strutture utilizzate sono state progettate per funzionare in modo OO all'interno del tradizionale C. Generalmente questo significa aggiungere un ulteriore livello di riferimento indiretto qua e là e progettare strutture da sovrapporre.
Puoi davvero fare molto in termini di OO situato in C in questo modo, anche se a volte sembra che sia così, i concetti di OO non sono nati completamente dalla mente di #include<favorite_OO_Guru.h>
. Costituivano davvero molte delle migliori pratiche consolidate del tempo. Linguaggi e sistemi OO solo distillati e amplificati parti dello zeitgeist programmatore del giorno.
La risposta alla domanda è "Sì, puoi".
Il kit C orientato agli oggetti (OOC) è per coloro che vogliono programmare in modo orientato agli oggetti, ma si attacca anche al buon vecchio C. OOC implementa classi, eredità singola e multipla, gestione delle eccezioni.
Caratteristiche
• Utilizza solo macro e funzioni C, non sono richieste estensioni di lingua! (ANSI-C)
• Codice sorgente di facile lettura per l'applicazione. Si è preso cura di rendere le cose il più semplice possibile.
• Singola eredità di classi
• Eredità multipla da interfacce e mixin (dalla versione 1.3)
• Implementazione di eccezioni (in puro C!)
• Funzioni virtuali per le classi
• Strumento esterno per una facile implementazione della classe
Per maggiori dettagli, visitare http://ooc-coding.sourceforge.net/ .
Sembra che le persone stiano cercando di emulare lo stile C ++ usando C. Il mio punto di vista è che fare una programmazione orientata agli oggetti C sta davvero facendo una programmazione orientata alla struttura. Tuttavia, puoi ottenere cose come associazione tardiva, incapsulamento ed ereditarietà. Per eredità definisci esplicitamente un puntatore alle strutture di base nella tua sottostruttura e questa è ovviamente una forma di eredità multipla. Dovrai anche determinare se il tuo
//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);
compilare con c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj
.
Quindi il consiglio è di attenersi a uno stile C puro e non cercare di forzare uno stile C ++. Anche in questo modo si presta a un modo molto pulito di costruire un'API.
Vedi http://slkpg.byethost7.com/instance.html per l'ennesima svolta su OOP in C. Enfatizza i dati di istanza per rientrare usando solo il nativo C. L'ereditarietà multipla viene fatta manualmente usando i wrapper di funzione. La sicurezza del tipo è mantenuta. Ecco un piccolo esempio:
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;
}
Sono un po 'in ritardo alla festa, ma voglio condividere la mia esperienza sull'argomento: al giorno d'oggi lavoro con cose incorporate e l'unico compilatore (affidabile) che ho è C, quindi voglio applicare orientato agli oggetti approccio nei miei progetti integrati scritti in C.
La maggior parte delle soluzioni che ho visto finora usano i caratteri tipografici pesantemente, quindi perdiamo la sicurezza dei tipi: il compilatore non ti aiuterà se commetti un errore. Questo è assolutamente inaccettabile.
Requisiti che ho:
Ho spiegato il mio approccio in dettaglio in questo articolo: programmazione orientata agli oggetti in C ; inoltre, esiste un'utilità per l'autogenerazione del codice boilerplate per le classi base e derivate.
Ho costruito una piccola biblioteca dove l'ho provato e per me funziona davvero bene. Quindi ho pensato di condividere l'esperienza.
https://github.com/thomasfuhringer/oxygen
La singola eredità può essere implementata abbastanza facilmente usando una struttura ed estendendola per ogni altra classe figlio. Un semplice cast sulla struttura genitore consente di utilizzare i metodi genitore su tutti i discendenti. Finché sai che una variabile punta a una struttura che contiene questo tipo di oggetto, puoi sempre eseguire il cast sulla classe radice e fare introspezione.
Come è stato menzionato, i metodi virtuali sono in qualche modo più complicati. Ma sono fattibili. Per semplificare le cose, utilizzo semplicemente una serie di funzioni nella struttura della descrizione della classe che ogni classe figlio copia e ripopola nei singoli slot dove richiesto.
L'ereditarietà multipla sarebbe piuttosto complicata da implementare e comporta un impatto significativo sulle prestazioni. Quindi lo lascio. Ritengo auspicabile e utile in parecchi casi modellare in modo pulito le circostanze della vita reale, ma probabilmente nel 90% dei casi la singola eredità copre i bisogni. E la singola eredità è semplice e non costa nulla.
Inoltre non mi interessa la sicurezza del tipo. Penso che non dovresti dipendere dal compilatore per impedirti di errori di programmazione. E ti protegge comunque solo da una parte piuttosto piccola di errori.
In genere, in un ambiente orientato agli oggetti si desidera anche implementare il conteggio dei riferimenti per automatizzare la gestione della memoria nella misura del possibile. Quindi ho inserito anche un conteggio di riferimento nella classe radice "Object" e alcune funzionalità per incapsulare allocazione e deallocazione della memoria heap.
È tutto molto semplice e snello e mi dà gli elementi essenziali di OO senza costringermi a gestire il mostro che è C ++. E conservo la flessibilità di rimanere in terra C, il che tra l'altro rende più semplice l'integrazione di librerie di terze parti.
Propongo di usare Objective-C, che è un superset di C.
Mentre Objective-C ha 30 anni, consente di scrivere codice elegante.
Sì, ma non ho mai visto nessuno tentare di implementare alcun tipo di polimorfismo con C.