Come si scriverebbe un codice orientato agli oggetti in C? [chiuso]


500

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 .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Programmazione orientata agli oggetti in C </a> di Laurent Deniau


25
@Camilo Martin: ho volutamente chiesto posso non dovrebbe . In realtà non sono interessato all'utilizzo di OOP in C. Tuttavia, vedendo le soluzioni OO in C, possiamo imparare di più sui limiti e / o sulla flessibilità di C e anche sui modi creativi per implementare e utilizzare il polimorfismo.
Dinah,

5
OO è solo uno schema. Controlla qui, può anche essere fatto in .bat File: dirk.rave.org/chap9.txt (qualsiasi modello può essere applicato a qualsiasi linguaggio di programmazione se sei abbastanza interessato, penso). Questo è comunque un buon spunto di riflessione. E probabilmente si può imparare molto dall'applicazione di tali schemi che diamo per scontati su lingue che non li hanno.
Camilo Martin,

6
GTK - 'scuse me, GObject - è in realtà un ottimo esempio di OOP (sorta) in C. Quindi, per rispondere a @Cilo, per l'interpoliabilità in C.
nuovo123456,

Risposte:


362

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.


28
Mentre i concetti di questo libro sono solidi, perderai la sicurezza del tipo.
diapir,

22
Prima di quelli che conosciamo come modelli di progettazione, era il modello di progettazione noto come "orientamento dell'oggetto"; lo stesso con la garbage collection e altri simili. Sono così radicati ora, tendiamo a dimenticare, quando sono stati concepiti per la prima volta, era più o meno allo stesso modo di quello che oggi pensiamo come modelli di design
Dexygen

11
Puoi ottenerlo direttamente dal sito dell'autore: cs.rit.edu/~ats/books/ooc.pdf altri documenti dello stesso autore: cs.rit.edu/~ats/books/index.html
pakman

10
La raccolta corretta (libro + esempi di codice sorgente) è disponibile da questo indice rit.edu Programmazione orientata agli oggetti con ANSI-C
David C. Rankin

3
Questo libro è stato sottoposto a peer review? C'è un refuso nella prima frase del primo paragrafo della prima pagina.
Dagrooms,

343

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 structper 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.


52
L'incapsulamento è piuttosto semplice, il polimorfismo è fattibile - ma l'eredità è complicata
Martin Beckett

5
lwn.net ha recentemente pubblicato un articolo intitolato Pattern di design orientato agli oggetti nel kernel sull'argomento di stuct simili alla risposta precedente - ovvero, una struttura contenente puntatori di funzione o un puntatore a una struttura che ha funzioni che portano un puntatore al strutt con i dati con cui stiamo lavorando come parametro.
radicalmatt,

11
+1 bell'esempio! Sebbene se qualcuno vuole davvero percorrere questa strada, sarebbe più appropriato che le strutture "di istanza" abbiano un singolo campo che punta alla loro istanza di "tabella virtuale", contenente tutte le funzioni virtuali per quel tipo in un unico posto. Vale a dire che il tuo tCommClassverrebbe rinominato tCommVT, e una tCommClassstruttura avrebbe solo campi dati e un singolo tCommVT vtcampo 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.
Groo,

1
Quindi questo dimostra l'implementazione di una singola interfaccia, ma di cosa dovresti implementare più interfacce? O eredità multipla?
weberc2,

Weber, se vuoi tutte le funzionalità di C ++, probabilmente dovresti usare C ++. La domanda posta specificamente sul polimorfismo, la capacità degli oggetti di assumere una "forma" diversa. Puoi certamente fare interfacce e ereditarietà multipla in C, ma è un bel po 'di lavoro extra e devi gestire tu stesso le intelligenze invece di usare roba incorporata in C ++.
paxdiablo,

86

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 *


1
@nategoose - st->my_type->push(st, thing2);anzichést->my_type.push(st, thing2);
Fabricio il

@nategoose: OPPURE struct stack_type my_type; anzichéstruct stack_type * my_type;
Fabricio il

3
Mi piace l'idea di avere una struttura per la classe. Ma che ne dici di una Classstruttura generica ? Ciò renderebbe OO C più dinamico di C ++. Che ne dici di quello? A proposito, +1.
Linuxios,

54

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


4
Nuovo URL per il manuale C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

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 ++.


5
"Alla fine, finisci per riscrivere C ++" Mi chiedevo se / temessi che fosse il caso.
Dinah,

39
Oppure potresti riscrivere l'obiettivo C, che sarebbe un risultato molto più attraente.
contratto del Prof. Falken è stato violato il

3
C'è il sapore senza classi di OOP, come in Javascript , in cui il guru dice: "Non abbiamo bisogno di classi per creare molti oggetti simili". Ma temo che non sia facile da raggiungere in C. Non (ancora) in grado di dirlo, però. (Esiste una routine clone () per clonare una struttura?)
Lumi,

1
Un altro ragazzo intelligente, che ha dovuto implementarlo e velocizzare l' implementazione (Google, motore V8) ha fatto di tutto per aggiungere classi (nascoste) a JavaScript.
cubuspl42,

Non è glibscritto in C in modo obiettivo?
kravemir,

26

Sicuro che è possibile. Questo è ciò su cui GObject , il framework su cui si basano tutti GTK + e GNOME .


Quali sono i pro / contro di tale approccio? Vale a dire. è molto più facile scriverlo usando C ++.
kravemir,

@kravemir Bene, C ++ non è portatile come C, ed è un po 'più difficile collegare C ++ a codice che potrebbe essere compilato da un altro compilatore C ++. Ma sì, è più facile scrivere classi in C ++, anche se GObject non è poi così difficile (supponendo che non ti dispiaccia una piccola piastra di caldaia).
Edwin Buck,

