Aller au menu - Aller au contenu

Icône Découper son programme en fonctions

Mise à jour : 27/05/2011
Difficulté : Facile Facile Durée d'étude : 1 jour Creative Commons BY-NC-SA
75 639 visites depuis 7 jours, dont 1 714 sur ce chapitre classé 5/786
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.
Sommaire du chapitre :
Icône du chapitre
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.

Code : C++
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 dis 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.

Une fonction traîte des arguments et produit une valeur de retour


Vous vous souvenez de pow() ? La fonction qui permet de calculer des puissances ? En utilisant le nouveau vocabulaire, on peut dire que cette fonction :
  1. reçoit deux arguments.
  2. effectue un calcul mathématique.
  3. renvoie le résultat du calcul.

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

Schéma de la fonction pow()


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 :

Code : C++
1
2
3
4
type nomFonction(arguments)
{
    //Instructions effectuées par la fonction
}


On retrouve les trois éléments dont je vous ai déjà parlé auxquels 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.

Code : C++
1
2
3
4
5
6
7
8
9
int ajouteDeux(int nombreRecu)
{
    int valeur(nombreRecu + 2); //On cree une case en memoire.
                                //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.

Image utilisateur


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 !

Code : C++
 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;

int ajouteDeux(int nombreRecu)
{
    int 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;
}


On retrouve la syntaxe que l'on connaissait déjà : résultat = fonction(argument). Facile pour ainsi dire ! ;)
Vous avez essayé le programme ? Vous devriez toujours essayer les exemples. Voici quand même ce que ça donne :

Code : Console
Valeur de a : 2
Valeur de b : 2
Valeur de a : 2
Valeur de b : 4


Après l'appel à la fonction, la variable b a été modifiée. Tout fonctionne donc comme annoncé.

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.

Code : C++
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.

Code : C++
1
2
3
4
5
6
7
string demanderNom()
{    
     cout << "Entrez votre nom : ";
     string 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. ;)

Code : C++ - Une fonction ne renvoyant rien
 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 x^2.

Code : C++
 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.

Déroulement d'un programme appelant une fonction.


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 :

Code : C++ - Calcul des carrés des 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) << endl;
    }
    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.

Code : C++ - Dessin d'un rectangle d'étoiles
 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 :
Code : Console
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 ! :diable: 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. Mais n'oubliez pas le QCM ! ;)

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

Code : C++
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.

Code : C++
 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 :

Code : Console
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 :
  1. Le programme évalue la valeur de nombre. Il trouve 4.
  2. 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.
  3. Le programme entre dans la fonction.
  4. Le programme ajoute 2 à la variable a.
  5. La valeur de a est ensuite copiée et assignée à la variable resultat, qui vaut donc maintenant 6.
  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 :

Etat de la mémoire dans la fonction après un passage par valeur


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 ? :pirate: 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.

Code : C++
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.

Etat de la mémoire dans la fonction après un passage par référence


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 :

Code : Console
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 echange(). C'est une fonction qui échange les valeurs des deux arguments qu'on lui fournit :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void echange(double& a, double& b)
{
    double 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 sauvegardée pour mettre l'ancienne valeur de 'a' dans 'b'
}

int main()
{
    double a(1.2), b(4.5);

    cout << "a vaut " << a << " et b vaut " << b << endl;

    echange(a,b);   //On utilise la fonction

    cout << "a vaut " << a << " et b vaut " << b << endl;
    return 0;
}


Ce code fournit le résultat suivant :

Code : Console
a vaut 1.2 et b vaut 4.5
a vaut 4.5 et b vaut 1.2


Les valeurs des deux variables ont été échangées.

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. ;)

Avancé: Le passage par référence constante



Puisque nous parlons de références, il faut quand même que je vous présente une application bien pratique. En fait, cela nous sera surtout utile dans la suite de ce cours, mais nous pouvons déjà prendre un peu d'avance.

Le passage par référence offre un gros avantage sur le passage par valeur, aucune copie n'est effectuée. Imaginez une fonction recevant en argument une string. Si votre chaîne de caractère contient un très long texte (l'entièreté de ce cours par exemple !), alors la copier va prendre du temps, même si tout cela se passe uniquement dans la mémoire de l'ordinateur. Cette copie est totalement inutile et il serait donc bien de pouvoir l'éliminer pour améliorer les performances du programme.
Et c'est là que vous me dites : "Utilisons un passage par référence !". Oui, c'est une bonne idée. En utilisant un passage par référence, aucune copie n'est effectuée. Seulement, cette manière de procéder a un petit défaut : elle autorise la modification de l'argument. Et oui, c'est justement dans ce but que les références existent.

