2013-07-12 10 views
15

So che sembra sciocco e so che C non è un linguaggio orientato agli oggetti.Invio di metodi dinamici in C

Ma esiste un modo in cui è possibile ottenere l'invio di metodi dinamici in C? Ho pensato ai puntatori di funzione ma non ho capito l'intera idea.

Come posso implementarlo?

+3

È possibile farlo imitando il modo in cui si è fatto in altre lingue "sotto il cofano". In C++, ad esempio, la spedizione viene eseguita cercando un indice in una tabella di puntatori di funzione. Quando sottoclassi qualcosa aggiungi i tuoi portatori alla fine del tavolo. Puoi usare una struct di portatori di funzioni per rappresentare la tabella e solo includere la strict della classe genitrice all'inizio della tua per ereditarietà. – Will

+1

Facciamo una discussione pratica dicendo cosa vuoi ottenere nella tua applicazione. Quindi possiamo fornire esempi di codice e idee realizzabili. La tua domanda attuale è piuttosto vaga e puoi rispondere con Google in 15 minuti. –

+0

bene che qualcuno preferirebbe che se hanno qualcosa di codice già scritto in C, ma per aggiungere alcune funzionalità. invece di scrivere da zero usando OOlanguage. – Dineshkumar

risposta

1

C++ è stato (inizialmente) costruito sopra C. Il primo compilatore C++ stava effettivamente generando C come passaggio intermedio. Ergo, sì è possibile.

Here's come C++ fa cose come questa.

C'è un sacco di informazioni solide disponibili online, più di quanto possiamo scrivere insieme qui in pochi minuti. "Google e troverai".

Lei ha detto in un commento sopra:

Beh qualcuno preferirebbe che se hanno qualcosa di codice già scritto in C, ma per aggiungere alcune funzionalità. invece di scrivere da zero usando OOlanguage.

Per avere funzionalità come questa in C, sarà necessario reimplementare le funzionalità del linguaggio OO. Fare in modo che le persone usino questo nuovo metodo OO è il più grande fattore contro l'usabilità. In altre parole, creando ancora un altro metodo per il riutilizzo, in realtà si rendono le cose meno riutilizzabili.

+4

Dire che è possibile senza spiegare come è una risposta piuttosto debole. –

+0

come implementarlo? – Dineshkumar

+1

@ChrisStratton Sono d'accordo, mi proverò. –

3

Sì. Può essere facilmente raggiunto. Dovresti utilizzare una serie di puntatori di funzione, quindi utilizzare quei puntatori di funzione per effettuare la chiamata. Se si desidera "sovrascrivere" una funzione, basta impostare lo slot corrispondente in modo che punti alla nuova funzione. Questo è esattamente il modo in cui C++ implementa le funzioni virtuali.

+1

È un buon inizio, ma devi anche tenere traccia dei tipi di dati, in modo da sapere quale versione utilizzare. –

+1

un pezzo di codice sarebbe di grande aiuto per i principianti come me! – Dineshkumar

+0

Una descrizione abbastanza completa della programmazione OO in C è disponibile all'indirizzo http://www.cs.rit.edu/~ats/books/ooc.pdf – cmaster

30

Come altri hanno notato, è certamente possibile implementarlo in C. Non solo è possibile, è un meccanismo abbastanza comune. L'esempio più comunemente utilizzato è probabilmente l'interfaccia del descrittore di file in UNIX. Una chiamata read() su un descrittore di file verrà inviata a una funzione di lettura specifica per il dispositivo o il servizio che ha fornito quel descrittore di file (era un file? Era un socket? Era un altro tipo di dispositivo?).

L'unico trucco è recuperare il puntatore del tipo concreto dal tipo astratto. Per i descrittori di file, UNIX utilizza una tabella di ricerca che contiene informazioni specifiche per tale descrittore. Se si utilizza un puntatore a un oggetto, il puntatore in possesso dell'utente dell'interfaccia è il tipo "base", non il tipo "derivato". C non ha ereditarietà di per sé, ma garantisce che il puntatore al primo elemento di un struct sia uguale al puntatore del contenente struct. Quindi puoi usare questo per recuperare il tipo "derivato" facendo diventare l'istanza della "base" il primo membro del "derivato".

Ecco un semplice esempio con una pila:

struct Stack { 
    const struct StackInterface * const vtable; 
}; 

struct StackInterface { 
    int (*top)(struct Stack *); 
    void (*pop)(struct Stack *); 
    void (*push)(struct Stack *, int); 
    int (*empty)(struct Stack *); 
    int (*full)(struct Stack *); 
    void (*destroy)(struct Stack *); 
}; 

