Lors de la rédaction de projets en C ++, la gestion de la segmentation des fichiers est très nécessaire. Aujourd'hui, l'éditeur de Foxin Technology Channel vous apportera une explication détaillée des fichiers d'en-tête et des fichiers source en C ++. J'espère qu'il vous sera utile d'apprendre ces connaissances!
Explication détaillée des fichiers d'en-tête et des fichiers source dans C ++
1. Mode de compilation C ++
Généralement, dans un programme C ++, il n'y a que deux types de fichiers. Parmi eux, le fichier .cpp est appelé le fichier source C ++, et le code source de C ++ y est placé; tandis que le fichier .h est appelé le fichier d'en-tête C ++ et que le code source de C ++ y est placé.
La langue C ++ prend en charge la "compilation séparée". En d'autres termes, tous les contenus d'un programme peuvent être divisés en différentes parties et placés dans différents fichiers .cpp. Les choses dans le fichier .cpp sont relativement indépendantes. Lors de la compilation (compiler), vous n'avez pas besoin de communiquer avec d'autres fichiers. Il vous suffit de créer un lien avec d'autres fichiers cibles après l'avoir compilé dans le fichier cible. Par exemple, une fonction globale "void a () {}" est définie dans le fichier a.cpp, et cette fonction doit être appelée dans le fichier b.cpp. Malgré cela, les fichiers A.CPP et B.CPP n'ont pas besoin de connaître l'existence de l'autre, mais peuvent les compiler séparément. Après les avoir compilés dans le fichier cible, liez-les et l'ensemble du programme peut être exécuté.
Comment cela est-il réalisé? Du point de vue des programmes d'écriture, c'est très simple. Dans le fichier B.CPP, avant d'appeler la fonction "void a ()", déclarez la fonction "void a ();" d'abord. En effet, le compilateur générera une table de symboles lors de la compilation de B.CPP. Des symboles comme "void a ()" qui ne peuvent pas être vus seront stockés dans ce tableau. Lors du lien à nouveau, le compilateur recherchera la définition de ce symbole dans d'autres fichiers d'objets. Une fois trouvé, le programme peut être généré en douceur.
Notez qu'il y a deux concepts mentionnés ici, l'un est "définition" et l'autre est "Déclaration". En termes simples, "définition" signifie décrire un symbole d'une manière complète et complète: qu'il s'agisse d'une variable ou d'une fonction, quel type il renvoie, de quels paramètres il a besoin, etc. "Déclaration" déclare simplement l'existence de ce symbole, c'est-à-dire, dit au compilateur que ce symbole est défini dans d'autres fichiers. Je vais l'utiliser en premier. Lorsque vous liez, allez dans un autre endroit pour savoir de quoi il s'agit. Lors de la définition, vous devez définir complètement un symbole (variable ou fonction) en fonction de la syntaxe C ++, et lors de la déclaration, vous n'avez qu'à écrire le prototype de ce symbole. Il convient de noter qu'un symbole peut être déclaré plusieurs fois tout au long du programme, mais ne doit être défini qu'une seule fois. Imaginez-vous, s'il y a deux définitions différentes d'un symbole, qui devrait écouter le compilateur?
Ce mécanisme apporte de nombreux avantages aux programmeurs C ++ et conduit également à une méthode d'écriture de programmes. Considérez que s'il existe une fonction très couramment utilisée "void f () {}" qui sera appelée dans de nombreux fichiers .cpp dans l'ensemble du programme, nous n'avons qu'à définir cette fonction dans un seul fichier et à déclarer cette fonction dans d'autres fichiers. Une fonction est facile à gérer, et cela signifie seulement une phrase pour le déclarer. Cependant, que se passe-t-il s'il y a trop de fonctions, comme un tas de fonctions mathématiques, il y en a des centaines? Chaque programmeur peut-il être sûr d'écrire avec précision et d'écrire toutes les fonctions sous la forme?
2. Qu'est-ce qu'un fichier d'en-tête
De toute évidence, la réponse est impossible. Mais il existe un moyen très simple d'aider les programmeurs à économiser la peine de se souvenir de tant de prototypes de fonctions: nous pouvons écrire toutes les instructions de déclaration de centaines de fonctions d'abord et les mettre dans un fichier. Lorsque le programmeur en a besoin, copiez toutes ces choses dans son code source.
Cette méthode est certainement possible, mais elle est encore trop gênante et semble maladroite. Ainsi, le fichier d'en-tête peut jouer son rôle. Le fichier soi-disant d'en-tête a en fait le même contenu que le contenu du fichier .cpp, qui sont tous deux du code source C ++. Mais le fichier d'en-tête n'a pas besoin d'être compilé. Nous mettons toutes les déclarations de fonction dans un fichier d'en-tête. Lorsqu'un fichier source .cpp en a besoin, ils peuvent être inclus dans ce fichier .cpp via une commande macro "#include", afin que leur contenu soit fusionné dans le fichier .cpp. Lorsque le fichier .cpp est compilé, les fonctions de ces fichiers .h incluses sont lues.
Donnons un exemple. Supposons que toutes les fonctions mathématiques n'en ont que deux: F1 et F2, alors nous mettons leurs définitions en math.cpp:
/ * math.cpp * / double f1 () {// faire quelque chose ici ... return;} double f2 (double a) {// faire quelque chose ici ... return a * a;} / * fin de math.cpp * /Et mettez la déclaration des fonctions "ces" dans un fichier d'en-tête math.h:
/ * math.h * / double f1 (); double f2 (double); / * fin de math.h * /
Dans un autre fichier main.cpp, je veux appeler ces deux fonctions, donc j'ai juste besoin d'inclure le fichier d'en-tête:
/ * main.cpp * / # inclut "math.h" main () {int number1 = f1 (); int number2 = f2 (nombre1);} / * fin de main.cpp * /Il s'agit d'un programme complet. Il convient de noter que le fichier .h n'a pas besoin d'être écrit après la commande du compilateur, mais il doit être trouvé à l'endroit où le compilateur peut le trouver (par exemple, dans le même répertoire que main.cpp). main.cpp et math.cpp peuvent être compilés pour générer respectivement main.o et math.o, puis lier ces deux fichiers d'objets, et le programme peut être exécuté.
3. #Include
#include est une commande macro du langage C qui fonctionne avant le compilation du compilateur, c'est-à-dire lorsqu'il est précompilé. Le but de #include est d'inclure complètement et complètement le contenu du fichier écrit après le fichier actuel. Il convient de mentionner qu'il n'a pas d'autres fonctions ou sous-fonctions. Sa fonction est de remplacer chaque endroit où il apparaît par le contenu du fichier qu'il écrit derrière. Remplacement de texte simple, rien d'autre. Par conséquent, la première phrase dans le fichier main.cpp (#include "math.h") sera remplacée par le contenu du fichier math.h avant la compilation. Autrement dit, lorsque le processus de compilation est sur le point de commencer, le contenu de Main.cpp a changé:
/ * ~ main.cpp * / double f1 (); double f2 (double); main () {int number1 = f1 (); int number2 = f2 (nombre1);} / * fin de ~ main.cpp * /Pas plus ou moins, juste. De même, si nous utilisons les fonctions F1 et F2 en plus de main.cpp, nous avons seulement besoin d'écrire une phrase #include "Math.h" avant d'utiliser ces deux fonctions.
4. Que devrait être écrit dans le fichier d'en-tête
Grâce à la discussion ci-dessus, nous pouvons comprendre que la fonction du fichier d'en-tête doit être incluse dans d'autres .cpps. Ils ne participent pas eux-mêmes à la compilation, mais en fait, leur contenu est compilé dans plusieurs fichiers .cpp. Grâce à la règle de «définition ne peut être qu'une seule fois», nous pouvons facilement conclure que seules les variables et les fonctions de fonctions doivent être placées dans le fichier d'en-tête et que leurs définitions ne doivent pas être placées. Parce que le contenu d'un fichier d'en-tête sera en fait introduit dans plusieurs fichiers .cpp différents, et ils seront tous compilés. Il est bien sûr acceptable de faire une déclaration. Si vous mettez une définition, elle équivaut à une définition d'un symbole (variable ou fonction) apparaissant dans plusieurs fichiers. Même si ces définitions sont les mêmes, il n'est pas légal que le compilateur le fasse.
Par conséquent, une chose dont vous devez vous souvenir est que dans le fichier d'en-tête .h, il ne peut y avoir que des variables ou des déclarations de fonctions, et ne placent pas de définitions. C'est-à-dire des phrases telles que: extern int a; et vide f (); ne peut être écrit que dans le fichier d'en-tête. Ce sont les déclarations. Si vous écrivez une phrase comme int a; ou void f () {}, puis une fois que le fichier d'en-tête est inclus dans deux fichiers .cpp ou plus, le compilateur rapportera immédiatement une erreur. (À propos de l'extern, il a été discuté auparavant, et la différence entre la définition et la déclaration ne sera pas discutée ici.) Cependant, il y a trois exceptions à cette règle.
1. La définition de l'objet const peut être écrite dans le fichier d'en-tête. Étant donné que l'objet Global Const n'est pas déclaré par Extern par défaut, il n'est valable que dans le fichier actuel. Écrivez un tel objet dans un fichier d'en-tête, même s'il est inclus dans plusieurs autres fichiers .cpp, l'objet n'est valide que dans le fichier le contenant et est invisible à d'autres fichiers, donc il ne conduira pas à plusieurs définitions. Dans le même temps, parce que les objets de ces fichiers .cpp sont inclus à partir d'un fichier d'en-tête, cela garantit que les valeurs des objets const dans ces fichiers .cpp sont les mêmes, ce qui peut être considéré comme tuer deux oiseaux avec une pierre. De même, la définition de l'objet statique peut également être placée dans le fichier d'en-tête.
2. La définition des fonctions en ligne peut être écrite dans le fichier d'en-tête. Étant donné que la fonction en ligne nécessite que le compilateur soit en ligne en fonction de sa définition lorsqu'il le rencontre, plutôt qu'une simple fonction ordinaire qui peut être déclarée d'abord puis liée (les fonctions en ligne ne seront pas liées), le compilateur doit voir la définition complète de la fonction en ligne pendant la compilation. Si une fonction en ligne ne peut être définie qu'une fois comme une fonction normale, ce sera difficile à faire. Parce que ça va dans un fichier, je peux écrire la définition de la fonction en ligne au début, afin que je puisse voir la définition lorsque je l'utilise plus tard; Mais que se passe-t-il si j'utilise cette fonction dans d'autres fichiers? Il n'y a presque pas de bonne solution à cela, donc C ++ stipule que les fonctions en ligne peuvent être définies plusieurs fois dans le programme. Tant que la fonction en ligne n'apparaît qu'une seule fois dans un fichier .cpp, et dans tous les fichiers .cpp, la définition de cette fonction en ligne est la même, et elle peut être compilée. De toute évidence, il est très sage de mettre la définition des fonctions en ligne dans un fichier d'en-tête.
3. La définition de la classe peut être écrite dans le fichier d'en-tête. Parce que lors de la création d'un objet de classe dans un programme, le compilateur ne peut savoir comment l'objet de cette classe doit être disposé lorsque la définition de cette classe est complètement visible, de sorte que les exigences pour la définition d'une classe sont fondamentalement les mêmes que les fonctions en ligne. Par conséquent, c'est une bonne pratique de mettre la définition de classe dans le fichier d'en-tête et d'inclure le fichier d'en-tête dans le fichier .cpp utilisé dans cette classe. Ici, il convient de mentionner que la définition d'une classe contient des membres de données et des membres de la fonction. Les membres de données ne seront pas définis tant que l'objet spécifique sera créé (espace alloué), mais les membres de la fonction doivent être définis dès le début, ce que nous appelons généralement l'implémentation de la classe. Généralement, notre approche consiste à placer la définition de classe dans le fichier d'en-tête et à mettre le code d'implémentation du membre de la fonction dans un fichier .cpp. C'est OK et une bonne façon. Cependant, il y a une autre façon. C'est-à-dire d'écrire directement le code d'implémentation des membres de la fonction dans la définition de classe. Dans les classes C ++, si les membres de la fonction sont définis dans le corps de définition de classe, le compilateur considère la fonction comme en ligne. Par conséquent, il est légal d'écrire la définition des membres de la fonction dans l'organisme de définition de classe et de les assembler dans le fichier d'en-tête. Notez qu'il est illégal d'écrire la définition du membre de la fonction dans le fichier d'en-tête de la définition de classe et non dans la définition de classe, car le membre de la fonction n'est pas en ligne pour le moment. Une fois le fichier d'en-tête inclus dans deux fichiers .cpp ou plus, ce membre de la fonction est redéfini.
5. Mesures de protection dans les fichiers d'en-tête
Considérez que si le fichier d'en-tête ne contient que des instructions de déclaration, ce sera bien s'il est inclus dans le même fichier .cpp plusieurs fois - car la survenue des instructions de déclaration n'est pas restreint. Cependant, les trois exceptions des fichiers d'en-tête discutées ci-dessus sont également une utilisation très courante des fichiers d'en-tête. Ensuite, une fois que l'une des trois exceptions ci-dessus apparaît dans un fichier d'en-tête et qu'elle est incluse plusieurs fois par un .CPP, le problème sera important. Parce que bien que les éléments de syntaxe de ces trois exceptions "peuvent être définis dans plusieurs fichiers source", "ne peut apparaître qu'une seule fois dans un fichier source". Imaginez que si AH contient la définition de la classe A et BH contient la définition de la classe B. Étant donné que la définition de la classe B dépend de la classe A, AH est également # inclus dans BH. Il existe maintenant un fichier source qui utilise à la fois la classe A et la classe B, donc le programmeur comprend à la fois AH et BH dans ce fichier source. Pour le moment, le problème se pose: la définition de la classe A apparaît deux fois dans ce fichier source! L'ensemble du programme ne peut donc pas être compilé. Vous pourriez penser que c'était l'erreur d'un programmeur - il devrait savoir que BH contient AH - mais en fait il ne devrait pas.
L'utilisation de "#define" avec compilation conditionnelle peut bien résoudre ce problème. Dans un fichier d'en-tête, un nom est défini via #Define, et #ifndef ... # endif est compilé conditionnellement afin que le compilateur puisse décider de continuer à compiler le contenu ultérieur dans l'en-tête en fonction de la définition du nom. Bien que cette méthode soit simple, vous devez vous rappeler de l'écrire lors de l'écriture du fichier d'en-tête.
Merci d'avoir lu cet article. J'espère que l'explication détaillée des fichiers d'en-tête et des fichiers source dans C ++ introduit dans cet article peut vous aider. Merci pour votre soutien du nouveau réseau de chaînes technologiques!