Code : C++
1
2
3
4
5
6
7
void f1(string texte)  //Implique une copie coûteuse de 'texte' 
{
}

void f2(string& texte)  //Implique que la fonction peut modifier 'texte' 
{
}


La solution est d'utiliser ce que l'on appelle un passage par référence constante. On évite la copie en utilisant une référence et l'on empêche la modification de l'argument en le déclarant constant. :soleil:

Code : C++
1
2
3
void f1(string const& texte)  //Pas de copie et pas de modification possible
{
}


Pour l'instant, cela peut vous sembler obscur et plutôt inutile. Dans la partie II de ce cours, nous aborderons la POO et nous utiliserons très souvent cette technique. Vous pourrez toujours revenir lire ces lignes à ce moment-là. ;)

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() :

Code : C++
1
2
3
4
5
6
int ajouteDeux(int nombreRecu)
{
    int 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.

Créer un nouveau fichier 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.

Sélection des options pour le fichier source


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".

Création d'un fichier d'en-tête


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.

Sélection des options pour le fichier d'en-tête


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 :

Les nouveaux fichiers du projet


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 :

Code : C++
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 :

Code : C++ - Le fichier math.cpp
1
2
3
4
5
6
7
8
#include "math.h"

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 ! o_O Il contient trois lignes mystérieuses :

Code : C++ - Les gardes anti-inclusions multiples
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.

Si vous n'utilisez pas Code::Blocks, vous n'aurez peut-être pas automatiquement ces lignes dans vos fichiers. Il faut alors les ajouter à la main. Le mot en majuscule doit être le même sur les 3 lignes où il apparaît. Et chaque fichier doit utiliser un mot différent.


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 :

Code : C++ - Contenu du fichier d'en-tête
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

Code : C++
1
#include "math.h"


au début de notre programme. Ce qui donne :

Code : C++
 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;
}


On inclut toujours le fichier d'en-tête (.h). Jamais le fichier source (.cpp).


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. :magicien:

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 :
  1. Ce que fait la fonction.
  2. La liste des ses arguments.
  3. La valeur retournée.


Plutôt qu'un long discours, voici ce qu'on pourrait écrire pour notre fonction ajouteDeux() :

Code : C++ - Le fichier math.h avec des commentaires
 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 reçu en argument
 * - nombreRecu : Le nombre auquel la fonction ajoute 2
 * Valeur retournée : 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

Le célèbre système doxygen utilise par exemple la notation suivante :
Code : C++
1
2
3
4
5
6
/**
 * \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);


Pour l'instant cela peut vous paraître un peu inutile, mais vous verrez dans la partie III de ce cours qu'avoir une bonne documentation est essentiel. A vous de choisir la notation que vous préférez.

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 :

Code : C++
 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 copiiez dans votre IDE pour faire les tests en même temps que moi :

Code : C++
 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;
}

// 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 :

Code : Console
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 :

Code : C++
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 :

Code : C++
 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 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;
}


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 que 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 :

Code : C++
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 :

Code : Console
3600


Mieux encore, vous pouvez indiquer juste les heures et les minutes si vous le désirez :

Code : C++
1
cout << nombreDeSecondes(1, 10) << endl;


Code : Console
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 :

Code : C++
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 :

Code : C++
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 :

Code : C++
1
2
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 :

Code : C++
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 :

Code : C++
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 :

Code : C++
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").

Q.C.M.

Que sont les arguments d'une fonction ?
Dans quel cas l'utilisation du mot return est-elle superflue ?
Cette fonction est-elle correcte ?


Code : C++
1
2
3
4
5
6
7
8
/* Fonction qui demande à l'utilisateur d'entrer un nombre
 * - nombre : Une référence sur la variable à remplir
 */
void demanderNombre(int& nombre);
{
    cout << "Entrez un nombre : ";
    cin >> nombre;
}
Quels sont les paramètres facultatifs de cette fonction ?

Code : C++
1
void ouvrirFenetre(string titre, int largeur = 250, int hauteur = 300, bool centree = false);

Peut-on appeler la fonction ouvrirFenetre() (présentée ci-dessus) de la manière suivante :


Code : C++
1
ouvrirFenetre("Bienvenue", 200, 200);

Statistiques de réponses au QCM

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

Partager

52 commentaires pour "Découper son programme en fonctions"
Note moyenne : 3.85 / 4 (1749 votes)
Pseudo Commentaire
Hors ligne darkdober # Posté le 26/03/2012 à 22:04:43

c'est ce que ça me donne es-ce que c'est correcte :) ? moi j'ai préféré permettre ua gens de copier tout se qu'ils voulaient a la place de petit caractère d'étoiles ^^

