2009-02-07 10 views
55

Esiste un modo per scrivere codice simile a OO nel linguaggio di programmazione C?Come posso simulare il polimorfismo in stile OO in C?


Consulta anche:

dalla ricerca per "[c] oo".

+0

grazie per il suggerimento con le parentesi quadre. Non lo sapevo. – prinzdezibel

+3

Sì, questo deve essere reso più rilevabile o qualcosa del genere. Ho cercato di formulare un buon suggerimento per uservoice, ma non è venuto insieme. – dmckee

risposta

48

Il primo compilatore C++ ("C con le classi") sarebbe in realtà generare il codice C, così che è sicuramente fattibile.

Fondamentalmente, la classe base è una struttura; le strutture derivate devono includere la struttura di base nella prima posizione, in modo che un puntatore alla struttura "derivata" sia anche un puntatore valido alla struttura di base.

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 

Le funzioni possono essere parte della struttura come puntatori a funzione, in modo che una sintassi simile p-> chiamata (p) diventa possibile, ma si devono ancora passare esplicitamente un puntatore alla struttura alla funzione stessa .

+6

Questo non spiega come il metodo che esegue l'override funzionerebbe in C. Come puoi sovrascrivere function_on_base per accedere al memeber_y derivato come puoi nelle chiamate polimorfe C++? –

+0

La sovrascrittura non è possibile in C. –

+6

Questa risposta è errata. Il passaggio di una 'struct derived *' a 'function_on_base' non verrà compilato; 'struct derived *' è un tipo diverso da 'struct base *' anche se l'indirizzo è corretto; tuttavia, se lanci il puntatore da 'derived *' a 'base *', funzionerà (ma ti perderai il controllo del tipo in fase di compilazione e invece otterrai un crash in fase di runtime). @PatrickCollins Override _è possibile in C: http://pastebin.com/W5xEytbv – weberc2

47

L'approccio comune è definire la struttura con i puntatori alle funzioni. Questo definisce i "metodi" che possono essere chiamati su qualsiasi tipo. I sottotipi quindi impostano le proprie funzioni in questa struttura comune e lo restituiscono.

Ad esempio, nel Kernel di Linux, c'è struct:

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

Ogni tipo di file system registrato quindi registra le proprie funzioni per create, lookup e funzioni rimanenti. Resto del codice può che utilizzare inode_operations generici:

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

Questo è fondamentalmente il modo in cui cfront (il compilatore C++ originale) ha convertito il C++ in C che è stato poi compilato con pcc. Ho imparato molto su come questo ha funzionato con i file core da quel casino. –

+4

Sembra divertente :-) –

23

C++ non è poi così lontano da C.

Le classi sono strutture con un puntatore nascosta a una tabella di puntatori a funzione chiamata VTable. Il Vtable stesso è statico. Quando i tipi puntano a Vtables con la stessa struttura ma dove i puntatori puntano ad altre implementazioni, si ottiene il polimorfismo.

Si consiglia di incapsulare la logica delle chiamate in funzione che prende la struttura come parametro per evitare l'ingombro del codice.

Si dovrebbero anche istanziare le strutture di incapsulamento e inizializzazione nelle funzioni (questo è equivalente a un costruttore C++) e la cancellazione (distruttore in C++). Queste sono comunque buone pratiche.

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

typedef struct 
{ 
    VTable* pVTable; 
    int member; 

} TheClass; 

per chiamare il metodo:

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

le funzioni File fopen, fclose, fread sono esempi di codice OO in C. invece dei dati privati ​​in classe, lavorano sulla struttura dei file, che è usato per incapsulare i dati e le funzioni C agiscono come funzioni di classe membro. http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016

+0

Il titolo ora legge: 'Un approccio orientato agli oggetti con C++' – prinzdezibel

+3

