Dans le chapitre précédent, je vous ai dit que le but des fonctions était de pouvoir réutiliser les "briques" déjà créées. Pour le moment, les fonctions que vous savez créer se situent à côté de la fonction main()
. On ne peut donc pas vraiment les réutiliser.
Créez un fichier source et un fichier d'en-tête
Pour faire les choses proprement, il ne faut pas un mais deux fichiers :
Un fichier source dont l'extension est
.cpp
: il contient le code source de la fonction.Un fichier d'en-tête dont l'extension est
.hpp
: il contient uniquement la description de la fonction, ce qu'on appelle le prototype de la fonction.
Créons donc ces deux fichiers pour notre fonction ajouteDeux()
:
int ajouteDeux(int nombreRecu)
{
int valeur(nombreRecu + 2);
return valeur;
}
Maintenant, voyons comment créer un fichier source et un fichier d’en-tête :
Déclarez la fonction dans les fichiers
Nous avons nos deux fichiers, il ne reste qu'à les remplir !
Le fichier source
Le compilateur a besoin de savoir que les fichiers .cpp
et .hpp
ont un lien entre eux. Il faut donc commencer le fichier par la ligne suivante :
#include "math.hpp"
Cette ligne indique que l'on va utiliser ce qui se trouve dans le fichier math.hpp
.
Le fichier math.cpp
au complet est donc :
#include "math.hpp"
int ajouteDeux(int nombreRecu)
{
int valeur(nombreRecu + 2);
return valeur;
}
Le fichier d'en-tête
Si vous regardez le fichier qui a été créé, il n'est pas vide ! Il contient trois lignes mystérieuses :
#ifndef MATH_HPP_INCLUDED
#define MATH_HPP_INCLUDED
#endif // MATH_HPP_INCLUDED
Ces lignes sont là pour empêcher le compilateur d'inclure plusieurs fois ce fichier. Le compilateur n'est parfois pas très malin, et risque de tourner en rond. Cette astuce évite donc de se retrouver dans cette situation.
Dans ce fichier, il faut mettre ce qu'on appelle le prototype de la fonction. C'est la première ligne de la fonction, celle qui vient avant les accolades. On copie le texte de cette ligne et on ajoute un point-virgule à la fin :
#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED
int ajouteDeux(int nombreRecu);
#endif // MATH_H_INCLUDED
Si vous utilisez des variables plus compliquées en argument, comme des string
(cela vaut aussi pour les tableaux, concept que nous verrons dans le prochain chapitre), il faut ajouter la ligne d'inclusion #include <string>
avant le prototype :
#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED
#include <string>
void afficherMessage(std::string message);
#endif // MATH_H_INCLUDED
L'autre élément important est l'ajout de std::
devant le mot string
. Il s'agit de la marque d'un espace de nom.
std
apparaît dans tous nos fichiers source via la ligne using namespace std
. Comme il n'y a pas de telle ligne ici (et qu'il est très vivement déconseillé de la mettre dans un fichier d'en-tête), il nous faut utiliser le nom complet du type string
, qui est std::string
.
Il ne nous reste qu'une seule chose à faire : inclure tout cela dans le fichier main.cpp
.
Il faut donc ajouter au début de notre programme la ligne suivante :
#include "math.hpp"
Ce qui donne :
#include <iostream>
#include "math.hpp"
using namespace std;
int main()
{
int a(2),b(2);
cout << "Valeur de a : " << a << endl;
cout << "Valeur de b : " << b << endl;
b = ajouteDeux(a); //Appel de la fonction
cout << "Valeur de a : " << a << endl;
cout << "Valeur de b : " << b << endl;
return 0;
}
Et voilà ! Nous avons maintenant des briques séparées utilisables dans plusieurs programmes.
Si vous voulez utiliser la fonction ajouteDeux()
dans un autre projet, il vous suffira de copier les fichiers math.cpp
et math.hpp
.
Commentez vos fonctions dans votre code
Rappelez-vous : c'est important de mettre des commentaires dans son programme pour comprendre ce qu'il fait. Et c'est particulièrement vrai pour les fonctions.
Comme il y a de la place dans les fichiers d'en-tête, on en profite généralement pour y préciser :
ce que fait la fonction ;
la liste des ses arguments ;
la valeur retournée.
Voici ce qu'on pourrait écrire pour la fonction ajouteDeux()
:
#ifndef MATH_HPP_INCLUDED
#define MATH_HPP_INCLUDED
/*
* Fonction qui ajoute 2 au nombre reçu en argument
* - nombreRecu : Le nombre auquel la fonction ajoute 2
* Valeur retournée : nombreRecu + 2
*/
int ajouteDeux(int nombreRecu);
#endif // MATH_HPP_INCLUDED
Le célèbre système doxygen utilise par exemple la notation suivante :
/**
* \brief Fonction qui ajoute 2 au nombre reçu en argument
* \param nombreRecu Le nombre auquel la fonction ajoute 2
* \return nombreRecu + 2
*/
int ajouteDeux(int nombreRecu);
Donnez des valeurs par défaut pour les arguments
Lorsque une fonction a trois arguments, il faut lui fournir trois valeurs pour qu'elle puisse fonctionner. Eh bien, je vais vous montrer que ce n'est pas toujours le cas…
Voyons tout cela avec la fonction suivante :
int nombreDeSecondes(int heures, int minutes, int secondes)
{
int total = 0;
total = heures * 60 * 60;
total += minutes * 60;
total += secondes;
return total;
}
Cette fonction calcule un nombre de secondes à partir d'un nombre d'heures, de minutes et de secondes qu'on lui transmet. Rien de bien compliqué !
Les variables heures
, minutes
et secondes
sont les paramètres de la fonction nombreDeSecondes()
. Ce sont des valeurs qu'elle reçoit, celles avec lesquelles elle va travailler.
Les valeurs par défaut
Pour bien voir comment on doit procéder, on va regarder le code complet. J'aimerais que vous l'écriviez dans votre IDE pour faire les tests en même temps que moi :
#include <iostream>
using namespace std;
// Prototype de la fonction
int nombreDeSecondes(int heures, int minutes, int secondes);
// Main
int main()
{
cout << nombreDeSecondes(1, 10, 25) << endl;
return 0;
}
// Définition de la fonction
int nombreDeSecondes(int heures, int minutes, int secondes)
{
int total = 0;
total = heures * 60 * 60;
total += minutes * 60;
total += secondes;
return total;
}
Ce code donne le résultat suivant :
4225
Sachant que 1 heure = 3600 secondes, 10 minutes = 600 secondes, 25 secondes =… 25 secondes, le résultat est logique car 3600 + 600 + 25 = 4225. Bref, tout va bien.
Maintenant, supposons que l'on veuille rendre certains paramètres facultatifs, par exemple parce qu'on utilise en pratique plus souvent les heures que le reste. On va devoir modifier le prototype de la fonction (et non sa définition, attention).
Indiquez la valeur par défaut que vous voulez donner aux paramètres s'ils ne sont pas renseignés lors de l'appel à la fonction :
int nombreDeSecondes(int heures, int minutes = 0, int secondes = 0);
Dans cet exemple, seul le paramètre heures
sera obligatoire, les deux autres étant désormais facultatifs. Si on ne renseigne pas les minutes et les secondes, les variables correspondantes vaudront alors 0 dans la fonction.
Voici le code complet que vous devriez avoir sous les yeux :
#include <iostream>
using namespace std;
// Prototype avec les valeurs par défaut
int nombreDeSecondes(int heures, int minutes = 0, int secondes = 0);
// Main
int main()
{
cout << nombreDeSecondes(1, 10, 25) << endl;
return 0;
}
// Définition de la fonction, SANS les valeurs par défaut
int nombreDeSecondes(int heures, int minutes, int secondes)
{
int total = 0;
total = heures * 60 * 60;
total += minutes * 60;
total += secondes;
return total;
}
Bon, ce code ne change pas beaucoup du précédent. À part les valeurs par défaut dans le prototype, rien n'a été modifié (et le résultat à l'écran sera toujours le même).
La nouveauté maintenant, c'est qu'on peut supprimer des paramètres lors de l'appel de la fonction (ici dans le main()
). On peut par exemple écrire :
cout << nombreDeSecondes(1) << endl;
Le compilateur lit les paramètres de gauche à droite. Comme il n'y en a qu'un et que seules les heures sont obligatoires, il devine que la valeur1
correspond à un nombre d'heures.
Le résultat à l'écran sera le suivant :
3600
Mieux encore, vous pouvez indiquer seulement les heures et les minutes si vous le désirez :
cout << nombreDeSecondes(1, 10) << endl;
4200
Tant que vous indiquez au moins les paramètres obligatoires, il n'y a pas de problème.
Cas particuliers, attention danger
Bon, mine de rien il y a quand même quelques pièges, ce n'est pas si simple que cela !
On va voir ces pièges sous la forme de questions / réponses :
Et si je veux envoyer à la fonction juste les heures et les secondes, mais pas les minutes ?
Tel quel, c'est impossible. Le compilateur analyse les paramètres de gauche à droite : le premier correspondra forcément aux heures, le second aux minutes et le troisième aux secondes.
Vous ne pouvez PAS écrire :
cout << nombreDeSecondes(1,,25) << endl;
Si vous voulez indiquer le premier et le dernier paramètre, il vous faudra obligatoirement spécifier ceux du milieu. On devra donc écrire :
cout << nombreDeSecondes(1, 0, 25) << endl;
Est-ce que je peux rendre seulement les heures facultatives, et rendre les minutes et secondes obligatoires ?
Si le prototype est défini dans le même ordre que tout à l'heure : non.
Les paramètres facultatifs doivent obligatoirement se trouver à la fin (à droite).
Ce code ne compilera donc pas :
int nombreDeSecondes(int heures = 0, int minutes, int secondes);
//Erreur, les paramètres par défaut doivent être à droite
La solution, pour régler ce problème, consiste à placer le paramètre heures
à la fin :
int nombreDeSecondes(int secondes, int minutes, int heures = 0);
//OK
Est-ce que je peux rendre tous mes paramètres facultatifs ?
Oui, cela ne pose pas de problème :
int nombreDeSecondes(int heures = 0, int minutes = 0, int secondes = 0);
Dans ce cas, l'appel de la fonction pourra s'écrire comme ceci :
cout << nombreDeSecondes() << endl;
Le résultat renvoyé sera bien entendu 0 dans le cas ci-dessus.
En résumé
Lorsque le programme grossit, il est conseillé de créer plusieurs fichiers regroupant des fonctions. Les fichiers
.cpp
contiennent les définitions des fonctions et les fichiers.hpp
, plus courts, contiennent leurs prototypes. Les fichiers.hpp
permettent d'annoncer l'existence des fonctions à l'ensemble des autres fichiers du programme.Seul le prototype doit contenir les valeurs par défaut (pas la définition de la fonction) ;
Les valeurs par défaut doivent se trouver à la fin de la liste des paramètres (c'est-à-dire à droite).
Dans les prochain chapitre et le suivant, je vais vous apprendre à manipuler deux sortes de tableaux : les tableaux statiques (leur taille est connue à l'avance ; par exemple une liste qui reprendrait le classement des dix meilleurs scores obtenus lors d'une partie de jeu en ligne) ; et les tableaux dynamiques (leur taille peut varier en permanence, comme la liste des visiteurs d'un site web qui, généralement, ne cesse de grandir). On y va !