void dessineRectangle(int l, int h)
{
cout << "Entrez votre dessin a reproduire : ";
string dessin;
cin >> dessin;

for(int ligne(0); ligne < h; ligne++)
{
for(int colonne(0); colonne < l; colonne++)
{
cout<< dessin;
}
cout << endl;
}
}

int main()
{
int largeur, hauteur;
cout << "Largeur du rectangle : ";
cin >> largeur;
cout << "Hauteur du rectangle : ";
cin >> hauteur;
if(largeur <0 && hauteur <0)
{
cout << "Vous ne pouvez pas faire cela!"<< endl;
}
dessineRectangle(largeur, hauteur);
return 0;
}
Hors ligne death-j # Posté le 31/03/2012 à 17:46:58
La mort arrive après la vie
Avatar

Avis : Très bon

#include <iostream>

using namespace std;

int main()
{
char lettreTrouver;
char lettreEntrer;
int coup, nbCoup(0);
int difficulter;
bool def = false;

cout << "la lettre mystere..." << endl;
cout << "ce jeu ce joue a deux joueur." << endl;
cout << "choix de la difficulter:" << endl << endl;

while (def == false)
{
cout << "1)facile(15 coups)" << endl << endl;
cout << "2)moyen(7 coups)" << endl << endl;
cout << "3)difficile(4 coups)" << endl << endl;
cin >> difficulter;

if (difficulter == 1)
{
coup = 15;
def = true;
}

else if (difficulter == 2)
{
coup = 7;
def = true;
}

else if (difficulter == 3)
{
coup = 4;
def = true;
}

else
{
cout << "veuillez entrez une des difficulter svp." << endl;
}
}

cout << "joueur 1, entrez une lettre:" << endl;
cin >> lettreTrouver;
cout << "" << endl;

while (lettreEntrer != lettreTrouver && coup > 0)
{
cout << "joueur 2, trouver la lettre!" << endl;
cin >> lettreEntrer;

if (lettreEntrer != lettreTrouver)
{
cout << "ce n'est pas la lettre!" << endl;
coup--;
nbCoup ++;
cout << "il vous reste: " << coup << " coup! " << endl;
}

else
{
cout << "bravo! Vous avez Trouver la lettre en " << nbCoup << " coup! " << endl;
}

}

if (coup == 0)
{
cout << "dommage! Vous avez perdu!" << endl;
}
return 0;
}

regarder cette video! Elle est impresionante! ici
90% of teens today would die if Facebook was completely destroyed. If you are one of the 10% that would be laughing, copy and paste this to your signature.

HTML5/CSS3: ||||||||||
 
Hors ligne Apoz # Posté le 08/04/2012 à 15:49:11

Bonjour tout le monde ^^

Petite question peut être complètement conne, mais est-ce que c'est possible de faire ça :

- inclure dans un fichier1, un fichier2
- et inclure dans le fichier2, le fichier1

J'imagine qu'il doit y avoir un terme plus sérieux pour ça mais je ne le connais pas donc... ^^

Merci d'avance
Hors ligne Chekroun # Posté le 28/04/2012 à 00:40:57

je débute avec le langage c++ j'ai fait l'exercice "Une fonction à deux arguments" celle qui consiste à :
Afficher un message d'erreur si la hauteur ou la largeur est négative.
Ajouter un argument pour le symbole à utiliser lors du dessin. je suis assez content de moi :D
le tuto est SUPER !!!

#include <iostream>
#include <string>
using namespace std;

void dessineRectangle(int l, int h)
{
cout << "Entrez votre dessin a reproduire : ";
string dessin;
cin >> dessin;

for(int ligne(0); ligne < h; ligne++)
{
for(int colonne(0); colonne < l; colonne++)
{
cout<< dessin;
}
cout << endl;
}
}

int main()
{
int largeur, hauteur;
cout << "Largeur du rectangle est : ";
cin >> largeur;
cout << "Hauteur du rectangle est : ";
cin >> hauteur;

if (largeur && hauteur < 5)
{

{
dessineRectangle(largeur, hauteur);
}
}
else
{
cout<<"erreur systeme";
}

return 0;
}
Hors ligne NEO_XXX # Posté le 02/05/2012 à 11:52:45

Bonjour,

J'ai un problème dans la partie "Utilisez plusieurs fichiers" lors de la création du fichier source en .cpp et le fichier d'en tête en .h je ne peux pas cocher =>
Add file to active project
In bult target(s):
Debug
Release

Merci pour aide.
En tout cas excellent tuto ;)

Voir tous les commentaires