J'ai toujours vu des gens écrire
class.h
#ifndef CLASS_H
#define CLASS_H
//blah blah blah
#endif
La question qui se pose est de savoir pourquoi ils ne le font pas également pour le fichier .cpp qui contient les définitions des fonctions de classe.
Disons que j'ai main.cpp
Et que main.cpp
Inclut class.h
. Le fichier class.h
N'importe rien, alors comment main.cpp
Sait ce qu'il y a dans le fichier class.cpp
?
Tout d'abord, pour répondre à votre première demande:
Lorsque vous voyez ceci dans le fichier . H:
#ifndef FILE_H
#define FILE_H
/* ... Declarations etc here ... */
#endif
Il s'agit d'une technique de préprocesseur consistant à empêcher l'inclusion multiple d'un fichier d'en-tête, ce qui peut poser problème 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 . Cpp, ouvrira tous les fichiers #included
par cela, les concaténer dans un fichier texte massif, puis effectuer une analyse syntaxique et enfin le convertir en un code intermédiaire, optimiser/effectuer d'autres tâches et enfin générer la sortie Assembly pour l'architecture cible. Pour cette raison, si un fichier est #included
plusieurs fois sous un fichier . cpp, le compilateur ajoutera son contenu deux fois. Par conséquent, s’il contient des définitions, 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, les deux premières lignes vérifient si FILE_H
a été défini pour le préprocesseur. Sinon, cela définira FILE_H
et continuez le traitement du code entre elle et le #endif
directive. La prochaine fois que le préprocesseur verra le contenu de ce fichier, la vérification par rapport à FILE_H
sera faux, donc il analysera immédiatement le #endif
et continue après. Cela évite les erreurs de redéfinition.
Et pour répondre à votre deuxième préoccupation:
En programmation C++, en règle générale, nous séparons le développement en deux types de fichiers. On est avec une extension de . H et on appelle cela un "fichier 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. 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, membres de classe, tous membres de structure qui ont besoin de définitions, variables globales, etc. Ainsi, le fichier . H déclare le code et le fichier . Cpp met en œuvre cette déclaration. Pour cette raison, nous compilons généralement chaque fichier . Cpp en un objet lors de la compilation, puis lions ces objets (car vous n'en voyez presque jamais un . Cpp le fichier en inclut un autre .cpp fichier).
Comment ces externals sont résolus est un travail pour l'éditeur de liens. Lorsque votre compilateur traite main.cpp, il obtient les déclarations du code dans class.cpp en incluant class.h. Il suffit de savoir à quoi ressemblent ces fonctions ou ces variables (ce que vous donne une déclaration). Donc, il compile votre fichier main.cpp dans un fichier objet (appelez-le main.obj). De même, class.cpp est compilé dans un fichier class.obj. Pour produire l'exécutable final, un éditeur de liens est appelé pour lier ces deux fichiers objet. Pour toutes les variables ou fonctions externes non résolues, le compilateur placera un stub à l'endroit où l'accès se produit. L'éditeur de liens prend ensuite ce stub et cherche le code ou la variable dans un autre fichier objet répertorié. S'il le trouve, 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.
Le CLASS_H
est un inclure garde ; cela évite d'inclure plusieurs fois le même fichier d'en-tête (via différentes routes) dans le même fichier CPP (ou, plus précisément, la même nité de traduction ), ce qui entraînerait des erreurs de définition multiple.
Les protections d'inclusion ne sont pas nécessaires dans 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 gardes d'inclusion comme ayant la même fonction que les instructions import
dans d'autres langages (tels que Java); ce n'est pas le cas, cependant. Le #include
lui-même est à peu près équivalent à import
dans d’autres langues.
Ce n'est pas le cas, du moins pendant la phase de compilation.
La traduction d'un programme c ++ du code source en code machine s'effectue en trois phases:
class.h
Est inséré à la place de la ligne #include "class.h
. Etant donné que vous pourriez être inclus dans votre fichier d'en-tête à plusieurs endroits, les clauses #ifndef
Évitent les erreurs de déclaration en double, car la directive préprocesseur n'est pas définie uniquement lors de la première inclusion du fichier d'en-tête.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.
Cela est fait pour les fichiers d'en-tête afin que le contenu n'apparaisse qu'une fois dans chaque fichier source prétraité, même s'il est inclus plusieurs fois (généralement parce qu'il est inclus dans d'autres fichiers d'en-tête). La première fois qu'il est inclus, le symbole CLASS_H
(connu sous le nom include guard) n’a pas encore été défini, de sorte que tout le contenu du fichier est inclus. Faire cela définit le symbole, donc s'il est inclus à nouveau, le contenu du fichier (à l'intérieur du #ifndef
/#endif
bloc) sont ignorés.
Il n'est pas nécessaire de faire cela 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, des fonctions associées et de tout autre élément, afin que tout fichier l’incluant ait suffisamment d’informations pour utiliser la classe. Les implémentations des fonctions peuvent aller dans un fichier source séparé; vous avez seulement besoin des déclarations pour les appeler.
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 doit connaître la définition.
C’est pourquoi vous allez inclure un fichier d’en-tête dans un ou plusieurs fichiers source, sans toutefois inclure un fichier source dans un autre.
Aussi tu veux dire #include
et non importé.
main.cpp ne doit pas savoir ce qu'il y a dans class.cpp. Il suffit 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
On s'attend généralement à ce que des modules de code tels que .cpp
Les fichiers sont compilés une fois et liés à plusieurs projets afin d’éviter une compilation répétitive inutile de la logique. Par exemple, g++ -o class.cpp
produirait class.o
que vous pouvez ensuite lier à partir de plusieurs projets pour utiliser g++ main.cpp class.o
.
Nous pourrions utiliser #include
en tant que notre linker, comme vous semblez l’impliquer, mais ce serait idiot de savoir comment lier correctement en utilisant notre compilateur avec moins de frappes au clavier et moins de répétitions inutiles de la compilation, plutôt que notre code avec plus de frappes au clavier et des répétitions plus inutiles de compilation ...
Les fichiers d'en-tête doivent toujours être inclus dans chacun des projets, car ils fournissent l'interface pour chaque module. Sans ces en-têtes, le compilateur ne connaît aucun des symboles introduits par le .o
des dossiers.
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 protecteurs pour empêcher de telles redéfinitions.
Les fichiers .cpp
Ne sont pas inclus (utilisez #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 que vous avez implémentée dans class.cpp
Uniquement parce que vous avez spécifié tout cela dans class.h
- c'est le but d'un fichier d'en-tête. (C’est à vous de vous assurer que class.h
Décrit correctement le code que vous implémentez dans class.cpp
.) Le code exécutable dans class.cpp
Sera mis à la disposition du code exécutable dans main.cpp
Grâce aux efforts de l'éditeur de liens.
c'est parce que Headerfiles définit ce que la classe contient (Membres, structures de données) et que les fichiers cpp l'implémentent.
Et bien sûr, la principale raison à cela est que vous pouvez inclure un fichier .h plusieurs fois dans d'autres fichiers .h, mais cela aboutirait à plusieurs définitions d'une classe, qui n'est pas valide.