Jusqu'à présent, les programmes que nous réalisons sont de toute petite dimension. Toutefois, lorsque nous construisons un véritable projet, il est assez fréquent d'avoir plusieurs milliers de ligne de code. Il n'est pas envisageable, dans ce cas là, que toutes ces lignes soient écrites sur un seul et unique fichier. C'est trop difficile à maintenir et lorsque que nous devons modifier une seule ligne, il est nécessaire de tout recompiler, ce qui représente un temps considérable vu le petit changement réalisé.
Par ailleurs, pour faciliter l'élaboration d'un projet, il est préférable de le découper et de le structurer en plusieurs fonctions. Cette approche a également le mérite de favoriser le travail en équipe. Toutefois, pour que cela soit vraiment efficace, il faut découper le projet en plusieurs fichiers, pour que chacun s'occupe de sa propre tâche.
Pour bien comprendre les mécanismes en jeu, nous allons revenir sur la notion de compilation. Rappelons que la compilation consiste à réaliser une traduction d'un langage de programmation (ici le C++) vers le langage binaire, le seul qui soit compréhensible par le microprocesseur. Cette compilation s'effectue en trois phases :
Nous allons élaborer un projet avec plusieurs fichiers sources. Chacun de ces fichiers disposera d'une petite fonction. Il est vrai que normalement, nous n'aurions pas eu besoin d'avoir autant de fichiers. Nous allons juste procéder de cette façon pour bien maîtriser les différents concepts mis en jeu.




Lorsque nous lançons la compilation, nous obtenons une erreur au moment de l'évocation de la fonction impaire dans le fichier "Principal.cpp". Une fonction doit toujours être connue, soit par une définition au préalable, soit par une simple déclaration. Comme les fonctions ne sont pas définies dans le même fichier, nous sommes obligés de prendre la deuxième solution. D'ailleurs, la déclaration des fonctions a justement été créée pour répondre à ce genre de problème.

En introduction, nous avons évoqués trois phases de compilation. La première n'est pas utilisée puisqu'il n'y a aucun texte à changer. Il reste donc la compilation qui opère la traduction et produit les fichiers binaires correspondant : "Principal.obj" , "impaire.obj", "paire.obj".
A partir de là, c'est ensuite l'éditeur de lien qui prend le relais et qui, comme son nom l'indique, assure la liaison entre les différents modules compilés pour créer un seul fichier qui deviendra l'exécutable de notre application, savoir : "Principal.exe".

Quelque soit le système de développement intégré, lorsque nous demandons une compilation, le système fait appel au ‘make' qui est un outil très puissant qui s'occupe de toutes les phases de compilation. Cet outil make n'effectue la compilation que sur les fichiers sources qui ont subits une modification. De même, l'édition de lien ne sera lancée que si, au moins, un fichier compilé a subi une modification. Avec ce principe le temps de compilation devient extrêmement réduit et c'est un des atouts de la gestion de projets multi fichiers.
Vous pouvez toutefois imposer de tout reconstruire même si certains fichiers sources n'ont pas été modifiés. Dans ce cas là, tout se passe comme si toutes les phases de compilation étaient faites pour la première fois.
Une fois que vous êtes sûrs de vos fichiers et que vous avez réalisés tous les tests nécessaires en contrôlant et en validant le bon déroulement de chacune des fonctions, vous n'êtes alors plus obligés de placer les sources dans votre projet. Cela vous évite de faire de mauvaises manipulations et vous permet également de cacher l'implémentation interne. Il suffit de placer dans votre projet, uniquement les fichiers compilés, puisque en définitive, l'éditeur de lien ne se sert que de ceux là.

Il arrive assez souvent que les fonctions que nous construisons soient utiles pour d'autres projets. Il suffit de procéder de la même manière en plaçant les fichiers compilés dans le nouveau projet. N'oubliez pas de faire les déclarations nécessaires dans votre programme principal.
Par contre, la manipulation peut être fastidieuse si vous devez placer de nombreux fichiers compilés dans votre projet. Il est préférable, dans ce cas là, de fabriquer une bibliothèque qui rassemble les fichiers compilés dans un seul et unique fichier. Cette façon de procéder est intéressante, surtout si les fichiers compilés correspondent à une même rubrique. C'est justement notre cas. Les fichiers annexes « impaire.obj » et « paire.obj » représentent des fonctions mathématiques.
Les bibliothèques sont aussi souvent appelées des librairies. D'ailleurs, l'extension proposée est « *.lib ». A ce sujet, il faut savoir que systématiquement, les outils de développement intégrés, place au moins une librairie qui stocke toutes les définitions des fonctions standards du langage C++ comme les fonctions d'affichage, de saisie, etc. Cette librairie s'appelle « c.lib » (suivant les outils, le nom peut être différent, mais il y a toujours la lettre c dedans). C'est notamment pour cette raison que la taille du fichier exécutable peut sembler conséquente.

Les environnements de développement intégrés permettent de créer de nouvelles bibliothèques. Dans le projet, au lieu de fabriquer un exécutable, nous demandons à la place de fabriquer une bibliothèque. Dans ce cas là, soit nous utilisons les fichiers déjà compilés, soit nous démarrons avec les sources qui seront donc compilés avant d'être intégrés dans la bibliothèque. Dans notre exemple, nous allons mettre en œuvre une bibliothèque « Mathématique ».


