Jeux Libres
       
           

» Les Tutoriels » Apprenez à programmer en C++ ! » Découper son programme en fonctions

Note : Vous vous apprêtez à lire un tutoriel de M@teo21 et Nanoc initialement publié à cette adresse sous la licence Creative Commons BY-NC-SA 2.0.

Découper son programme en fonctions


Nous venons de voir comment faire varier le déroulement d'un programme en utilisant des boucles et des branchements. Avant ça, je vous ai parlé des variables. Ce sont des éléments qui se retrouvent dans tous les langages de programmation. C'est aussi le cas de la notion que nous allons aborder dans ce chapitre : les fonctions.

Tous les programmes C++ utilisent des fonctions et vous en avez aussi utilisé plusieurs sans le savoir. :euh:
Le but des fonctions est de découper son programme en petits éléments réutilisables, un peu comme des briques. On peut les assembler d'une certaine manière pour créer un mur, ou d'une autre manière pour faire un cabanon ou même un gratte-ciel. Une fois que les briques sont créées, la tâche du programmeur ne consiste "plus qu'à" les assembler.

Commençons par créer des briques. Nous apprendrons à les utiliser dans un deuxième temps.





Chapitre précédent     Sommaire     Chapitre suivant


Créer et utiliser une fonction


Dès le début de ce cours, nous avons utilisé des fonctions, toujours la même en fait. La fonction main(). C'est le point d'entrée de tous les programmes C++, c'est par là que tout commence.
1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
 
int main() //Debut de la fonction main() et donc du programme
{
 cout << "Bonjour tout le monde !" << endl;
 return 0;
} //Fin de la fonction main() et donc du programme

Le programme commence réellement à la ligne 4 et se termine à la ligne 8 après l'accolade fermante. C'est-à-dire que tout se déroule dans une seule et unique fonction. On n'en sort pas. Il n'y a qu'une seule portion de code qui est exécutée dans l'ordre sans jamais sauter ailleurs.

Alors, si je vous dit tout ça, c'est vous vous en doutez, que l'on peut écrire d'autres fonctions. Et donc avoir un programme découpé en plusieurs modules indépendants.

Pourquoi vouloir faire ça ?


C'est vrai après tout, mettre l'entièreté du code dans la fonction main() est tout à fait possible. Ce n'est cependant pas une bonne pratique.

Imaginons que nous voulions créer un jeu vidéo 3D. Comme c'est quand même assez complexe, le code source va nécessiter plusieurs dizaines de milliers de lignes ! :( Si l'on garde tout dans une seule fonction, il va être très difficile de s'y retrouver. Il serait certainement plus simple d'avoir un morceau de code dans un coin qui fait bouger un personnage et un autre bout de code ailleurs qui charge les niveaux, etc. Découper son programme en fonctions permet de s'organiser.
En plus, si vous êtes plusieurs programmeurs à travailler sur le même programme, vous pourrez vous partager plus facilement le travail. Chacun travaille sur une fonction différente.

Mais ce n'est pas tout !
Prenons par exemple le calcul de la racine carrée. Si vous créez un programme de maths, il est bien possible que vous ayez besoin à plusieurs endroits d'effectuer des calculs de racine. Avoir une fonction sqrt() va nous permettre de faire plusieurs de ces calculs sans avoir à recopier le même code à plusieurs endroits. On peut utiliser plusieurs fois la même fonction et c'est une des raisons principales d'en écrire.

Présentation des fonctions



Une fonction est un morceau de code qui accomplit une tâche particulière. Elle reçoit des données à traiter, effectue des actions avec et finalement renvoie une valeur.

Les données entrantes s'appellent les arguments et on utilise l'expression valeur retournée pour les éléments qui sortent de la fonction.


Vous vous souvenez de pow() ? La fonction qui permet de calculer des puissances ? En utilisant le nouveau vocabulaire, on peut dire que cette fonction :

  • reçoit deux arguments.
  • effectue un calcul mathématique.
  • renvoie le résultat du calcul.

En utilisant un schéma comme le précédent, on peut imaginer pow() comme ceci :


Vous en avez déjà fait l'expérience, on peut utiliser cette fonction plusieurs fois. Ce qui implique que nous ne sommes pas obligés de copier le code (compliqué) qui se trouve à l'intérieur de pow() à chaque fois que l'on souhaite effectuer un calcul de puissance.

Définir une fonction