17

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.


Lo stdio non è astratto sul livello del kernel? Se non sbaglio, C-library li tratta come file / dispositivi di caratteri e i driver del kernel fanno il loro lavoro, ...
kravemir,

15

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 thispuntatore in giro.

Se dovessi svolgere funzioni non virtuali, è trival. Semplicemente non li aggiungi alla vtable e le funzioni statiche non richiedono un thispuntatore. 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.


typedefdentro a structnon è possibile in C.
masoud

13

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.


12

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.


10

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_sendPer 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.


10

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 thisnei 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.


6
Se typedef struct FOO_type FOO_typeinvece di un typedef si annulla l'intestazione, si ottiene il vantaggio aggiuntivo del controllo del tipo, pur non esponendo la struttura.
Scott Wales,

8

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.


5
Il primissimo compilatore C ++ ha fatto esattamente questo: ha convertito il codice C ++ in codice C equivalente (ma brutto e non leggibile dall'uomo), che è stato quindi compilato dal compilatore C.
Adam Rosenfield,

2
EDG, Cfront e alcuni altri sono ancora in grado di farlo. Con un'ottima ragione: non tutte le piattaforme hanno un compilatore C ++.
Jasper Bekkers,

Per qualche ragione ho pensato che C-front supportava solo alcune estensioni C ++ (ad esempio riferimenti) ma non l'emulazione OOP / invio dinamico completo.
Uri,

2
Puoi anche fare la stessa cosa con LLVM e il backend C.
Zifre,

7

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.


3
se Linus vede il tuo commento. Sicuramente ti riderà o ti maledirà
Anders Lind,

7

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


36
Non rispondo davvero alla domanda ...
Brian Postow,

2
@Brian, il link al PDF sembrerebbe rispondere direttamente alla domanda, anche se non ho avuto il tempo di controllare da solo.
Mark Ransom,

5
Il link al PDF sembra essere un intero libro di testo sull'argomento ... Una bella prova, ma non si adatta al margine ...
Brian Postow,

5
si, rispondi alla domanda. è perfettamente valido chiedere come usare una lingua in un modo particolare. non è stato richiesto il parere su altre lingue ....
Tim Ring

9
@Brian & Tim Ring: la domanda per consigli sui libri su un argomento; Gli ho dato un link a un libro che affronta in modo specifico questo argomento. Ho anche espresso la mia opinione sul perché l'approccio al problema potrebbe non essere ottimale (cosa che penso che molte persone qui sembrano concordare, sulla base di voti e altri commenti / risposte). Hai qualche suggerimento per migliorare la mia risposta?
RarrRarrRarr

6

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

6

Certo, non sarà bello come usare una lingua con supporto integrato. Ho anche scritto "assemblatore orientato agli oggetti".


6

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

5

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

OOC-GCC


le CLASS STATIC ASM NEW DELETE ST ... sono macro nel OOC-GCC
dameng



4

OOP è solo un paradigma che pone i dati più importanti del codice nei programmi. OOP non è una lingua. Quindi, come C semplice è un linguaggio semplice, OOP in C semplice è anche semplice.


3
Ben detto, ma questo dovrebbe essere un commento.
pqsk,

4

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.


4

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/ .


4

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.


Per ereditarietà, in genere la classe di base o la struttura di istanza è incorporata in quella derivata, non allocata separatamente e riferita usando i puntatori. In questo modo la base più in alto è sempre all'inizio di una qualsiasi delle strutture dei suoi tipi derivati, in modo che possano essere facilmente fusi l'uno con l'altro, cosa che non si può fare con puntatori che potrebbero essere in qualsiasi offset.
underscore_d,

2

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

2

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:

  • Evita i dattiloscritti il ​​più possibile, quindi non perdiamo la sicurezza dei tipi;
  • Polimorfismo: dovremmo essere in grado di usare metodi virtuali e l'utente della classe non dovrebbe sapere se un determinato metodo è virtuale o no;
  • Eredità multipla: non la uso spesso, ma a volte voglio davvero che una classe implementi più interfacce (o estenda più superclassi).

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.


2

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.


1

Propongo di usare Objective-C, che è un superset di C.

Mentre Objective-C ha 30 anni, consente di scrivere codice elegante.

http://en.wikipedia.org/wiki/Objective-C


In tal caso, consiglierei invece C ++ dato che in realtà è orientato agli oggetti ...
yyny,

Questa non è una risposta Ma comunque, @YoYoYonnY: non uso Objective-C e non uso C ++, ma commenti del genere non servono a nulla senza base e non ne hai fornito nessuno. Perché affermi che Objective-C non è "orientato agli oggetti ..."? E perché C ++ ha successo laddove Objective-C fallisce? La cosa divertente è che Objective-C, beh, ha letteralmente la parola Object nel suo nome, mentre C ++ si commercializza come un linguaggio multi-paradigma, non uno OOP (cioè non principalmente OOP, e in una visione piuttosto estrema della gente non OOP affatto) ... quindi sei sicuro di non aver preso quei nomi nel modo sbagliato?
underscore_d

0

Sì, ma non ho mai visto nessuno tentare di implementare alcun tipo di polimorfismo con C.


6
Devi guardarti intorno :) Ad esempio, Direct X di Microsoft ha un'interfaccia C polimorfica.
AShelly,

8
Guarda l'implementazione del kernel linux per esempio. È pratica molto diffusa e ampiamente usata in C.
Ilya,

3
anche glib è polimorfico, o può essere usato in un modo che consente il polimorfismo (è come il C ++ devi dire esplicitamente quali chiamate sono virtuali)
Spudd86

1
Il polimorfismo non è così raro in C, d'altra parte lo è l'eredità multipla.
Johan Bjäreholt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.