J'ai toujours vu des gens écrire
class.h
#ifndef CLASS_H
#define CLASS_H
//blah blah blah
#endif
La question est, pourquoi ne font-ils pas également cela pour le fichier .cpp qui contient des définitions pour les fonctions de classe?
Disons que j'ai main.cpp
, et main.cpp
comprend class.h
. Le class.h
fichier ne fait include
rien, alors comment main.cpp
savoir ce qu'il y a dans le class.cpp
?
FILE_H
, nonCLASS_H
.Réponses:
Tout d'abord, pour répondre à votre première demande:
Lorsque vous voyez cela dans le fichier .h :
Il s'agit d'une technique de préprocesseur pour empêcher un fichier d'en-tête d'être inclus plusieurs fois, ce qui peut être problématique pour diverses raisons. Lors de la compilation de votre projet, chaque fichier .cpp (généralement) est compilé. En termes simples, cela signifie que le compilateur prendra votre fichier, le compilateur ajoutera deux fois le contenu de son fichier, donc s'il y a des définitions dans ce fichier, vous obtiendrez une erreur du compilateur vous indiquant que vous avez redéfini une variable. Lorsque le fichier est traité par l'étape du préprocesseur dans le processus de compilation, la première fois que son contenu est atteint, les deux premières lignes vérifieront s'il a été défini pour le préprocesseur. Sinon, il définira et continuera à traiter le code entre lui et le fichier .cpp , ouvrira tous les fichiers
#included
, les concaténera tous dans un fichier texte massif, puis effectuera une analyse de syntaxe et enfin il le convertira en un code intermédiaire, optimisera / exécutera d'autres tâches, et enfin générer la sortie d'assembly pour l'architecture cible. Pour cette raison, si un fichier est#included
plusieurs fois sous un .cppFILE_H
FILE_H
#endif
directif. La prochaine fois que le contenu de ce fichier sera vu par le préprocesseur, la vérificationFILE_H
sera fausse, de sorte qu'il analysera immédiatement le#endif
et continuera après lui. Cela évite les erreurs de redéfinition.Et pour répondre à votre deuxième préoccupation:
Dans la programmation C ++ en tant que pratique générale, nous séparons le développement en deux types de fichiers. L'un est avec une extension de .h et nous l'appelons un "fichier d'en-tête". Ils fournissent généralement une déclaration de fonctions, de classes, de structures, de variables globales, de typedefs, de macros de prétraitement et de définitions, etc. Fondamentalement, ils vous fournissent simplement des informations sur votre code. Ensuite, nous avons l' extension .cpp que nous appelons un "fichier de code". Cela fournira des définitions pour ces fonctions, les membres de classe, tous les membres de structure qui ont besoin de définitions, de variables globales, etc. Ainsi, le fichier .h déclare le code et le fichier .cpp implémente cette déclaration. Pour cette raison, nous compilons généralement chaque .cpp lors de la compilationdans un objet, puis liez ces objets (car vous ne voyez presque jamais un fichier .cpp inclure un autre fichier .cpp ).
La façon dont ces externes sont résolus est un travail pour l'éditeur de liens. Lorsque votre compilateur traite main.cpp , il obtient des déclarations pour le code de class.cpp en incluant class.h . Il a seulement besoin de savoir à quoi ressemblent ces fonctions ou variables (ce que vous donne une déclaration). Il compile donc votre fichier main.cpp dans un fichier objet (appelez-le main.obj ). De même, class.cpp est compilé dans un class.objfichier. Pour produire l'exécutable final, un éditeur de liens est appelé pour lier ces deux fichiers objets ensemble. Pour toutes les variables ou fonctions externes non résolues, le compilateur placera un stub où l'accès se produit. L'éditeur de liens prendra alors ce stub et recherchera le code ou la variable dans un autre fichier objet répertorié, et s'il est trouvé, il combine le code des deux fichiers objet dans un fichier de sortie et remplace le stub par l'emplacement final de la fonction ou variable. De cette façon, votre code dans main.cpp peut appeler des fonctions et utiliser des variables dans class.cpp SI ET UNIQUEMENT SI ILS SONT DÉCLARÉS DANS class.h .
J'espère que cela a été utile.
la source
Le
CLASS_H
est un garde d'inclusion ; il est utilisé pour éviter que le même fichier d'en-tête ne soit inclus plusieurs fois (via des itinéraires différents) dans le même fichier CPP (ou, plus précisément, la même unité de traduction ), ce qui entraînerait des erreurs de définition multiple.Les gardes d'inclusion ne sont pas nécessaires sur les fichiers CPP car, par définition, le contenu du fichier CPP n'est lu qu'une seule fois.
Vous semblez avoir interprété les protections d'inclusion comme ayant la même fonction que les
import
instructions dans d'autres langages (comme Java); ce n'est cependant pas le cas. Le#include
lui-même est à peu près équivalent à celuiimport
dans d'autres langues.la source
Ce n'est pas le cas - du moins pendant la phase de compilation.
La traduction d'un programme C ++ du code source au code machine s'effectue en trois phases:
class.h
est inséré à la place de la ligne#include "class.h
. Étant donné que vous pouvez être inclus dans votre fichier d'en-tête à plusieurs endroits, les#ifndef
clauses évitent les erreurs de déclaration en double, car la directive du préprocesseur n'est définie que la première fois que le fichier d'en-tête est inclus.En résumé, les déclarations peuvent être partagées via un fichier d'en-tête, tandis que le mappage des déclarations aux définitions est effectué par l'éditeur de liens.
la source
C'est la distinction entre déclaration et définition. Les fichiers d'en-tête incluent généralement uniquement la déclaration et le fichier source contient la définition.
Pour utiliser quelque chose, il vous suffit de connaître sa déclaration et non sa définition. Seul l'éditeur de liens a besoin de connaître la définition.
C'est pourquoi vous allez inclure un fichier d'en-tête dans un ou plusieurs fichiers source mais vous n'incluerez pas de fichier source dans un autre.
Vous voulez dire aussi
#include
et ne pas importer.la source
Ceci est fait pour les fichiers d'en-tête afin que le contenu n'apparaisse qu'une seule fois dans chaque fichier source prétraité, même s'il est inclus plus d'une fois (généralement parce qu'il est inclus à partir d'autres fichiers d'en-tête). La première fois qu'il est inclus, le symbole
CLASS_H
(connu sous le nom de garde d'inclusion ) n'a pas encore été défini, donc tout le contenu du fichier est inclus. Cela définit le symbole, donc s'il est à nouveau inclus, le contenu du fichier (à l'intérieur du#ifndef
/#endif
bloc ) est ignoré.Il n'est pas nécessaire de le faire pour le fichier source lui-même car (normalement) il n'est inclus dans aucun autre fichier.
Pour votre dernière question,
class.h
devrait contenir la définition de la classe, et les déclarations de tous ses membres, les fonctions associées, et quoi que ce soit d'autre, afin que tout fichier qui l'inclut ait suffisamment d'informations pour utiliser la classe. Les implémentations des fonctions peuvent aller dans un fichier source séparé; vous n'avez besoin que des déclarations pour les appeler.la source
main.cpp n'a pas besoin de savoir ce qu'il y a dans class.cpp . Il a juste besoin de connaître les déclarations des fonctions / classes qu'il va utiliser, et ces déclarations sont dans class.h .
L'éditeur de liens relie les endroits où les fonctions / classes déclarées dans class.h sont utilisées et leurs implémentations dans class.cpp
la source
.cpp
les fichiers ne sont pas inclus (en utilisant#include
) dans d'autres fichiers. Par conséquent, ils n'ont pas besoin d'inclure la garde.Main.cpp
connaîtra les noms et les signatures de la classe dans laquelle vous avez implémentéclass.cpp
uniquement parce que vous avez spécifié tout cela dansclass.h
- c'est le but d'un fichier d'en-tête. (C'est à vous de vous assurer queclass.h
décrit avec précision le code que vous implémentezclass.cpp
.) Le code exécutable dansclass.cpp
sera mis à disposition du code exécutablemain.cpp
grâce aux efforts de l'éditeur de liens.la source
On s'attend généralement à ce que les modules de code tels que les
.cpp
fichiers soient compilés une fois et liés dans plusieurs projets, pour éviter une compilation répétitive inutile de la logique. Par exemple,g++ -o class.cpp
produiraitclass.o
que vous pourriez ensuite lier à partir de plusieurs projets à l'utilisationg++ main.cpp class.o
.Nous pourrions utiliser
#include
comme notre éditeur de liens, comme vous semblez le sous-entendre, mais ce serait juste idiot quand nous savons comment lier correctement en utilisant notre compilateur avec moins de frappes et moins de répétitions inutiles de compilation, plutôt que notre code avec plus de frappes et plus de gaspillage répétition de la compilation ...Cependant, les fichiers d'en-tête doivent toujours être inclus dans chacun des multiples projets, car cela fournit l'interface pour chaque module. Sans ces en-têtes, le compilateur ne connaîtrait aucun des symboles introduits par les
.o
fichiers.Il est important de réaliser que ce sont les fichiers d'en-tête qui introduisent les définitions des symboles pour ces modules; une fois que cela est réalisé, il est logique que de multiples inclusions puissent provoquer des redéfinitions de symboles (ce qui provoque des erreurs), nous utilisons donc des gardes d'inclusion pour empêcher de telles redéfinitions.
la source
C'est à cause de Headerfiles de définir ce que la classe contient (membres, structures de données) et les fichiers cpp l'implémentent.
Et bien sûr, la raison principale en est que vous pouvez inclure un fichier .h plusieurs fois dans d'autres fichiers .h, mais cela entraînerait plusieurs définitions d'une classe, ce qui n'est pas valide.
la source