Une fois que la bibliothèque est créée, il est possible de l'utiliser dans n'importe quel projet. Nous allons d'ailleurs intégrer cette bibliothèque dans le projet de départ pour bien voir la différence.

Grâce à toutes ces techniques, nous avons beaucoup progressé dans l'élaboration d'un projet. La situation est devenue assez satisfaisante, mais pas tout a fait. Imaginons que nous ayons besoin d'une dizaine de fonctions issues d'une bibliothèque. Nous sommes obligés de déclarer systématiquement ces dix fonctions à chacun des fichiers que nous développons. Cette démarche devient fastidieuse. Il est préférable que ces déclarations soient faites automatiquement en liaison avec la bibliothèque utilisée.
Il existe un fichier qui s'occupe de toutes les déclarations nécessaires à une bibliothèque. Il s'agit des fichiers en-têtes. Généralement, le nom du fichier inclus porte le même nom que la bibliothèque suivi de l'extension « *.h » (header – en-tête). Toutefois, si la librairie est conséquente, il est possible d'avoir plusieurs fichiers en-têtes qui sont établis par thème d'étude. C'est le cas avec le fichier en-tête « iostream.h » qui ne dispose que des déclarations relatives aux entrées-sorties.
Il faut maintenant que le contenu du fichier en-tête soit copié automatiquement dans le fichier source requérant ces déclarations. Il s'agit d'une copie de texte, et c'est le préprocesseur qui s'occupe de ce genre d'intervention. Dans ce cas, il faut indiquer au préprocesseur le traitement à réaliser en utilisant des directives appropriés. Les directives du préprocesseur sont spécifiées par un dièse « # » dans la toute première colonne d'une ligne de programme. Pour l'inclusion, il faut utiliser la directive « #include » suivi du nom du fichier à inclure. Il existe trois syntaxes :

Pour notre projet, nous fabriquons un fichier en-tête « mathématique.h » relatif à la bibliothèque « Mathématique.lib » qui comporte les déclarations des deux fonctions. Il faut penser à changer le fichier source de la fonction paire parce qu'elle-même fait référence à la fonction impaire et doit donc disposer de sa déclaration.
Le fait d'utiliser des fichiers en-têtes nous évite de se souvenir de toutes les déclarations. Il suffit de placer la directive d'inclusion sans aucun tracas particulier. Il est vrai que nous aurions pu laisser la déclaration de la fonction impaire dans le fichier source de la fonction paire plutôt que de proposer l'inclusion. En effet, seule la déclaration de la fonction impaire nous intéresse à ce moment là. A vous de choisir la meilleure opportunité.
Dans cet exemple simplifié au maximum, vous remarquez malgré tout qu'un même fichier en-tête peut être sollicité plusieurs fois. Du coup, le temps de compilation peut augmenter considérablement par ces ouvertures successives. Surtout pour relire systématiquement les mêmes choses alors que le préprocesseur, lui, est capable de mémoriser les déclarations qui ont déjà été faites. Il est souhaitable de prévenir ce genre de problème en proposant des compilations conditionnelles.
| Constantes symboliques | |
| #define identificateur | Permet de définir un paramètre de nom identificateur qui pourra être utilisé dans une clause #if. Tant que le préprocesseur n'est pas passé sur cette ligne, l'identificateur n'est pas encore connu. Par contre, après lecture de cette ligne, cet identificateur est définitivement validé (sauf avis contraire grâce à la directive #undef ). |
| #define PI 3.141592 | Sert à effectuer un changement de texte en remplaçant un symbole par un autre ou par une constante. Chaque fois que le symbole PI sera rencontré, le préprocesseur le remplacera par la constante 3.141592. il est toutefois préférable d'utiliser const pour gérer les constantes. |
| Compilation conditionnelle | |
| Les directives conditionnelles permettent d'incorporer ou d'exclure de la compilation des portions de texte de programme selon que l'évaluation de la condition donne vrai ou faux comme résultat. | |
| #ifdef identificateur | Inclusion du texte qui suit cette ligne si l'identificateur est connu, c'est-à-dire s'il a déjà été défini. |
| #ifndef identificateur | Inclusion du texte qui suit cette ligne si l'identificateur n'est pas encore connu. |
| #else | Clause sinon associée à #ifdef ou à #ifndef |
| #endif | Fin du si associé à #ifdef ou à #ifndef |
| #undef | Met fin à l'existence d'un identificateur associé à un #define |
Pour être sûr que notre fichier en-tête soit lu qu'une seule fois, nous allons donc lui spécifier une compilation conditionnelle.

Attention : Ces fichiers ne doivent absolument pas contenir de définition, seulement des déclarations, ou tout ce qui se rapporte à du traitement de texte comme, par exemple, les fonctions « en ligne ».
Il est donc possible d'avoir :
Avec ce qui vient d'être dit, vous avez un aperçu de ce que nous ne devons absolument pas retrouver dans un fichier en-tête.
Pour notre projet, nous allons changer de stratégie. Comme les fonctions utilisées sont très réduites, il est souhaitable de les écrire en ligne. Elles vont donc se retrouver dans le fichier en-tête. Nous aurions pu le prévoir dès le commencement, mais le but poursuivi était de bien maîtriser les mécanismes des projets multi fichiers.
