Comment écrirait-on du code orienté objet en C? [fermé]


500

Quels sont les moyens d'écrire du code orienté objet en C? Surtout en ce qui concerne le polymorphisme.


Voir aussi cette question Stack Overflow orienté objet en C .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> objet programmation orientée objet en C </a> par Laurent Deniau


25
@Camilo Martin: Je lui ai demandé intentionnellement peut ne devrait . Je ne suis pas vraiment intéressé par l'utilisation de la POO en C. Cependant, en voyant les solutions OO en C, je / nous nous tenons à en savoir plus sur les limites et / ou la flexibilité de C et également sur les façons créatives de mettre en œuvre et d'utiliser le polymorphisme.
Dinah

5
OO n'est qu'un modèle. Vérifiez ici, cela peut même être fait dans les fichiers .bat: dirk.rave.org/chap9.txt (n'importe quel modèle peut être appliqué à n'importe quel langage de programmation si vous êtes assez intéressé, je pense). C'est une bonne matière à réflexion, cependant. Et il y a probablement beaucoup à apprendre de l'application de tels modèles que nous tenons pour acquis sur les langues qui n'en ont pas.
Camilo Martin

6
GTK - 'scuse me, GObject - est en fait un assez bon exemple de POO (sorta) en C. Donc, pour répondre à @Camilo, pour l'interpoliabilité C.
new123456

Réponses:


362

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


28
Bien que les concepts de ce livre soient des solides, vous perdrez la sécurité du type.
diapir

22
Avant ce que nous appelons les modèles de conception, le modèle de conception était connu sous le nom d '"orientation d'objet"; même avec la collecte des ordures et autres. Ils sont tellement ancrés maintenant, nous avons tendance à oublier, lorsqu'ils ont été conçus pour la première fois, c'était à peu près de la même manière que ce que nous considérons aujourd'hui comme des modèles de conception
Dexygen

11
Vous pouvez l'obtenir directement sur le site de l'auteur: cs.rit.edu/~ats/books/ooc.pdf autres articles du même auteur: cs.rit.edu/~ats/books/index.html
pakman

10
La collection appropriée (livre + exemples de code source) est disponible à partir de cet index rit.edu Programmation orientée objet avec ANSI-C
David C. Rankin

3
Ce livre est-il évalué par des pairs? Il y a une faute de frappe dans la première phrase du premier paragraphe de la première page.
Dagrooms

343

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

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

Ainsi, dans une classe de communication, vous auriez un appel ouvert, lu, écrit et fermé qui serait maintenu comme quatre pointeurs de fonction dans la structure, à côté des données pour un objet, quelque chose 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 ont appelé ces fonctions le feraient via les pointeurs de fonction, vous donnant votre polymorphisme:

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

Un peu comme une table manuelle.

