Question Pouvez-vous écrire du code orienté objet en C? [fermé]


Pouvez-vous écrire du code orienté objet en C? Surtout en ce qui concerne le polymorphisme.


Voir aussi Stack Overflow question Orientation objet en C.


421


origine


Réponses:


Oui. En fait, Axel Schreiner fournit son livre "Programmation orientée objet en ANSI-C" gratuitement qui couvre le sujet assez complètement.


315



Puisque vous parlez de polymorphisme, alors oui, vous le pouvez, nous faisions ce genre de choses des années avant le lancement de C ++.

Fondamentalement, vous utilisez un struct conserver à la fois les données et une liste de pointeurs de fonction pour pointer vers les fonctions pertinentes pour ces données.

Donc, dans une classe de communication, vous auriez un appel ouvert, en lecture, en écriture et en fermeture qui serait maintenu sous la forme de quatre pointeurs de fonction dans la structure, à côté des données d'un objet, comme:

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;

Bien sûr, ces segments de code ci-dessus seraient en fait dans un "constructeur" tel que rs232Init().

Lorsque vous «héritez» de cette classe, vous changez simplement les pointeurs pour pointer vers vos propres fonctions. Tous ceux qui appelaient ces fonctions le faisaient via les pointeurs de fonction, ce qui vous donnait votre polymorphisme:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Un peu comme un vtable manuel.

Vous pourriez même avoir des classes virtuelles en définissant les pointeurs sur NULL - le comportement serait légèrement différent de C ++ (un core dump au moment de l'exécution plutôt qu'une erreur au moment de la compilation).

Voici un exemple de code qui le démontre. D'abord la structure de classe de premier niveau:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

Ensuite, nous avons les fonctions pour la 'sous-classe' 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;
}

Et le HTTP aussi:

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

Et enfin un programme de test pour le montrer en action:

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

Cela produit la sortie:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

vous pouvez donc voir que les différentes fonctions sont appelées, en fonction de la sous-classe.


293



Les espaces de noms sont souvent faits en faisant:

stack_push(thing *)

au lieu de

stack::push(thing *)

Faire un C struct en quelque chose comme un C ++ classe que vous pouvez transformer:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

Dans

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

Et fait:

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
}

Je n'ai pas fait le destructeur ou supprimer, mais il suit le même schéma.

this_is_here_as_an_example_only est comme une variable de classe statique - partagée entre toutes les instances d'un type. Toutes les méthodes sont vraiment statiques, sauf que certaines prennent ceci *


77



Je crois qu'en plus d'être utile en soi, la mise en œuvre de la POO en C est un excellent moyen de apprendre OOP et comprendre son fonctionnement interne. L'expérience de nombreux programmeurs a montré que pour utiliser une technique de manière efficace et en toute confiance, un programmeur doit comprendre comment les concepts sous-jacents sont finalement mis en œuvre. L'émulation de classes, l'héritage et le polymorphisme dans C enseigne juste cela.

Pour répondre à la question originale, voici quelques ressources qui enseignent comment faire de la POO en C:

Article de blog EmbeddedGurus.com "La programmation par objets en C" montre comment implémenter des classes et un héritage unique dans le C portable: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/

Note d'application "" C + "- Programmation orientée objet en C" montre comment implémenter les classes, l'héritage simple et la liaison tardive (polymorphisme) en C en utilisant des macros de préprocesseur: http://www.state-machine.com/resources/cplus_3.0_manual.pdf, le code d'exemple est disponible à partir de http://www.state-machine.com/resources/cplus_3.0.zip


49



Je l'ai vu fait. Je ne le recommanderais pas. À l'origine, C ++ a commencé comme préprocesseur qui a produit le code C en tant qu'étape intermédiaire.

Essentiellement ce que vous finissez par faire est de créer une table d'expédition pour toutes vos méthodes où vous stockez vos références de fonction. Dériver une classe impliquerait de copier cette table de distribution et de remplacer les entrées que vous souhaitiez remplacer, vos nouvelles méthodes devant appeler la méthode d'origine si elle veut appeler la méthode de base. Finalement, vous finissez par réécrire C ++.


30



Bien sûr que c'est possible. C'est quoi GObject, le cadre que tous GTK + et GNOME est basé sur, fait.


24



La sous-bibliothèque C stdio FILE est un excellent exemple de la façon de créer de l'abstraction, de l'encapsulation et de la modularité dans un C. non-adultéré.

L'héritage et le polymorphisme - les autres aspects souvent considérés comme essentiels à la POO - ne fournissent pas nécessairement les gains de productivité qu'ils promettent et raisonnable  arguments avoir a été fait qu'ils peuvent réellement entraver le développement et la réflexion sur le domaine du problème.


17