Bon bon, il est temps d'attaquer le concret. Il faut que je vous montre comment définir une fonction. Je pourrais vous dire de regarder comment main() est fait et vous laisser patauger, mais je suis sympa. Je vais vous guider ;)

Vous êtes prêt ? Alors allons-y !

Toutes les fonctions ont la forme suivante :
1
2
3
4
type nomFonction(arguments)
{
   //Instructions effectues par la fonction
}

On retrouve les trois éléments dont je vous ai déjà parlé auquels s'ajoute le nom de la fonction.

  • Le premier élément est le type de retour. Il permet d'indiquer le type de variable renvoyé par la fonction. Si votre fonction doit renvoyer du texte, alors ce sera string, si votre fonction effectue un calcul, alors ce sera int ou double.
  • Le deuxième élément est le nom de la fonction. Vous connaissez déjà main(), pow() ou sqrt(). L'important est de choisir un nom de fonction qui décrit bien ce que fait la fonction. Comme pour les variables en fait. ;)
  • Entre les parenthèses, on trouve la liste des arguments de la fonction. Ce sont les données avec lesquelles la fonction va travailler. Il peut y avoir un argument (comme pour sqrt()), plusieurs arguments (comme pour pow()) ou aucun argument (comme pour main()).
  • Finalement, il y a des accolades qui délimitent le contenu de la fonction. Toutes les opérations qui seront effectuées se trouvent entre les deux accolades.

Il est possible de créer plusieurs fonctions ayant le même nom. Il faut alors que la liste des arguments des deux fonctions soit différente. C'est ce qu'on appelle la surcharge d'une fonction.

Dans un même programme, il peut par exemple y avoir la fonction int addition(int a, int b) et la fonction double addition(double a, double b). Les deux fonctions ont le même nom mais une travaille avec des entiers et l'autre avec des nombres réels.


Créons donc des fonctions !

Une fonction toute simple



Commençons par une fonction basique. Une fonction qui reçoit un nombre entier, ajoute 2 à ce nombre et le renvoie.
1
2
3
4
5
6
7
8
9
int ajouteDeux(int nombreRecu)
{
   int valeur(0);           //On cree une case en memoire.
   valeur = nombreRecu + 2; //On prend le nombre recu en argument, on y ajoute 2.
                            //Et on met tout a dans la memoire.
 
   return valeur;           //On indique que la valeur qui sort de la fonction
                            //est la valeur de la variable 'valeur'
}

Il n'y a pas de point-virgule ! Ni après la déclaration, ni après l'accolade fermante.


Analysons ce code en détail. Il y a deux lignes vraiment nouvelles pour vous.

Avec ce que je vous ai expliqué, vous devriez comprendre la première ligne. On déclare une fonction nommée ajouteDeux qui va recevoir un nombre entier en argument et qui, une fois qu'elle aura terminé, va recracher un autre nombre entier.


Toutes les lignes suivantes utilisent des choses déjà connues sauf l'avant-dernière. Si vous vous posez des questions sur ces lignes, je vous invite à relire le chapitre sur l'utilisation de la mémoire.

Le return de l'avant-dernière ligne indique quelle valeur va ressortir de la fonction. En l'occurrence, c'est la valeur de la variable valeur qui va être renvoyée.

Appeler une fonction



Bon, c'est bien joli tout ça, mais il faut encore apprendre à l'utiliser cette fonction. C'est vrai, mais vous savez déjà le faire. Souvenez-vous des fonctions mathématiques !
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
int ajouteDeux(int nombreRecu)
{
   int valeur(0);
   valeur = nombreRecu + 2;
 
   return valeur;
}
 
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;
}

Plusieurs paramètres



On n'est pas encore au bout de nos peines. Il y a des fonctions qui prennent plusieurs paramètres, comme pow() et getline() par exemple.

Pour passer plusieurs paramètres à une fonction, il faut les séparer par des virgules.
1
2
3
4
5
6
7
8
9
int addition(int a, int b)
{
   return a+b;
}
 
double multiplication(double a, double b, double c)
{
   return a*b*c;
}

La première de ces fonctions calcule la somme des deux nombres qui lui sont fournis alors que la deuxième calcule le produit des trois nombres reçus.

Vous pouvez bien sûr écrire des fonctions qui prennent des arguments de type différent. ;)


Pas d'arguments



A l'inverse, il est aussi possible de créer des fonctions sans arguments. o_O Il faut simplement ne rien écrire entre les parenthèses !