Il libro a cui punta è solo C++. Nota: ne ho una copia e non la consiglierei oggi. Se avessi una raccomandazione OO in C per dare oggi sarebbe C Interfacce e Implementazioni: Tecniche per la creazione di software riutilizzabile di David Hanson (http://amzn.com/0201498413). Un libro assolutamente geniale e la maggior parte dei programmatori farebbe bene a capirlo, la maggior parte degli esempi in esso contenuti proviene dai backend del compilatore, quindi il codice è esemplare. – Harry

12

Appendice B di questo articolo Open Reusable Object Models, da Ian Piumarta e Alessandro Warth di VPRI è un'implementazione di un modello a oggetti in GNU C, circa 140 linee di codice. È una lettura affascinante!

Ecco la versione memorizzata nella cache della macro che invia messaggi a oggetti, utilizzando un'estensione GNU C (istruzione di espressione):

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); \ 
}) 

Nello stesso documento, hanno uno sguardo al object, vtable , vtable_delegated e symbol, e le funzioni _bind e vtable_lookup.

Cheers!

12

Ho guardato tutti risposte altre famiglie e arrivato fino a questo:

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

spero che risponde ad alcune domande.

+3

Buon esempio. Sarebbe ancora meglio se tu avessi 2 classi "derivate" con diversi init/get/set. I membri/funzioni "privati" possono essere eseguiti con [strutture opache] (http://stackoverflow.com/questions/3965279/opaque-c-struct-how-should-the-be-declared). Anche la convenzione sui nomi è importante: 'mylib_someClass_aMethod (this)' è una buona possibilità. –

+0

Questo sembra pulito. Ho messo questo in un file c e compilato bene per un microcontrollore ARM. Non mi sto ancora allontanando, ma mi piace solo rilasciare una nota come riconoscimento per il post di red_hax0r. – coarist

+0

dov'è il polimorfismo? –

0

Da Wikipedia: In linguaggi di programmazione e teoria dei tipi, il polimorfismo (dal πολύς greca, poligoni, "molti, tanto" e μορφή, morphe, "la forma, la forma") è la fornitura di una singola interfaccia a soggetti di diversa tipi.

Quindi direi che l'unico modo per implementarlo in C è l'utilizzo di argomenti variadici insieme ad una gestione di informazioni di tipo (semi) automatico. Per esempio in C++ è possibile scrivere (mi dispiace per 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); 

In C, tra le altre soluzioni, il meglio che puoi fare è qualcosa di simile:

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, ...); 

allora avete bisogno:

  1. per attuare il "typeinfo" con enums/macro
  2. per attuare quest'ultima funzione con roba stdarg.h
  3. di dire addio a C di tipo statico controllando

Sono quasi sicuro che qualsiasi altra applicazione del polimorfismo dovrebbe essere molto simile a questo uno. Le risposte di cui sopra, invece, sembrano provare ad indirizzare l'ereditarietà più del polimorfismo!

+0

In C11, la nuova parola chiave [_Generic] (http://en.cppreference.com/w/c/language/generic) semplifica notevolmente questo modello di progettazione. Lo consiglio vivamente a chi sceglie questo approccio. –

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

L'uscita era:

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

quindi funziona, è un codice polimorfico.

UncleZeiv lo ha spiegato all'inizio.

0

Allo scopo di creare la funzionalità OO in C, è possibile esaminare le risposte precedenti.

Ma, (come è stato chiesto in altre domande reindirizzate a questo) se si vuole capire che cos'è il polimorfismo, con esempi in linguaggio C.Forse ho torto, ma non riesco a pensare a qualcosa di così facile da capire come l'aritmetica dei puntatori C. A mio parere, l'aritmetica del puntatore è intrinsecamente polimorfa in C. Nell'esempio seguente la stessa funzione (metodo in OO), cioè l'addizione (+), produrrà un comportamento diverso a seconda delle proprietà delle strutture di input.

Esempio:

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: io sono molto nuovo in C e molto impaziente di essere corretto e imparare dai commenti di altri utenti, o anche cancellare completamente questa risposta, dovrebbe essere sbagliato. Molte grazie,