Auparavant, je n'utilisais que des langages de programmation orientés objet (C ++, Ruby, Python, PHP) et j'apprends maintenant le C. J'ai du mal à trouver la bonne façon de faire les choses dans un langage sans concept 'Objet'. Je me rends compte qu'il est possible d'utiliser des paradigmes de POO en C, mais j'aimerais apprendre la méthode C-idiomatique.
Lors de la résolution d'un problème de programmation, la première chose que je fais est d'imaginer un objet qui résoudra le problème. Quelles étapes dois-je remplacer par ceci lors de l’utilisation d’un paradigme de programmation impérative non-POO?
object-oriented
c
Mas Bagol
la source
la source
qux = foo.bar(baz)
deviennentqux = Foo_bar(foo, baz)
.Réponses:
struct
.C'est ça.
Comment as-tu écrit un cours? C'est à peu près comment vous écrivez un fichier .C. Certes, vous n'obtenez pas des éléments comme le polymorphisme de méthode et l'héritage, mais vous pouvez quand même simuler ceux avec des noms de fonction et une composition différents .
Pour ouvrir la voie, étudiez la programmation fonctionnelle. C'est vraiment étonnant ce que l'on peut faire sans cours, et certaines choses fonctionnent mieux sans les frais généraux des cours.
Lectures
suggérées Orientation objet en ANSI C
la source
typedef
çastruct
et faire quelque chose de classe . Lestypedef
types et -ed peuvent être inclus dans d'autresstruct
s qui eux-mêmes peuvent êtretypedef
-ed. Ce que vous n’obtenez pas avec C, c’est la surcharge d’opérateurs et l’héritage superficiel et simple des classes et des membres qu’il contient en C ++. et vous n'obtenez pas beaucoup de syntaxe étrange et contre nature que vous obtenez avec C ++. J'aime vraiment le concept de la programmation orientée objet, mais je pense que C ++ est une réalisation laide de la programmation orientée objet. i comme C , car il est une langue plus petite et les feuilles sur la syntaxe de la langue qui est préférable de laisser aux fonctions.a lot
of things actually work better without the overhead of classes
a lot of things actually work better with addition of class-based OOP
. Source: TypeScript, Dart, CoffeeScript et toutes les autres méthodes utilisées par l’industrie pour s’éloigner du langage OOP fonctionnel / prototype.Lisez SICP et découvrez Scheme, ainsi que l’ idée pratique des types de données abstraits . Il est alors facile de coder en C (car avec SICP, un peu de C, et un peu de PHP, Ruby, etc ...), votre réflexion serait suffisamment élargie et vous comprendriez que la programmation orientée objet peut ne pas être le meilleur style en tous les cas, mais seulement pour certains types de programmes). Faites attention à l'allocation de mémoire dynamique en C , qui est probablement la partie la plus difficile. Le langage de programmation standard C99 ou C11 et sa bibliothèque standard C sont en fait assez médiocres (on ne connaît pas le protocole TCP ni les répertoires!), Et vous aurez souvent besoin de bibliothèques externes ou d’interfaces (par exemplePOSIX , libcurl pour la bibliothèque client HTTP, libonion pour la bibliothèque de serveur HTTP, GMPlib pour les fichiers bignums, une bibliothèque comme libunistring pour UTF-8, etc ...).
Vos "objets" sont souvent en C, des
struct
-s liés , et vous définissez l’ensemble des fonctions qui les exploitent. Pour des fonctions courtes ou très simples, pensez à les définir, avec les éléments pertinentsstruct
, commestatic inline
dans certains fichiers d’en-tête quifoo.h
doivent être#include
-d ailleurs.Notez que la programmation orientée objet n'est pas le seul paradigme de programmation . Dans certains cas, d'autres paradigmes sont intéressants ( programmation fonctionnelle à la Ocaml ou Haskell ou encore Scheme ou Commmon Lisp, programmation logique à la Prolog, etc. etc ... Lisez également le blog de J.Pitrat sur l'intelligence artificielle déclarative). Voir le livre de Scott: Programming Language Pragmatique
En réalité, un programmeur en C ou en Ocaml ne souhaite généralement pas coder dans un style de programmation orienté objet. Il n'y a aucune raison de vous forcer à penser à des objets quand cela ne sert à rien.
Vous en définirez certaines
struct
et leurs fonctions (souvent par le biais de pointeurs). Vous pourriez avoir besoin de quelques unions étiquetées (souvent, unstruct
membre avec un tag, souventenum
, et d’autres à l’union
intérieur), et il pourrait être utile d’avoir un membre de tableau flexible à la fin de certains de vosstruct
-s.Regardez à l'intérieur du code source de certains logiciels libres existants en C (voir github & sourceforge pour en trouver). Il est probablement utile d’installer et d’utiliser une distribution Linux: celle-ci n’est constituée que de logiciels libres, elle dispose d’excellents compilateurs de logiciels libres ( GCC , Clang / LLVM ) et d’outils de développement. Voir aussi Programmation Linux avancée si vous souhaitez développer pour Linux.
N'oubliez pas de compiler avec tous les avertissements et informations de débogage, par exemple,
gcc -Wall -Wextra -g
notamment pendant les phases de développement et de débogage, et d'apprendre à utiliser certains outils, par exemple, valgrind pour chasser les fuites de mémoire , legdb
débogueur, etc. Veillez à bien comprendre ce qui n'est pas défini comportement et évitez-le fortement (rappelez-vous qu'un programme peut avoir un UB et parfois sembler "fonctionner").Lorsque vous avez réellement besoin de constructions orientées objet (notamment l' héritage ), vous pouvez utiliser des pointeurs sur des structures et des fonctions associées. Vous pouvez avoir votre propre machine vtable , chaque objet commençant par un pointeur sur un
struct
pointeur de fonction contenant. Vous tirez parti de la possibilité de convertir un type de pointeur en un autre type de pointeur (et du fait que vous pouvez convertir à partir d'unstruct super_st
contenant les mêmes types de champs que ceux commençant parstruct sub_st
émuler un héritage). Notez que C est suffisant pour implémenter des systèmes d’objets assez sophistiqués - en particulier en respectant certaines conventions -, comme le montre GObject (de GTK / Gnome).Lorsque vous avez réellement besoin de fermetures , vous les imitez souvent avec des rappels , avec la convention selon laquelle chaque fonction utilisant un rappel est transmise à la fois à un pointeur de fonction et à certaines données client (utilisées par le pointeur de la fonction lorsqu'il appelle cela). Vous pourriez aussi avoir (de manière conventionnelle) vos propres
struct
-s de type fermeture (contenant un pointeur de fonction et les valeurs fermées).Comme le langage C est un langage de très bas niveau, il est important de définir et de documenter vos propres conventions (inspirées de la pratique des autres programmes C), en particulier en ce qui concerne la gestion de la mémoire, et probablement aussi certaines conventions de dénomination. Il est utile d’avoir une idée de l’ architecture des jeux d’instructions . Ne pas oublier que un C compilateur peut faire beaucoup d' optimisations sur votre code (si vous lui demandez), il ne se soucie pas trop de faire des micro-optimisations à la main, que l' autorisation à votre compilateur (
gcc -Wall -O2
pour la compilation optimisée de sortie Logiciel). Si vous vous souciez de l'analyse comparative et des performances brutes, vous devez activer les optimisations (une fois que votre programme a été débogué).N'oubliez pas que parfois, la métaprogrammation est utile . Assez souvent, les gros logiciels écrits en C contiennent des scripts ou des programmes ad-hoc pour générer du code C utilisé ailleurs (et vous pouvez également jouer à des astuces de préprocesseur sales , par exemple X-macros ). Il existe des générateurs de programmes C utiles (par exemple, yacc ou gnu bison pour générer des analyseurs syntaxiques, gperf pour générer des fonctions de hachage parfait, etc.). Sur certains systèmes (notamment Linux et POSIX), vous pouvez même générer du code C à l'exécution
generated-001.c
, le compiler en un objet partagé en exécutant une commande (commegcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so
) à l'exécution, charger cet objet partagé de manière dynamique à l'aide de dlopen.& récupère un pointeur de fonction à partir d’un nom utilisant dlsym . Je fais de telles astuces dans MELT (un langage spécifique au domaine, similaire à Lisp, qui pourrait vous être utile, car il permet la personnalisation du compilateur GCC ).Soyez conscient des concepts et des techniques de collecte des déchets (le comptage de références est souvent une technique de gestion de la mémoire en C, et IMHO est une forme de collecte des déchets médiocre qui ne traite pas bien les références circulaires ; vous pourriez avoir des pointeurs faibles pour vous aider, mais cela peut être délicat). À certaines occasions, vous pourriez envisager d'utiliser le récupérateur de déchets conservateur de Boehm .
la source
La façon dont le programme est construit consiste essentiellement à définir quelles actions (fonctions) doivent être exécutées afin de résoudre le problème (c'est pourquoi il est appelé langage procédural). Chaque action correspondra à une fonction. Vous devez ensuite définir le type d’informations que chaque fonction recevra et les informations qu’elles doivent renvoyer.
Le programme est normalement séparé en fichiers (modules). Chaque fichier aura normalement un groupe de fonctions liées. Au début de chaque fichier, vous déclarez (en dehors de toute fonction) les variables qui seront utilisées par toutes les fonctions de ce fichier. Si vous utilisez le qualificatif "statique", ces variables ne seront visibles qu'à l'intérieur de ce fichier (mais pas à partir d'autres fichiers). Si vous n'utilisez pas de qualificatif "statique" sur les variables définies en dehors des fonctions, ils seront également accessibles à partir d'autres fichiers. Ces autres fichiers doivent déclarer la variable comme "extern" (mais pas la définir) afin que le compilateur les recherche. dans d'autres fichiers.
En bref, vous pensez d’abord aux procédures (fonctions), puis vous vous assurez que toutes les fonctions ont accès aux informations dont elles ont besoin.
la source
Les API C ont souvent - peut-être même, généralement - une interface essentiellement orientée objet si vous les regardez correctement.
En C ++:
En C:
Comme vous le savez peut-être, en C ++ et dans divers autres langages OO formels, une méthode objet prend sous le capot un premier argument qui est un pointeur sur l'objet, un peu comme la version C
bar()
ci - dessus. Pour un exemple de ce qui apparaît à la surface en C ++, voyez commentstd::bind
utiliser les méthodes d'objet pour ajuster les signatures de fonction:Comme d'autres l'ont fait remarquer, la vraie différence réside dans le fait que les langages OO formels peuvent implémenter un polymorphisme, un contrôle d'accès et diverses autres fonctionnalités astucieuses. Mais l'essence de la programmation orientée objet, la création et la manipulation de structures de données discrètes et complexes, est déjà une pratique fondamentale en C.
la source
L'une des principales raisons pour lesquelles les gens sont encouragés à apprendre le C est que c'est l'un des langages de programmation de haut niveau les plus bas. Les langages POO facilitent la réflexion sur les modèles de données et la modélisation du passage du code et des messages, mais à la fin de la journée, un microprocesseur exécute le code pas à pas, sautant dans des blocs de code (fonctions en C) et se déplaçant. des références à des variables (pointeurs en C) autour afin que différentes parties d’un programme puissent partager des données. Pensez à C comme langage d'assemblage en anglais - fournissant des instructions étape par étape au microprocesseur de votre ordinateur - et vous ne vous tromperez pas. En prime, la plupart des interfaces de système d’exploitation fonctionnent comme des appels de fonction C plutôt que comme des paradigmes de POO,
la source
uint16_t blah(uint16_t x) {return x*x;}
fonctionnera de manière identique sur les machines deunsigned int
16 bits ou de 33 bits ou plus. Cependant, certains compilateurs pour des machines deunsigned int
17 à 32 bits peuvent considérer un appel à cette méthode ...uint16_t
9, le Standard n’impose pas de tels comportements lors de la multiplication de valeurs de typeuint16_t
sur des plates-formes de 17 à 32 bits.Je suis aussi un natif OO (C ++ en général) qui doit parfois survivre dans un monde de C. Pour moi, le plus gros obstacle consiste fondamentalement à gérer les erreurs et à gérer les ressources.
En C ++, nous devons jeter une erreur d'où cela arrive jusqu'au niveau supérieur où nous pouvons le traiter et nous avons des destructeurs pour libérer automatiquement notre mémoire et d'autres ressources.
Vous remarquerez peut-être que de nombreuses API C incluent une fonction init qui vous fournit un typedef'd void *, qui est en réalité un pointeur sur une structure. Ensuite, vous transmettez ceci en tant que premier argument pour chaque appel d'API. Cela devient essentiellement votre pointeur "ceci" de C ++. Il est utilisé pour toutes les structures de données internes qui sont cachées (un concept très orienté objet). Vous pouvez également l'utiliser pour gérer la mémoire, par exemple avoir une fonction appelée myapiMalloc qui malloque votre mémoire et enregistre le malloc dans votre version C d'un pointeur this afin que vous puissiez vous assurer qu'il est libéré lors du retour de votre API. En outre, comme je l’ai récemment découvert, vous pouvez l’utiliser pour stocker des codes d’erreur et utiliser setjmp et longjmp pour vous donner un comportement très similaire à celui de la capture. La combinaison des deux concepts vous donne une grande partie des fonctionnalités d'un programme C ++.
Maintenant, vous avez dit que vous ne vouliez pas apprendre à forcer C en C ++. Ce n'est pas vraiment ce que je décris (du moins pas délibérément). C'est simplement une méthode (espérons-le) bien conçue pour exploiter la fonctionnalité C. Certaines versions de OO ont peut-être été utilisées. C'est peut-être pour cette raison que les langages OO ont été développés. Ils étaient un moyen de formaliser / appliquer / faciliter les concepts que certaines personnes trouvaient être la meilleure pratique.
Si vous pensez que cela vous fait peur, l’alternative est d’avoir à peu près toutes les fonctions qui renvoient un code d’erreur que vous devez vous assurer religieusement de vérifier après chaque appel de fonction et de propager la pile d’appels. Vous devez vous assurer que toutes les ressources sont libérées non seulement à la fin de chaque fonction, mais à chaque point de retour (éventuellement après tout appel de fonction pouvant renvoyer une erreur indiquant que vous ne pouvez pas continuer). Cela peut devenir très fastidieux et vous faire penser que je n’ai probablement pas besoin de faire face à cette défaillance potentielle d’allocation de mémoire (ou de lecture de fichier ou de connexion de port ...), je suppose simplement que cela fonctionnera ou que je " ll écrira le code "intéressant" maintenant et reviendra et traitera le traitement des erreurs - ce qui ne se produit jamais.
la source