Mais à quoi ça sert ?


On peut imaginer plusieurs scénarios, mais pensez par exemple à une fonction qui demande à l'utilisateur d'entrer son nom. Elle n'a pas besoin de paramètres.
1
2
3
4
5
6
7
string demanderNom()
{    
    string nom;
    cout << "Entrez votre nom : ";
    cin >> nom;
    return nom;
}

Je suis sûr que vous trouverez plein d'exemples par la suite ! Même si c'est vrai que ce type de fonctions est plus rare. ;)

Des fonctions qui ne renvoient rien



Tous les exemples que je vous ai donnés jusque-là prenaient des arguments et renvoyaient une valeur. Mais il est aussi possible d'écrire des fonctions qui ne renvoient rien. Enfin presque.
Rien ne ressort de la fonction, mais quand on la déclare, il faut quand même indiquer un type. On utilise le "type" void, ce qui signifie néant en anglais. Ça veut tout dire, il n'y a réellement rien qui ressort de la fonction. ;)
1
2
3
4
5
6
7
8
9
10
11
12
void direBonjour()
{
   cout << "Bonjour !" << endl;
   //Comme rien ne ressort, il n'y a pas de return !
}
 
int main()
{
   direBonjour();    //Comme la fonction ne renvoie rien, on l'appelle
                     //sans mettre la valeur de retour dans une variable
   return 0;
}

Avec ce dernier point, nous avons fait le tour de la théorie. Dans la suite du chapitre, je vous propose quelques exemples et un super schéma récapitulatif. Ce n'est pas le moment de partir.


Quelques exemples


Le carré



Commençons de manière simple. Calculer le carré d'un nombre. Cette fonction reçoit un nombre x en argument et calcule la valeur de x2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
 
double carre(double x)
{
   double resultat;
   resultat = x*x;
   return resultat;
}
 
int main()
{
   double nombre, carreNombre;
   cout << "Entrez un nombre : ";
   cin >> nombre;
 
   carreNombre = carre(nombre); //On utilise la fonction
 
   cout << "Le carre de " << nombre << " est " << carreNombre << endl;
   return 0;
}

Je vous avais promis un schéma, le voilà. Voyons ce qui se passe dans ce programme et dans quel ordre les lignes sont exécutées.


Il y a une chose dont il faut absolument se rappeler. Les valeurs des variables transmises aux fonctions sont copiées dans de nouvelles cases mémoires. La fonction carre() n'agit donc pas sur les variables déclarées dans la fonction main(). Elle travaille uniquement avec ses propres cases mémoires.
Ce n'est que lors du return que les variables de main() sont modifiées. Ici la variable carreNombre. La variable nombre reste inchangée lors de l'appel à la fonction.

Réutiliser la même fonction



L'intérêt d'utiliser une fonction ici est bien sûr de pouvoir calculer facilement le carré de différents nombres. Par exemple de tous les nombres entre 1 et 20 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
 
double carre(double x)
{
   double resultat;
   resultat = x*x;
   return resultat;
}
 
int main()
{
   for(int i(1); i<=20 ; i++)
   {
       cout << "Le carre de " << i << " est : " << carre(i);
   }
   return 0;
}

On écrit une seule fois la "formule" du calcul du carré et ensuite on utilise cette "brique" vingt fois. Ici, le calcul est simple, mais dans bien des cas, utiliser une fonction raccourcit beaucoup le code !

Une fonction à deux arguments



Avant de terminer cette partie, voici un dernier exemple. Cette fois, je vous propose une fonction utilisant deux arguments. Nous allons dessiner un rectangle d'étoiles * dans la console. La fonction a besoin de deux arguments : la largeur et la hauteur du rectangle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
 
void dessineRectangle(int l, int h)
{
   for(int ligne(0); ligne < h; ligne++)
   {
       for(int colonne(0); colonne < l; colonne++)
       {
           cout << "*";
       }
       cout << endl;
   }
}
 
int main()
{
   int largeur, hauteur;
   cout << "Largeur du rectangle : ";
   cin >> largeur;
   cout << "Hauteur du rectangle : ";
   cin >> hauteur;
   
   dessineRectangle(largeur, hauteur);
   return 0;
}

Une fois compilé ce programme s'exécute et donnera par exemple :
1
2
3
4
5
Largeur du rectangle : 16
Hauteur du rectangle : 3
****************
****************
****************

