Lorsque vous faites appel à la fonction dans le main
, l'ordinateur connaît la fonction et sait où aller la chercher.
En revanche, si vous mettez votre fonction après le main
, ça ne marchera pas… Mais rassurez-vous, les programmeurs ont prévu le coup.
Grâce à ce que je vais vous apprendre maintenant, vous pourrez positionner vos fonctions dans n'importe quel ordre dans le code source !
Annoncez une fonction à l'aide d'un prototype
Regardez la première ligne de notre fonction aireRectangle
:
double aireRectangle(double largeur, double hauteur)
{
return largeur * hauteur;
}
Copiez la première ligne tout en haut de votre fichier source, juste après les
#include
.Rajoutez un point-virgule à la fin de cette nouvelle ligne, comme ceci :
double aireRectangle(double largeur, double hauteur);
Voilà ! Maintenant, vous pouvez placer votre fonction aireRectangle
après la fonction main
si vous le voulez !
Vous devriez avoir le code suivant sous les yeux :
#include <stdio.h>
#include <stdlib.h>
// La ligne suivante est le prototype de la fonction aireRectangle :
double aireRectangle(double largeur, double hauteur);
int main(int argc, char *argv[])
{
printf("Rectangle de largeur 5 et hauteur 10. Aire = %f\n", aireRectangle(5, 10));
printf("Rectangle de largeur 2.5 et hauteur 3.5. Aire = %f\n", aireRectangle(2.5, 3.5));
printf("Rectangle de largeur 4.2 et hauteur 9.7. Aire = %f\n", aireRectangle(4.2, 9.7));
return 0;
}
// Notre fonction aireRectangle peut maintenant être mise n'importe où dans le code source :
double aireRectangle(double largeur, double hauteur)
{
return largeur * hauteur;
}
Ce qui a changé ici, c'est l'ajout du prototype en haut du code source.
Créez les fichiers d'en-tête (header) de vos projets
Un projet, c'est l'ensemble des fichiers sources de votre programme. En général, on crée plusieurs fichiers par projet.
Pour le moment, nos projets n'étaient composés que d'un fichier source : main.c
.
Voyons comment structurer un vrai projet à l’aide de fichiers d’en-tête (ou "header", en anglais)
Créez plusieurs fichiers par projet
L'idée, c'est d'avoir plusieurs fichiers (dont le fichier main.c
qui contient la fonction main
) :
Pourquoi plusieurs ? Combien de fichiers je dois créer pour mon projet ?
En général, on regroupe dans un même fichier des fonctions ayant le même thème :
dans le fichier
view.c
j'ai regroupé toutes les fonctions concernant l'interface graphique ;dans le fichier
controller.c
, j'ai regroupé toutes les fonctions concernant la logique de l'application.
Faites la différence entre les fichiers .h
et .c
En général, on met donc rarement les prototypes dans les fichiers .c
comme on l'a fait tout à l'heure dans le main.c
(sauf si votre programme est tout petit).
Pour chaque fichier .c
, il y a donc son équivalent .h
qui contient les prototypes des fonctions :
controller.c
(le code des fonctions) etcontroller.h
(les prototypes des fonctions) ;view.c
etview.h
, etc.
Mais comment faire pour que l'ordinateur sache que les prototypes sont dans un autre fichier que le .c
?
Il faut inclure le fichier .h
grâce à une directive de préprocesseur. Attention, préparez-vous à comprendre beaucoup de choses d'un coup !
Regardez par exemple le début de mon fichier view.c
:
#include <stdlib.h>
#include <stdio.h>
#include "view.h"
void menu()
{
// ...
L'inclusion se fait grâce à la directive de préprocesseur #include
que vous connaissez bien maintenant. Regardez les premières lignes du code source :
#include <stdlib.h>
#include <stdio.h>
#include "view.h" // On inclut view.h
On inclut trois fichiers .h
:
stdio
;stdlib
;view
.
/*
view.h
-----
Rôle : prototypes des fonctions de view.
*/
void menu();
int choixUtilisateur();
void afficherInfos(const char* infos);
Voilà comment fonctionne un vrai projet ! Un prototype, c'est donc le mode d'emploi de la fonction pour l'ordinateur. Tout est une question d'ordre :
Si vous placez vos prototypes dans des
.h
inclus en haut des.c
, votre ordinateur connaîtra le mode d'emploi de toutes vos fonctions dès le début de la lecture du fichier, et vous n'aurez ainsi pas à vous soucier de l'ordre dans lequel les fonctions se trouvent dans vos fichiers.c
.Si maintenant vous faites un petit programme contenant deux ou trois fonctions, vous vous rendrez peut-être compte que les prototypes semblent facultatifs (ça marche sans). Mais ça ne durera pas longtemps ! Dès que vous aurez un peu plus de fonctions, si vous ne mettez pas vos prototypes de fonctions dans des
.h
, la compilation échouera sans aucun doute.
Comment puis-je ajouter des fichiers .c
et .h
à mon projet ?
Ça dépend de l'IDE que vous utilisez, mais globalement la procédure est la même :Fichier / Nouveau / Fichier source
.
Cela crée un nouveau fichier vide.
C'est aussi simple que cela. Enregistrez votre fichier dans le répertoire dans lequel se trouvent les autres fichiers de votre projet (le même dossier que main.c
). Généralement, vous enregistrerez tous vos fichiers dans le même répertoire, les .c
comme les .h
.
Avec Code::Block, cochez la case "Add file to active project" pour l’ajouter à votre projet. Sinon il faudra l’ajouter manuellement. Pour l'ajouter au projet, faites un clic droit sur le nom de votre projet dans la partie à gauche de l'écran (où il y a la liste des fichiers du projet), et choisissez "Add files".
Le dossier du projet regroupe des .c
et des .h
ensemble.
Maintenant que vous avez compris la théorie, il est temps de pratiquer un peu. Je vous propose de suivre la vidéo suivante, et d’effectuer les mêmes manipulations pour créer un fichier "header" et placer le prototype d'une fonction dedans. Nous allons reprendre l’exemple de la fonction "aireRectangle" :
Les include
des bibliothèques standard
Si on inclut les fichiers stdio.h
et stdlib.h
, c'est donc qu'ils existent quelque part et qu'on peut aller les chercher, non ?
Oui ! Ils sont normalement installés là où se trouve votre IDE.
Il faut généralement chercher un dossier include
: dedans, vous trouverez de très nombreux fichiers ; ce sont des headers (.h
) des bibliothèques standard, c'est-à-dire des bibliothèques disponibles partout (que ce soit sous Windows, Mac, Linux…). Vous y retrouverez donc stdio.h
et stdlib.h
, entre autres.
Vous pouvez les ouvrir si vous voulez, mais ça risque de piquer un peu les yeux. En effet, c'est un peu compliqué (il y a pas mal de choses qu'on n'a pas encore vues). Si vous cherchez bien, vous verrez que ce fichier est rempli de prototypes de fonctions standard, comme printf
par exemple.
OK, je sais maintenant où se trouvent les prototypes des fonctions standard. Mais comment pourrai-je voir le code source de ces fonctions ? Où sont les .c
?
Vous ne les avez pas ! En fait, les fichiers .c
sont déjà compilés (en code binaire, c'est-à-dire en code machine). Il est donc totalement impossible de les lire.
Vous pouvez retrouver les fichiers compilés dans un répertoire appelé lib
(c'est l'abréviation de library
qui signifie "bibliothèque", en français).
Les fichiers compilés des bibliothèques ont l'extension .a
sous Code::Blocks (qui utilise le compilateur appelé mingw
), et ont l'extension .lib
sous Visual Studio (qui utilise le compilateur Visual
). N'essayez pas de les lire : ce n'est absolument pas digeste pour un humain.
Retenez que dans vos fichiers .c
, vous incluez les .h
des bibliothèques standard pour pouvoir utiliser des fonctions standard comme printf
. Votre ordinateur a ainsi les prototypes sous les yeux, et peut vérifier si vous appelez les fonctions correctement, par exemple que vous n'oubliez pas de paramètres.
Découvrez le fonctionnement de la compilation séparée
Comprenez la portée des fonctions et des variables
Nous allons voir quand les variables et les fonctions sont accessibles, c'est-à-dire quand on peut faire appel à elles.
Les variables propres aux fonctions
Lorsque vous déclarez une variable dans une fonction, elle est supprimée de la mémoire à la fin de la fonction :
int triple(int nombre)
{
int resultat = 0; // La variable resultat est créée en mémoire
resultat = 3 * nombre;
return resultat;
} // La fonction est terminée, la variable resultat est supprimée de la mémoire
Regardez ce code :
int triple(int nombre);
int main(int argc, char *argv[])
{
printf("Le triple de 15 est %d\n", triple(15));
printf("Le triple de 15 est %d", resultat); // Erreur
return 0;
}
int triple(int nombre)
{
int resultat = 0;
resultat = 3 * nombre;
return resultat;
}
Dans le main
, j'essaie ici d'accéder à la variable resultat
. Or, comme cette variable resultat
a été créée dans la fonction triple
, elle n'est pas accessible dans la fonction main
!
Créez une variable globale accessible uniquement dans un fichier
Il est possible de déclarer des variables qui seront accessibles dans toutes les fonctions de tous les fichiers du projet, mais généralement il faut éviter de le faire : ça aura l'air de simplifier votre code au début, mais ensuite vous risquez de vous retrouver avec de nombreuses variables accessibles partout, ce qui risquera de vous créer des soucis.
Exemple :
#include <stdio.h>
#include <stdlib.h>
int resultat = 0; // Déclaration de variable globale
void triple(int nombre); // Prototype de fonction
int main(int argc, char *argv[])
{
triple(15); // On appelle la fonction triple, qui modifie la variable globale resultat
printf("Le triple de 15 est %d\n", resultat); // On a accès à resultat
return 0;
}
void triple(int nombre)
{
resultat = 3 * nombre;
}
Ici, ma fonction triple
ne renvoie plus rien (void
). Elle se contente de modifier la variable globale resultat
que la fonction main
peut récupérer.
Ma variable resultat
sera accessible dans tous les fichiers du projet, on pourra donc faire appel à elle dans TOUTES les fonctions du programme.
La variable globale que nous venons de voir était accessible dans tous les fichiers du projet.
Il est possible de la rendre accessible uniquement dans le fichier dans lequel elle se trouve. Ça reste une variable globale quand même, mais disons qu'elle n'est globale qu'aux fonctions de ce fichier, et non à toutes les fonctions du programme.
Pour créer une variable globale accessible uniquement dans un fichier, rajoutez simplement le mot-clé static
devant :
static int resultat = 0;
Conservez la valeur d'une variable avec static
Si vous rajoutez le mot-clé static
devant la déclaration d'une variable à l'intérieur d'une fonction, ça n'a pas le même sens que pour les variables globales. En fait, la variable static
n'est plus supprimée à la fin de la fonction. La prochaine fois qu'on appellera la fonction, la variable aura conservé sa valeur.
Par exemple :
int triple(int nombre)
{
static int resultat = 0; // La variable resultat est créée la première fois que la fonction est appelée
resultat = 3 * nombre;
return resultat;
} // La variable resultat n'est PAS supprimée lorsque la fonction est terminée.
Cela signifie qu'on pourra rappeler la fonction plus tard, et que la variable resultat
contiendra toujours la valeur de la dernière fois.
Voici un petit exemple pour bien comprendre :
int incremente();
int main(int argc, char *argv[])
{
printf("%d\n", incremente());
printf("%d\n", incremente());
printf("%d\n", incremente());
printf("%d\n", incremente());
return 0;
}
int incremente()
{
static int nombre = 0;
nombre++;
return nombre;
}
1 2 3 4
Ici, la première fois qu'on appelle la fonction incremente
, la variable nombre
est créée. Elle est incrémentée à 1, et une fois la fonction terminée la variable n'est pas supprimée.
Lorsque la fonction est appelée une seconde fois, la ligne de la déclaration de variable est tout simplement "sautée". On ne recrée pas la variable, on réutilise la variable qu'on avait déjà créée. Comme la variable valait 4, elle vaudra maintenant 5, puis 6, puis 7, etc.
Définissez des fonctions locales à un fichier
Normalement, quand vous créez une fonction, celle-ci est globale à tout le programme. Elle est accessible depuis n'importe quel autre fichier .c
.
Il se peut que vous ayez besoin de créer des fonctions qui ne seront accessibles que dans le fichier dans lequel se trouve la fonction. Pour faire cela, rajoutez static
devant la fonction :
static int triple(int nombre)
{
// Instructions
}
Pensez à mettre à jour le prototype aussi :
static int triple(int nombre);
En résumé
Un programme contient des fichiers
.c
. Chaque fichier.c
a un petit frère du même nom mais avec l'extension.h
. Le.c
contient les fonctions tandis que le.h
contient les prototypes.Le contenu des fichiers
.h
est inclus en haut des.c
par un programme appelé préprocesseur. Les.c
sont transformés en fichiers.o
binaires par le compilateur. Les.o
sont assemblés en un exécutable (.exe
) par le "linker", aussi appelé éditeur de liens.Une variable déclarée dans une fonction est supprimée à la fin de la fonction, elle n'est accessible que dans cette fonction. Une variable déclarée dans une fonction avec
static
devant n'est pas supprimée à la fin de la fonction, elle conserve sa valeur au fur et à mesure de l'exécution du programme.Une variable déclarée en dehors des fonctions est une variable globale, accessible depuis toutes les fonctions de tous les fichiers source du projet. Une variable globale avec
static
devant est globale uniquement dans le fichier dans lequel elle se trouve, elle n'est pas accessible depuis les fonctions des autres fichiers.Une fonction est par défaut accessible depuis tous les fichiers du projet, on peut donc l'appeler depuis n'importe quel autre fichier. Si on veut qu'une fonction ne soit accessible que dans le fichier dans lequel elle se trouve, il faut rajouter
static
devant.
Je vous sens prêt pour attaquer un gros morceau du langage C : les pointeurs ! J’espère que vous êtes bien attaché, car ce nouveau concept est costaud. Mais pas de panique, c’est loin d’être insurmontable !