Vous pouvez même avoir des classes virtuelles en définissant les pointeurs sur NULL - le comportement serait légèrement différent de C ++ (un vidage de mémoire 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. Tout d'abord la structure de classe de niveau supérieur:

#include <stdio.h>

// The top-level class.

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

Ensuite, nous avons les fonctions de 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, selon la sous-classe.


52
L'encapsulation est assez facile, le polymorphisme est faisable - mais l'héritage est délicat
Martin Beckett

5
lwn.net a récemment publié un article intitulé Patterns de conception orientée objet dans le noyau au sujet de structures similaires à la réponse ci-dessus - c'est-à-dire une structure contenant des pointeurs de fonction, ou un pointeur vers une structure qui a des fonctions qui prennent un pointeur vers la struct avec les données avec lesquelles nous travaillons comme paramètre.
radicalmatt

11
+1 Bel exemple! Bien que si quelqu'un veut vraiment suivre cette voie, il serait plus approprié que les structures "instance" aient un seul champ pointant vers leur instance "table virtuelle", contenant toutes les fonctions virtuelles pour ce type à un seul endroit. C'est-à-dire que votre tCommClassserait renommé tCommVT, et une tCommClassstructure n'aurait que des champs de données et un seul tCommVT vtchamp pointant vers la "seule et unique" table virtuelle. Porter tous les pointeurs autour de chaque instance ajoute une surcharge inutile et ressemble plus à la façon dont vous feriez des choses en JavaScript qu'à C ++, à mon humble avis.
Groo

1
Cela démontre donc la mise en œuvre d'une interface unique, mais qu'en est-il de la mise en œuvre de plusieurs interfaces? Ou l'héritage multiple?
weberc2

Weber, si vous voulez toutes les fonctionnalités de C ++, vous devriez probablement utiliser C ++. La question portait spécifiquement sur le polymorphisme, la capacité des objets à prendre une "forme" différente. Vous pouvez certainement faire des interfaces et des héritages multiples en C, mais c'est un peu de travail supplémentaire, et vous devez gérer vous-même l'intelligence plutôt que d'utiliser des éléments intégrés C ++.
paxdiablo

86

Les espaces de noms se font souvent en faisant:

stack_push(thing *)

au lieu de

stack::push(thing *)

Pour transformer une structure C en quelque chose comme une classe C ++ , vous pouvez activer:

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 ni supprimé, 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 *


1
@nategoose - st->my_type->push(st, thing2);au lieu dest->my_type.push(st, thing2);
Fabricio

@nategoose: OU struct stack_type my_type; au lieu destruct stack_type * my_type;
Fabricio

3
J'aime le concept d'avoir une structure pour la classe. Mais que diriez-vous d'une Classstructure générique ? Cela rendrait l'OO C plus dynamique que C ++. Et ça? Au fait, +1.
Linuxios

54

Je crois qu'en plus d'être utile en soi, la mise en œuvre de la POO en C est un excellent moyen d' apprendre la POO et de 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 des classes, de l'héritage et du polymorphisme en C enseigne exactement cela.

Pour répondre à la question d'origine, voici quelques ressources qui enseignent comment faire la POO en C:

Le billet de blog d'EmbeddedGurus.com "Programmation basée sur les objets en C" montre comment implémenter les classes et l'héritage unique dans le C portable: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

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


4
Nouvelle URL pour le manuel C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

Je l'ai vu faire. Je ne le recommanderais pas. C ++ a commencé à l'origine de cette façon en tant que préprocesseur qui a produit du code C comme étape intermédiaire.

Essentiellement, vous finissez par créer une table de répartition pour toutes vos méthodes où vous stockez vos références de fonction. Dériver une classe impliquerait de copier cette table de répartition et de remplacer les entrées que vous vouliez remplacer, vos nouvelles "méthodes" devant appeler la méthode d'origine si elle voulait invoquer la méthode de base. Finalement, vous finissez par réécrire C ++.


5
"Finalement, vous finissez par réécrire C ++" Je me demandais si / craignais que ce soit le cas.
Dinah

39
Ou, vous pourriez finir par réécrire l'objectif C, ce qui serait un résultat beaucoup plus attrayant.
contrat du professeur Falken a été rompu

3
Il y a la saveur sans classe de la POO, comme en Javascript , où le gourou dit: "Nous n'avons pas besoin de classes pour faire beaucoup d'objets similaires." Mais je crains que ce ne soit pas facile à réaliser en C. Pas (encore) en mesure de le dire, cependant. (Existe-t-il une routine clone () pour cloner une structure?)
Lumi

1
Un autre gars intelligent, qui devait réellement implémenter cela et rendre cette implémentation rapide (Google, moteur V8) a tout fait pour ajouter des classes (cachées) à JavaScript.
cubuspl42

N'est-il pas glibécrit en C de manière objective?
kravemir

26

Bien sûr que c'est possible. C'est ce que fait GObject , le framework sur lequel GTK + et GNOME sont basés.


Quels sont les avantages / inconvénients d'une telle approche? C'est à dire. il est beaucoup plus facile de l'écrire en C ++.
kravemir

@kravemir Eh bien, C ++ n'est pas aussi portable que C, et il est un peu plus difficile de lier C ++ à du code qui pourrait être compilé par un compilateur C ++ différent. Mais oui, il est plus facile d'écrire des classes en C ++, bien que GObject ne soit pas vraiment difficile non plus (en supposant que cela ne vous dérange pas une petite plaque de chaudière).
Edwin Buck

17

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

L'héritage et le polymorphisme - les autres aspects souvent considérés comme essentiels à la POO - n'apportent pas nécessairement les gains de productivité qu'ils promettent et des arguments raisonnables ont été avancés selon lesquels ils peuvent réellement entraver le développement et la réflexion sur le domaine problématique.


Stdio n'est-il pas abstrait sur la couche noyau? Si je ne me trompe pas, la bibliothèque C les traite comme des fichiers / périphériques de caractères, et les pilotes du noyau font le travail, ...
kravemir

15

Exemple trivial avec un animal et un chien: vous reflétez le mécanisme vtable de C ++ (en grande partie de toute façon). Vous séparez également l'allocation et l'instanciation (Animal_Alloc, Animal_New) afin que nous n'appelions pas malloc () plusieurs fois. Nous devons également passer explicitement le thispointeur.

Si vous deviez faire des fonctions non virtuelles, c'est trival. Vous ne les ajoutez tout simplement pas aux fonctions vtable et statiques ne nécessitent pas de thispointeur. L'héritage multiple nécessite généralement plusieurs vtables pour résoudre les ambiguïtés.

De plus, vous devriez pouvoir utiliser setjmp / longjmp pour gérer les exceptions.

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. Ceci est testé sur un compilateur C ++, mais il devrait être facile de le faire fonctionner sur un compilateur C.


typedefl'intérieur d'un structn'est pas possible en C.
masoud

13

Découvrez GObject . Il est censé être OO en C et une implémentation de ce que vous recherchez. Si vous voulez vraiment OO, optez pour C ++ ou un autre langage OOP. Il peut être parfois difficile de travailler avec GObject si vous avez l'habitude de gérer les langages OO, mais comme tout, vous vous habituerez aux conventions et au flux.


12

Cela a été intéressant à lire. J'ai moi-même réfléchi à la même question, et les avantages d'y penser sont les suivants:

  • Essayer d'imaginer comment implémenter des concepts OOP dans un langage non-OOP m'aide à comprendre les points forts du langage OOp (dans mon cas, C ++). Cela m'aide à mieux juger de l'utilisation du C ou du C ++ pour un type d'application donné - où les avantages de l'un l'emportent sur l'autre.

  • Dans ma navigation sur le Web pour obtenir des informations et des opinions à ce sujet, j'ai trouvé un auteur qui écrivait du code pour un processeur intégré et n'avait qu'un compilateur C disponible: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Création-Fondation-Classes-Part-1

Dans son cas, l'analyse et l'adaptation des concepts de POO en simple C était une poursuite valable. Il semble qu'il était prêt à sacrifier certains concepts de POO en raison du dépassement des performances résultant de la tentative de les implémenter en C.

La leçon que j'ai tirée est, oui, cela peut être fait dans une certaine mesure, et oui, il y a de bonnes raisons de l'essayer.

En fin de compte, la machine tourne des bits de pointeur de pile, faisant sauter le compteur de programme et calculant les opérations d'accès à la mémoire. Du point de vue de l'efficacité, moins ces calculs sont effectués par votre programme, mieux c'est ... mais parfois nous devons payer cette taxe simplement pour pouvoir organiser notre programme de manière à le rendre moins vulnérable aux erreurs humaines. Le compilateur de langage OOP s'efforce d'optimiser les deux aspects. Le programmeur doit être beaucoup plus prudent lors de la mise en œuvre de ces concepts dans un langage comme C.


10

Il peut être utile de consulter la documentation d'Apple pour son ensemble d'API Core Foundation. Il s'agit d'une pure API C, mais de nombreux types sont pontés vers des équivalents d'objet Objective-C.

Vous pouvez également trouver utile de regarder la conception d'Objective-C lui-même. C'est un peu différent de C ++ en ce que le système objet est défini en termes de fonctions C, par exemple objc_msg_sendpour appeler une méthode sur un objet. Le compilateur traduit la syntaxe entre crochets en ces appels de fonction, vous n'avez donc pas besoin de la connaître, mais compte tenu de votre question, vous trouverez peut-être utile d'apprendre comment cela fonctionne sous le capot.


10

Il existe plusieurs techniques qui peuvent être utilisées. Le plus important est davantage de savoir comment diviser le projet. Nous utilisons une interface dans notre projet qui est déclarée dans un fichier .h et l'implémentation de l'objet dans un fichier .c. La partie importante est que tous les modules qui incluent le fichier .h ne voient qu'un objet en tant que a void *, et le fichier .c est le seul module qui connaît les éléments internes de la structure.

Quelque chose comme ça pour une classe que nous nommons FOO comme exemple:

Dans le fichier .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

Le fichier d'implémentation C sera quelque chose comme ça.

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

Je donne donc le pointeur explicitement à un objet pour chaque fonction de ce module. Un compilateur C ++ le fait implicitement, et en C nous l'écrivons explicitement.

J'utilise vraiment thisdans mes programmes, pour m'assurer que mon programme ne compile pas en C ++, et il a la belle propriété d'être dans une autre couleur dans mon éditeur de coloration syntaxique.

Les champs du FOO_struct peuvent être modifiés dans un module et un autre module n'a même pas besoin d'être recompilé pour être toujours utilisable.

Avec ce style, je gère déjà une grande partie des avantages de la POO (encapsulation de données). En utilisant des pointeurs de fonction, il est même facile d'implémenter quelque chose comme l'héritage, mais honnêtement, ce n'est vraiment que rarement utile.


6
Si vous le faites à la typedef struct FOO_type FOO_typeplace d'un typedef à annuler dans l'en-tête, vous obtenez l'avantage supplémentaire de la vérification de type, tout en n'exposant pas votre structure.
Scott Wales

8

Vous pouvez le simuler en utilisant des pointeurs de fonction, et en fait, je pense qu'il est théoriquement possible de compiler des programmes C ++ en C.

Cependant, il est rarement logique d'imposer un paradigme à une langue plutôt que de choisir une langue qui utilise un paradigme.


5
Le tout premier compilateur C ++ a fait exactement cela - il a converti le code C ++ en code C équivalent (mais laid et non lisible par l'homme), qui a ensuite été compilé par le compilateur C.
Adam Rosenfield

2
EDG, Cfront et quelques autres sont encore capables de le faire. Pour une très bonne raison: toutes les plateformes n'ont pas de compilateur C ++.
Jasper Bekkers

Pour une raison quelconque, je pensais que C-front ne supportait que certaines extensions C ++ (par exemple, les références) mais pas l'émulation OOP / répartition dynamique complète.
Uri

2
Vous pouvez également faire la même chose avec LLVM et le backend C.
Zifre

7

C orienté objet, peut être fait, j'ai vu ce type de code en production en Corée, et c'était le monstre le plus horrible que j'avais vu depuis des années (c'était comme l'année dernière (2007) que j'ai vu le code). Alors oui, cela peut être fait, et oui, les gens l'ont déjà fait, et le font encore de nos jours. Mais je recommanderais C ++ ou Objective-C, les deux sont des langages nés de C, dans le but de fournir une orientation d'objet avec différents paradigmes.


3
si Linus voit votre commentaire. Il va certainement rire ou vous maudire
Anders Lind

7

Si vous êtes convaincu qu'une approche POO est supérieure pour le problème que vous essayez de résoudre, pourquoi tenteriez-vous de le résoudre avec un langage non POO? Il semble que vous n'utilisiez pas le bon outil pour le travail. Utilisez C ++ ou un autre langage variant C orienté objet.

Si vous demandez parce que vous commencez à coder sur un grand projet déjà existant écrit en C, alors vous ne devriez pas essayer de forcer vos propres paradigmes de POO (ou ceux de quelqu'un d'autre) dans l'infrastructure du projet. Suivez les directives qui sont déjà présentes dans le projet. En général API, propres et les bibliothèques et les modules isolés vont un long chemin vers avoir un OOP- propre ish design.

Si, après tout cela, vous êtes vraiment prêt à faire de la POO C, lisez ceci (PDF).


36
Ne répond pas vraiment à la question ...
Brian Postow

2
@Brian, le lien vers le PDF semble répondre directement à la question, même si je n'ai pas eu le temps de vérifier par moi-même.
Mark Ransom

5
Le lien vers le PDF semble être un manuel complet sur le sujet ... Une belle preuve, mais elle ne rentre pas dans la marge ...
Brian Postow

5
oui, réponds à la question. il est parfaitement valable de demander comment utiliser une langue d'une manière particulière. il n'y a eu aucune demande d'avis sur d'autres langues ....
Tim Ring

9
@Brian & Tim Ring: La question demandait des recommandations de livres sur un sujet; Je lui ai donné un lien vers un livre qui traite spécifiquement de ce sujet. J'ai également donné mon avis sur les raisons pour lesquelles l'approche du problème peut ne pas être optimale (ce que je pense que beaucoup de gens ici semblent être d'accord, sur la base des votes et d'autres commentaires / réponses). Avez-vous des suggestions pour améliorer ma réponse?
RarrRarrRarr

6

Oui, vous pouvez. Les gens écrivaient du C orienté objet avant que C ++ ou Objective-C n'entrent en scène. C ++ et Objective-C étaient, en partie, des tentatives de prendre certains des concepts OO utilisés en C et de les formaliser dans le cadre du langage.

Voici un programme très simple qui montre comment vous pouvez créer quelque chose qui ressemble à / est un appel de méthode (il existe de meilleures façons de le faire. C'est juste la preuve que le langage prend en charge les concepts):

#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

Bien sûr, ce ne sera pas aussi joli que d'utiliser un langage avec un support intégré. J'ai même écrit "assembleur orienté objet".


6

Un petit code OOC à ajouter:

#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

Je creuse ça depuis un an:

Comme le système GObject est difficile à utiliser avec du C pur, j'ai essayé d'écrire de belles macros pour faciliter le style OO avec 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;
}

Voici mon site de projet (je n'ai pas assez de temps pour écrire en. Doc, cependant la doc en chinois est bien meilleure).

OOC-GCC


les CLASS STATIC ASM NEW DELETE ST ... sont des macros dans l'OOC-GCC
dameng


4

Quels articles ou livres sont bons pour utiliser les concepts de POO en C?

Les interfaces et implémentations C de Dave Hanson sont excellentes pour l'encapsulation et la dénomination et très bonnes pour l'utilisation des pointeurs de fonction. Dave n'essaie pas de simuler l'héritage.


4

La POO n'est qu'un paradigme qui place les données plus importantes que le code dans les programmes. La POO n'est pas une langue. Ainsi, comme le C simple est un langage simple, la POO en C simple est également simple.


3
Bien dit, mais cela devrait être un commentaire.
pqsk

4

Une chose que vous voudrez peut-être faire est d'examiner la mise en œuvre de la boîte à outils Xt pour X Window . Bien sûr, cela prend du temps dans la dent, mais la plupart des structures utilisées ont été conçues pour fonctionner de manière OO dans le C. traditionnel.Généralement, cela signifie ajouter une couche supplémentaire d'indirection ici et là et concevoir des structures à superposer.

Vous pouvez vraiment faire beaucoup de choses sur la façon dont OO situé en C de cette façon, même si cela en a parfois l'air, les concepts OO ne sont pas complètement nés de l'esprit de #include<favorite_OO_Guru.h>. Ils constituaient vraiment bon nombre des meilleures pratiques établies de l'époque. Les langages et systèmes OO ne distillent et n'amplifient que des parties de la programmation du jour.


4

La réponse à la question est «Oui, vous le pouvez».

Le kit C orienté objet (OOC) est destiné à ceux qui souhaitent programmer de manière orientée objet, mais il colle également au bon vieux C. OOC implémente les classes, l'héritage simple et multiple, la gestion des exceptions.

Caractéristiques

• Utilise uniquement des macros et des fonctions C, aucune extension de langue requise! (ANSI-C)

• Code source facile à lire pour votre application. On a pris soin de rendre les choses aussi simples que possible.

• Héritage unique des classes

• Héritage multiple par interfaces et mixins (depuis la version 1.3)

• Implémentation d'exceptions (en C pur!)

• Fonctions virtuelles pour les classes

• Outil externe pour une implémentation de classe facile

Pour plus de détails, visitez http://ooc-coding.sourceforge.net/ .


4

Il semble que les gens essaient d'émuler le style C ++ en utilisant C. Mon point de vue est que faire de la programmation orientée objet C fait vraiment de la programmation orientée struct. Cependant, vous pouvez réaliser des choses comme la liaison tardive, l'encapsulation et l'héritage. Pour l'héritage, vous définissez explicitement un pointeur vers les structures de base dans votre sous-structure et c'est évidemment une forme d'héritage multiple. Vous devrez également déterminer si votre

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

compiler avec c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Le conseil est donc de s'en tenir à un style C pur et de ne pas essayer de forcer dans un style C ++. De plus, cette méthode se prête à une manière très propre de créer une API.


Pour l'héritage, la classe de base ou la structure d'instance est généralement intégrée à celle dérivée, non allouée séparément et référencée à l'aide de pointeurs. De cette façon, la base la plus haute est toujours au début de n'importe quelle structure de ses types dérivés, de sorte qu'ils peuvent être castés les uns aux autres avec facilité, ce que vous ne pouvez pas faire avec des pointeurs qui pourraient être à n'importe quel décalage.
underscore_d

2

Voir http://slkpg.byethost7.com/instance.html pour encore une autre torsion sur la POO en C. Il met l'accent sur les données d'instance pour la réentrance en utilisant uniquement le C. natif. L'héritage multiple se fait manuellement à l'aide de wrappers de fonction. La sécurité du type est maintenue. Voici un petit échantillon:

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

Je suis un peu en retard à la fête, mais je veux partager mon expérience sur le sujet: je travaille avec des trucs embarqués de nos jours, et le seul compilateur (fiable) que j'ai est C, donc je veux appliquer une approche orientée objet approche dans mes projets embarqués écrits en C.

La plupart des solutions que j'ai vues jusqu'à présent utilisent fortement les typecasts, nous perdons donc la sécurité des types: le compilateur ne vous aidera pas si vous faites une erreur. C'est totalement inacceptable.

Exigences que j'ai:

  • Évitez les typecasts autant que possible, afin de ne pas perdre la sécurité des types;
  • Polymorphisme: nous devrions pouvoir utiliser des méthodes virtuelles, et l'utilisateur de la classe ne devrait pas savoir si une méthode particulière est virtuelle ou non;
  • Héritage multiple: je ne l'utilise pas souvent, mais parfois je veux vraiment qu'une classe implémente plusieurs interfaces (ou étende plusieurs superclasses).

J'ai expliqué mon approche en détail dans cet article: Programmation orientée objet en C ; de plus, il existe un utilitaire pour la génération automatique de code passe-partout pour les classes de base et dérivées.


2

J'ai construit une petite bibliothèque où j'ai essayé ça et pour moi ça marche vraiment bien. J'ai donc pensé partager l'expérience.

https://github.com/thomasfuhringer/oxygen

L'héritage unique peut être implémenté assez facilement en utilisant une structure et en l'étendant pour toutes les autres classes enfants. Un simple cast vers la structure parent permet d'utiliser des méthodes parents sur tous les descendants. Tant que vous savez qu'une variable pointe vers une structure contenant ce type d'objet, vous pouvez toujours transtyper vers la classe racine et effectuer une introspection.

Comme cela a été mentionné, les méthodes virtuelles sont quelque peu plus délicates. Mais ils sont réalisables. Pour garder les choses simples, j'utilise simplement un tableau de fonctions dans la structure de description de classe que chaque classe enfant copie et repeuple les emplacements individuels si nécessaire.

L'héritage multiple serait plutôt compliqué à implémenter et aurait un impact significatif sur les performances. Je le laisse donc. Je considère qu'il est souhaitable et utile dans de nombreux cas de modéliser proprement les circonstances de la vie réelle, mais dans 90% des cas, l'héritage unique couvre probablement les besoins. Et l'héritage unique est simple et ne coûte rien.

De plus, je ne me soucie pas de la sécurité des types. Je pense que vous ne devriez pas dépendre du compilateur pour vous éviter des erreurs de programmation. Et cela ne vous protège de toute façon que d'une petite partie des erreurs.

En règle générale, dans un environnement orienté objet, vous souhaitez également implémenter le comptage des références pour automatiser autant que possible la gestion de la mémoire. J'ai donc également mis un décompte de références dans la classe racine «Object» et quelques fonctionnalités pour encapsuler l'allocation et la désallocation de la mémoire du tas.

Tout cela est très simple et léger et me donne l'essentiel d'OO sans me forcer à faire face au monstre qu'est le C ++. Et je conserve la flexibilité de rester en C land, ce qui facilite entre autres l'intégration de bibliothèques tierces.


1

Je propose d'utiliser Objective-C, qui est un surensemble de C.

Si Objective-C a 30 ans, il permet d'écrire du code élégant.

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


Dans ce cas, je recommanderais C ++ à la place, car il est en fait orienté objet ...
yyny

Ce n'est pas une réponse. Mais de toute façon, @YoYoYonnY: Je n'utilise pas Objective-C et j'utilise C ++, mais des commentaires comme celui-ci ne sont d'aucune utilité sans fondement, et vous n'en avez fourni aucun. Pourquoi prétendez-vous qu'Objective-C n'est pas "réellement orienté objet ..."? Et pourquoi C ++ réussit-il là où Objective-C échoue? Ce qui est drôle, c'est que Objective-C, bien, a littéralement le mot Object dans son nom, alors que C ++ se commercialise comme un langage multi-paradigme, pas un langage OOP (c.-à-d. Pas principalement OOP, et de l'avis de gens plutôt extrêmes, pas OOP du tout) ... alors êtes-vous sûr que vous n'avez pas obtenu ces noms dans le mauvais sens?
underscore_d

0

Oui, mais je n'ai jamais vu personne tenter d'implémenter un quelconque polymorphisme avec C.


6
Vous devez regarder plus loin :) Par exemple, Microsoft Direct X a une interface C polymorphe.
AShelly

8
Regardez par exemple l'implémentation du noyau Linux. C'est une pratique très courante et largement utilisée en C.
Ilya

3
glib est également polymorphe, ou peut être utilisé d'une manière qui permet le polymorphisme (c'est comme C ++, vous devez dire explicitement quels appels sont virtuels)
Spudd86

1
Le polymorphisme n'est pas si rare en C, en revanche l'hérédité multiple l'est.
Johan Bjäreholt
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.