Voilà la première version d'un logiciel de dessin révolutionnaire !

Cette fonction ne fait qu'afficher du texte. Elle n'a donc pas besoin de renvoyer quelque chose. C'est pour ça, qu'elle est déclarée avec le "type" void.
On peut facilement modifier la fonction pour qu'elle renvoie la surface du rectangle. A ce moment-là, il faudra qu'elle renvoie un int.

Essayez de modifier cette fonction ! Voici deux idées :

  • Afficher un message d'erreur si la hauteur ou la largeur est négative.
  • Ajouter un argument pour le symbole à utiliser lors du dessin.

Amusez-vous bien. C'est important de bien maîtriser tous ces concepts.

La fin de ce chapitre est consacrée à trois notions un peu plus avancées. Vous pourrez toujours y revenir plus tard si nécessaire.


Passage par valeur et passage par référence


La première des choses avancées dont il faut que je vous parle c'est la manière dont l'ordinateur gère la mémoire avec les fonctions.

Passage par valeur



Prenons une fonction simple qui ajoute simplement deux à son argument. Vous commencez à bien la connaître. Je l'ai donc modifiée un poil. :p
1
2
3
4
5
int ajouteDeux(int a)
{
   a+=2;
   return a;
}

Utiliser += ici est volontairement bizarre ! Vous verrez tout de suite pourquoi.


Testons donc cette fonction. Je pense ne rien vous apprendre en vous proposant le code suivant.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
 
int ajouteDeux(int a)
{
   a+=2;
   return a;
}
 
int main()
{
   int nombre(4), resultat;
   resultat = ajouteDeux(nombre);
   
   cout << "Le nombre original vaut : " << nombre << endl;
   cout << "Le resultat vaut : " << resultat << endl;
   return 0;
}

Ce qui donne sans surprise :
1
2
Le nombre original vaut : 4
Le resultat vaut : 6

L'étape intéressante est bien sûr ce qui se passe à la ligne 13. Vous vous rappelez des schémas de la mémoire ? Il est temps de les ressortir.

Lors de l'appel à la fonction, il se passe énormément de choses :

  • Le programme évalue la valeur de nombre. Il trouve 4.
  • Le programme alloue un nouvel espace dans la mémoire et y écrit la valeur 4. Cet espace mémoire possède l'étiquette a, le nom de la variable dans la fonction.
  • Le programme entre dans la fonction.
  • Le programme ajoute 2 à la variable a.
  • La valeur de a est ensuite copiée et assignée à la variable resultat, qui vaut donc maintenant 6.
  • On sort alors de la fonction.

Ce qui est important, c'est que la variable nombre est copiée dans une nouvelle case mémoire. On dit que a est passé par valeur. Lorsque le programme se situe dans la fonction, la mémoire ressemble donc à ce qui se trouve sur ce schéma :


On se retrouve donc avec trois cases dans la mémoire. L'autre élément important est que la variable nombre va rester inchangée.

Si j'insiste sur ces points, c'est bien sûr que l'on peut faire autrement. ;)

Passage par référence



Vous vous rappelez des références ? Oui, oui, ces choses bizarres dont je vous ai parlé il y a quelques chapitres. Si vous n'êtes pas sûr de vous, n'hésitez-pas à vous rafraichir la mémoire. C'est le moment de voir à quoi servent ces drôles de bêtes.

Plutôt que de copier la valeur de nombre dans la variable a, il est possible d'ajouter une "deuxième étiquette" à la variable nombre à l'intérieur de la fonction. Et c'est bien sûr une référence qu'il faut utiliser comme argument de la fonction.
1
2
3
4
5
int ajouteDeux(int& a) //Notez le petit & !!
{
   a+=2;
   return a;
}

Lorsque l'on appelle la fonction, il n'y a plus de copie. Le programme donne juste un alias à la variable nombre. Jetons un ?il à la mémoire dans ce cas.


Cette fois, la variable a et la variable nombre sont confondues. On dit que l'argument a est passé par référence.

Quel intérêt y a-t-il à faire un passage par référence ?


Cela va permettre à la fonction ajouteDeux() de modifier ses arguments ! Elle va ainsi pouvoir avoir une influence durable sur le reste du programme. Essayons pour voir. Reprenons le programme précédent, mais avec une référence comme argument. On obtient cette fois :
1
2
Le nombre original vaut : 6
Le resultat vaut : 6