inline int stack_top (struct Stack *s) { return s->vtable->top(s); } 
inline void stack_pop (struct Stack *s) { s->vtable->pop(s); } 
inline void stack_push (struct Stack *s, int x) { s->vtable->push(s, x); } 
inline int stack_empty (struct Stack *s) { return s->vtable->empty(s); } 
inline int stack_full (struct Stack *s) { return s->vtable->full(s); } 
inline void stack_destroy (struct Stack *s) { s->vtable->destroy(s); } 

Ora, se volevo implementare uno stack utilizzando una matrice fissa di dimensioni, avrei potuto fare qualcosa di simile:

struct StackArray { 
    struct Stack base; 
    int idx; 
    int array[STACK_ARRAY_MAX]; 
}; 
static int stack_array_top (struct Stack *s) { /* ... */ } 
static void stack_array_pop (struct Stack *s) { /* ... */ } 
static void stack_array_push (struct Stack *s, int x) { /* ... */ } 
static int stack_array_empty (struct Stack *s) { /* ... */ } 
static int stack_array_full (struct Stack *s) { /* ... */ } 
static void stack_array_destroy (struct Stack *s) { /* ... */ } 
struct Stack * stack_array_create() { 
    static const struct StackInterface vtable = { 
     stack_array_top, stack_array_pop, stack_array_push, 
     stack_array_empty, stack_array_full, stack_array_destroy 
    }; 
    static struct Stack base = { &vtable }; 
    struct StackArray *sa = malloc(sizeof(*sa)); 
    memcpy(&sa->base, &base, sizeof(base)); 
    sa->idx = 0; 
    return &sa->base; 
} 

E se Volevo implementare uno stack utilizzando invece un elenco:

struct StackList { 
    struct Stack base; 
    struct StackNode *head; 
}; 
struct StackNode { 
    struct StackNode *next; 
    int data; 
}; 
static int stack_list_top (struct Stack *s) { /* ... */ } 
static void stack_list_pop (struct Stack *s) { /* ... */ } 
static void stack_list_push (struct Stack *s, int x) { /* ... */ } 
static int stack_list_empty (struct Stack *s) { /* ... */ } 
static int stack_list_full (struct Stack *s) { /* ... */ } 
static void stack_list_destroy (struct Stack *s) { /* ... */ } 
struct Stack * stack_list_create() { 
    static const struct StackInterface vtable = { 
     stack_list_top, stack_list_pop, stack_list_push, 
     stack_list_empty, stack_list_full, stack_list_destroy 
    }; 
    static struct Stack base = { &vtable }; 
    struct StackList *sl = malloc(sizeof(*sl)); 
    memcpy(&sl->base, &base, sizeof(base)); 
    sl->head = 0; 
    return &sl->base; 
} 

Le implementazioni dello stack ope le razioni farebbero semplicemente il struct Stack * a quello che sa dovrebbe essere. Ad esempio:

static int stack_array_empty (struct Stack *s) { 
    struct StackArray *sa = (void *)s; 
    return sa->idx == 0; 
} 

static int stack_list_empty (struct Stack *s) { 
    struct StackList *sl = (void *)s; 
    return sl->head == 0; 
} 

Quando un utente di una pila invoca un'operazione pila sul stack di istanza, l'operazione invierà alla corrispondente operazione nel vtable. Questo vtable viene inizializzato dalla funzione di creazione con le funzioni corrispondenti alla sua particolare implementazione. Quindi:

Stack *s1 = stack_array_create(); 
Stack *s2 = stack_list_create(); 

stack_push(s1, 1); 
stack_push(s2, 1); 

stack_push() è chiamato sia s1 e s2. Ma, per s1, che invierà a stack_array_push(), mentre per s2, che invierà a stack_list_push().

+2

+1 Ottima risposta! –

+1

Inoltre, il membro 'vtable' è in genere' const' e punta a una memoria costante (se MMU/MPU è presente e OS fa la cosa giusta, ovviamente). –

0

Sono un po 'sorpreso che nessuno ha aggiunto glib e/o tutta la roba gtk come esempio. Quindi, per favore controlla: http://www.gtk.org/features.php

Sono consapevole che è piuttosto un codice di codice che devi avere per usare gtk e non è così "facile" avere la giusta volta per la prima volta. Ma se uno l'ha usato è notevole. L'unica cosa che devi ricordare è usare un tipo di "Oggetto" come primo parametro per le tue funzioni. Ma se guardi attraverso l'API, lo vedi che è usato ovunque. IMHO che fa davvero un buon esempio sui meriti e dei problemi si può avere con OOP-