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()
.
È 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
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. –
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