Que s'est-il passé ? C'est à la fois simple et compliqué. :(
Comme a et la variable nombre correspondent à la même case mémoire, faire a+=2 a modifié la valeur de nombre !
Utiliser des références peut donc être très dangereux. C'est pour cela qu'on ne les utilise que quand on en a réellement besoin.

Justement, est-ce qu'on pourrait avoir un exemple utile ?


J'y viens, j'y viens. Ne soyez pas trop pressés. ;)
L'exemple classique est la fonction swap(). C'est une fonction qui échange les valeurs des deux arguments qu'on lui fournit :
1
2
3
4
5
6
7
void swap(double& a, double& b)
{
   double temporaire;
   temporaire = a;     //On sauvegarde la valeur de 'a'
   a=b;                //On remplace la valeur de 'a' par celle de 'b'
   b = temporaire;     //Et on utilise la valeur sauvegardee pour mettre la valeur de 'a' dans 'b'
}

Si l'on n'utilisait pas un passage par référence, alors ce seraient des copies des arguments qui seraient échangées. Et pas les vrais arguments. Cette fonction serait donc inutile.
Je vous invite à tester cette fonction avec et sans les références. Vous verrez ainsi précisément ce qui se passe.

A priori, le passage par référence peut vous sembler obscur et compliqué. Vous verrez par la suite qu'il est souvent utilisé. Vous pourrez toujours revenir lire cette partie plus tard si ce n'est pas vraiment clair dans votre esprit.
Je vous rassure, il m'a fallu un moment pour vraiment saisir tout ça. ;)


Utiliser plusieurs fichiers


Dans l'introduction, je vous ai dit que le but des fonctions était de pouvoir réutiliser les briques créées dans plusieurs programmes.
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.

Le C++ permet de découper son programme en plusieurs fichiers sources. Chaque fichier contient une ou plusieurs fonctions. On peut alors inclure les fichiers, et donc les fonctions, dont on a besoin dans différents projets. On a ainsi réellement des briques séparées utilisables pour construire différents programmes.

Les fichiers nécessaires



Pour faire les choses proprement, il ne faut pas un mais, deux fichiers :) :

  • Un fichier source dont l'extension est .cpp. Ce fichier contient le code source de la fonction.
  • Un fichier d'en-tête dont l'extension est .h. Ce deuxième fichier contient uniquement la description de la fonction. Ce qu'on appelle le prototype de la fonction.

Créons donc ces deux fichiers pour notre célèbre fonction ajouteDeux() :
1
2
3
4
5
6
7
int ajouteDeux(int nombreRecu)
{
   int valeur(0);
   valeur = nombreRecu + 2;
 
   return valeur;
}

Le fichier source



C'est le plus simple des deux. Allez dans le menu File / New /File. Choisissez ensuite C/C++ source.


Cliquez ensuite sur "Go". Comme lors de la création du projet, le programme vous demande ensuite si vous faites du C ou du C++. Choisissez "C++" bien sûr ! ;)

Finalement, on vous demande le nom du fichier à créer. Comme pour tout, il vaut mieux choisir un nom intelligent pour ses fichiers. On peut ainsi mieux s'y retrouver. Pour la fonction ajouteDeux(), je choisis le nom math.cpp et je place le fichier dans le même dossier que mon fichier main.cpp.

Cochez ensuite toutes les options.


Cliquez sur "Finish". Votre fichier source est maintenant créé. Passons au fichier d'en-tête.

Le fichier d'en-tête



Le début est quasiment identique. Ouvrez le menu File / new / File. Sélectionnez ensuite "C/C++ header".


Dans la fenêtre suivante, indiquez le nom du fichier à créer. Il est conseillé d'utiliser le même nom que pour le fichier source mais avec une extension .h au lieu de .cpp. Dans notre cas, ce sera math.h. Placez le fichier dans le même dossier que les deux autres.

Ne touchez pas le champ juste en-dessous et n'oubliez pas de cocher toutes les options.


Cliquez sur "Finish". Et voilà. ;)

Une fois que les deux fichiers sont créés, vous devriez les voir apparaître dans la colonne de gauche de Code::Blocks :


Le nom du projet sera certainement différent dans votre cas. ;)


Déclarer la fonction dans les fichiers



Maintenant que nous avons nos fichiers, il ne reste qu'à les remplir.

Le fichier source



Je vous ai dit que le fichier source contenait la déclaration de la fonction. C'est un des éléments.
L'autre est plus compliqué à comprendre. Le compilateur a besoin de savoir que les fichiers .cpp et .h ont un lien entre eux. Il faut donc commencer le fichier par la ligne suivante :
1
#include "math.h"

Vous reconnaissez certainement cette ligne. Elle indique que l'on va utiliser ce qui se trouve dans le fichier math.h.

Il faut utiliser des guillemets " ici et pas des chevrons < et > comme vous en aviez l'habitude jusque là.



Le fichier math.cpp au complet est donc :
1
2
3
4
5
6
7
8
9
#include "math.h"
 
int ajouteDeux(int nombreRecu)
{
   int valeur(0);
   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 ! o_O Il contient trois lignes mystérieuses :
1
2
3
4
5
#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED
 
 
#endif // MATH_H_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 de se retrouver dans cette situation. Il ne faut donc pas toucher ces lignes et surtout, écrire tout le code entre la deuxième et la troisième.

Le texte en majuscule sera différent pour chaque fichier. C'est le texte qui apparaît dans le champ que nous n'avons pas modifié lors de la création du fichier.


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 prend cette ligne et on ajoute un point-virgule à la fin.

C'est donc très court. Voici ce que l'on obtient pour notre fonction :
1
2
3
4
5
6
#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED
 
int ajouteDeux(int nombreRecu);
 
#endif // MATH_H_INCLUDED

N'oubliez pas le point-virgule ici !


Et c'est tout. ;) Il ne nous reste qu'une seule chose à faire : inclure tout ça dans le fichier main.cpp. Si on ne le fait pas, le compilateur ne saura pas où trouver la fonction ajouteDeux() lorsqu'on essaiera de l'utiliser. Il faut donc ajouter la ligne
1
#include "math.h"

au début de notre programme. Ce qui donne :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include "math.h"
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 réellement 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.h.

On peut bien sûr mettre plusieurs fonctions par fichier. On les regroupe généralement par catégorie. Les fonctions mathématiques d'un côté, les fonctions pour l'affichage d'un menu dans un autre fichier et celle pour faire se déplacer un personnage de jeu vidéo dans un troisième. Programmer, c'est aussi être organisé. ;)


Documenter son code



Avant de terminer ce chapitre, je veux juste vous présenter un point qui peut sembler futile. On vous l'a dit dès le début, il est important de mettre des commentaires dans son programme pour comprendre ce qu'il fait.
C'est particulièrement vrai pour les fonctions puisque vous allez peut-être utiliser des fonctions écrites par d'autres programmeurs. Il est donc important de savoir ce que font ces fonctions sans avoir besoin de lire tout le code ! (Rappelez-vous, le programmeur est fainéant...)

Comme il y a de la place dans les fichiers d'en-tête, on en profite généralement pour décrire ce que font les fonctions. Il y a généralement trois choses décrites :

  • Ce que fait la fonction.
  • La liste des ses arguments.
  • La valeur retournée.

Plutôt qu'un long discours, voici ce qu'on pourrait écrire pour notre fonction ajouteDeux() :
1
2
3
4
5
6
7
8
9
10
11
#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED
 
/**
* Fonction qui ajoute 2 au nombre reu en argument
* - nombreRecu : Le nombre auquel la fonction ajoute 2
* Valeur retourne : nombreRecu + 2
*/

int ajouteDeux(int nombreRecu);
 
#endif // MATH_H_INCLUDED

Bien sûr, dans ce cas, le descriptif est très simple. Mais c'est une habitude qu'il faut prendre. C'est d'ailleurs tellement courant de mettre des commentaires dans les fichiers .h qu'il existe des systèmes quasi-automatiques qui génèrent des sites web ou des livres à partir de ces commentaires. o_O


Des valeurs par défaut pour les arguments


Les arguments de fonctions, vous commencez à connaître. Je vous en parle depuis le début du chapitre. Lorsque une fonction a trois arguments, il faut lui fournir trois valeurs pour qu'elle puisse fonctionner.
Et bien, je vais vous montrer que ce n'est pas toujours le cas.

Voyons tout ça avec la fonction suivante :
1
2
3
4
5
6
7
8
9
10
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 le nombre de secondes en additionnant les heures, minutes et secondes qu'on lui envoie. 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.
Mais ça, vous le savez déjà. ;)

Les valeurs par défaut



La nouveauté, c'est qu'on peut donner des valeurs par défaut à certains paramètres de nos fonctions. Ainsi, on ne sera pas obligé d'indiquer à chaque fois tous les paramètres lorsqu'on appelle une fonction !

Pour bien voir comment on doit procéder, on va regarder le code complet. J'aimerais que vous le copiez dans votre IDE pour faire les tests en même temps que moi :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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;
}
 
// Dfinition 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 qu'1 heure = 3600s, 10 minutes = 600s, 25 secondes =... 25s, 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 si on ne les a pas renseignés lors de l'appel de la fonction :
1
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 vaudront alors 0 dans la fonction.

Voici le code complet que vous devriez avoir sous les yeux :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
 
using namespace std;
 
// Prototype avec les valeurs par dfaut
int nombreDeSecondes(int heures, int minutes = 0, int secondes = 0);
 
// Main
int main()
{
   cout << nombreDeSecondes(1, 10, 25) << endl;
 
   return 0;
}
 
// Dfinition de la fonction, SANS les valeurs par dfaut
int nombreDeSecondes(int heures, int minutes, int secondes)
{
   int total = 0;
 
   total = heures * 60 * 60;
   total += minutes * 60;
   total += secondes;
 
   return total;
}

Si vous avez lu attentivement ce code, vous avez dû vous rendre compte de quelque chose : les valeurs par défaut sont spécifiées uniquement dans le prototype, PAS dans la définition de la fonction ! Si votre code est découpé en plusieurs fichiers, alors il ne faut spécifier les valeurs par défaut uniquement dans le fichier d'en-tête .h. On se fait souvent avoir, je vous préviens... :p
Si vous vous trompez, le compilateur vous indiquera une erreur à la ligne de la définition de la fonction.


Bon, ce code ne change pas beaucoup du précédent. A 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 :
1
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 valeur "1" correspond à un nombre d'heures.

Le résultat à l'écran sera le suivant :
3600

Mieux encore, vous pouvez indiquer juste les heures et les minutes si vous le désirez :
1
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 ça ! ^^
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. En effet, je vous l'ai dit plus haut, le compilateur va analyser 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 :
1
cout << nombreDeSecondes(1,,25) << endl;

C'est interdit. Si vous le faites, le compilateur vous fera comprendre qu'il n'apprécie guère vos man?uvres. C'est comme ça : en C++, on ne peut pas "sauter" des paramètres, même s'ils sont facultatifs. Si vous voulez indiquer le premier et le dernier paramètre, il vous faudra obligatoirement spécifier ceux du milieu. On devra donc écrire :
1
cout << nombreDeSecondes(1, 0, 25) << endl;

Est-ce que je peux rendre juste 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 :
1
2
int nombreDeSecondes(int heures = 0, int minutes, int secondes);
                                 // Erreur, les paramtres par dfaut doivent tre droite

La solution, pour régler ce problème, consiste à placer le paramètre heures à la fin :
1
2
int nombreDeSecondes(int secondes, int minutes, int heures = 0);
                                               // OK

Est-ce que je peux rendre tous mes paramètres facultatifs ?


Oui, ça ne pose pas de problème :
1
int nombreDeSecondes(int heures = 0, int minutes = 0, int secondes = 0);

Dans ce cas, l'appel de la fonction pourra être fait comme ceci :
1
cout << nombreDeSecondes() << endl;

Le résultat retourné sera bien entendu 0 dans notre cas. :p

Règles à retenir



En résumé, il y a 2 règles que vous devez retenir pour les valeurs par défaut :

  • 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 ("à droite").




Souvenez-vous qu'il est très important de découper son programme en fonctions. Dans certaines entreprises, on oblige les gens à faire des fonctions dès qu'un morceau de code dépasse une hauteur d'écran ! C'est peut-être un peu extrême, mais force les programmeurs à avoir la bonne attitude, découper, découper et découper encore. ;)

Au terme de ce chapitre, vous connaissez les variables, les branchements et les fonctions. Vous connaissez donc presque tous les concepts de base. o_O Dans le chapitre suivant, nous allons découvrir un nouveau type de variables.



Chapitre précédent     Sommaire     Chapitre suivant



Distribué et adapté par David
Consulté 12125 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 240 jours 11 heures 31 minutes
Espace libre : 1570 Mo
Dernière sauvegarde : inconnue
Taille de la sauvegarde : 1112 Mo


5357116 pages ont été consultées sur le site !
Dont 2663 pages pendant les 24 dernières heures.

Page générée en 1.33 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2019 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.