• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 19/02/2019

Découpez votre programme en fonctions

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous venons de voir comment faire varier le déroulement d'un programme en utilisant des boucles et des branchements. Avant cela, 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++ exploitent des fonctions et vous en avez déjà utilisé plusieurs, sans forcément le savoir.
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, le programmeur « n'a plus qu'à » les assembler.

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

Créez et utilisez une fonction

Dès le début de ce cours, nous avons utilisé des fonctions. C'était en fait toujours la même : la fonctionmain(). C'est le point d'entrée de tous les programmes C++, c'est par là que tout commence.

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

Si je vous dis tout cela, vous devez vous douter que l'on peut écrire d'autres fonctions et donc avoir un programme découpé en plusieurs modules indépendants.

Pourquoi vouloir faire cela ?

C'est vrai, après tout. Mettre l'ensemble du code dans la fonctionmain()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 dans un coin un morceau de code qui fait bouger un personnage, ailleurs un autre bout de code qui charge les niveaux, etc. Découper son programme en fonctions permet de s'organiser.
En plus, si vous êtes plusieurs développeurs à travailler sur le même programme, vous pourrez vous partager plus facilement le travail : chacun s'attelle une fonction différente.

Mais ce n'est pas tout !
Prenons par exemple le calcul de la racine carrée, que nous avons vu précédemment. Si vous créez un programme de maths, il est bien possible que vous ayez besoin, à plusieurs endroits, d'effectuer des calculs de racines. Avoir une fonctionsqrt()va nous permettre de faire plusieurs de ces calculs sans avoir à recopier le même code à plusieurs endroits. On peut ré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 enfin renvoie une valeur.

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

Une fonction traite des arguments et produit une valeur de retour
Une fonction traite 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 représenter pow()comme à la figure suivante.

Schéma de la fonction pow()

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

Définir une fonction

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êts ? Alors allons-y !

Toutes les fonctions ont la forme suivante :

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ée par la fonction. Si votre fonction doit renvoyer du texte, alors ce sera string; si votre fonction effectue un calcul, alors ce sera intou 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 qu'elle fait, 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.

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.

int ajouteDeux(int nombreRecu)
{
    int valeur(nombreRecu + 2);
    //On crée une case en mémoire
    //On prend le nombre reçu en argument, on lui ajoute 2
    //Et on met tout cela dans la mémoire

    return valeur;
    //On indique que la valeur qui sort de la fonction
    //Est la valeur de la variable 'valeur'
}

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éeajouteDeuxqui reçoit un nombre entier en argument et qui, une fois qu'elle a terminé, renvoie un autre nombre entier : 

La fonction reçoit un nombre en entrée et produit un autre nombre en sortie
La fonction reçoit un nombre en entrée et produit un autre nombre en sortie

Toutes les lignes suivantes utilisent des choses déjà connues, saufreturn valeur;. Si vous vous posez des questions sur ces lignes, je vous invite à relire le chapitre sur l'utilisation de la mémoire.

L'instructionreturnindique quelle valeur ressort de la fonction. En l'occurrence, c'est la valeur de la variablevaleurqui est renvoyée.

Appelez une fonction

Que diriez-vous si je vous disais que vous savez déjà appeler une fonction ? Souvenez-vous des fonctions mathématiques !

#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(b); //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 ? Voici ce que cela donne :

Valeur de a : 2
Valeur de b : 2
Valeur de a : 2
Valeur de b : 4

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

Plusieurs paramètres

Nous ne sommes pas encore au bout de nos peines. Il y a des fonctions qui prennent plusieurs paramètres, commepow()etgetline()par exemple.

Pour passer plusieurs paramètres à une fonction, il faut les séparer par des virgules.

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.

Pas d'arguments

À l'inverse, on peut aussi créer des fonctions sans arguments. Il suffit de ne rien écrire entre les parenthèses !

Mais à quoi cela sert-il ?

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ètre.

string demanderNom()
{    
     cout << "Entrez votre nom : ";
     string nom;
     cin >> nom;
     return nom;
}

Même s'il est vrai que ce type de fonctions est plus rare, je suis sûr que vous trouverez plein d'exemples par la suite !

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 typevoid, ce qui signifie « néant » en anglais. Cela veut tout dire : il n'y a vraiment rien qui soit renvoyé par la fonction.

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;
}

Notez également qu'il n'est pas possible de renvoyer plus qu'une valeur. Une fonction produit toujours au maximum un résultat.

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 avec un exemple simple : calculer le carré d'un nombre. Cette fonction reçoit un nombrexen argument et calcule la valeur de $\(x^2\)$.

#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 sont exécutées les lignes (figure suivante).

Déroulement d'un programme appelant une fonction
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 returnque les variables demain()sont modifiées c'est-à-dire ici la variable carreNombre. La variable nombrereste inchangée lors de l'appel à la fonction.

Réutilisez 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 :

#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 on utilise ensuite vingt fois cette « brique ». Ici, le calcul est simple mais, dans bien des cas, utiliser une fonction raccourcit grandement le code !

Des variables avec le même nom

Dans le chapitre sur la mémoire, nous avions vu que chaque variable devait avoir un nom unique. C'est tout à fait correct, mais cette règle n'est valable qu'à l'intérieur d'une même fonction. Il est tout à fait possible d'avoir deux variables ayant le même nom pour autant qu'elles soient déclarées dans des fonctions différentes.

#include <iostream>
using namespace std;

double carre(double x)
{
    double nombre;
    nombre = x*x;
    return nombre;
}

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;
}

Comme vous pouvez le voir, il existe une variable nombredans la fonction main()et une autre dans la fonction carre(). Le compilateur ne va pas sourciller et ce code fait exactement la même chose que le précédent. Il n'y a pas de confusion possible entre les deux variables puisque le compilateur s'occupe d'une fonction à la fois et ne verra donc pas qu'il y a deux variables au même nom.

Mais pourquoi faire ça ?

Souvenez-vous que l'on choisit les noms des variables de sorte à ce qu'ils représentent précisément ce que la variable contient. Il y a donc bien des cas où différentes variables vont jouer des rôles similaires. Il est donc pertinent d'utiliser le même nom. On évite ainsi la difficulté de créer des noms de variables compliqués juste pour qu'ils soient entièrement uniques. Et puis, dans un code de plusieurs centaines de milliers de ligne, tel un jeu vidéo, il faudrait avoir une sacrée imagination pour inventer tant de noms de variables différents. ;)

Une fonction à deux arguments

Avant de terminer cette section, 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.

#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 donne par exemple :

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 cela qu'elle est déclarée avec le type void.
On peut facilement modifier la fonction pour qu'elle renvoie la surface du rectangle. À 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 dans le dessin.

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

Passage par valeur et passage par référence

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

La première des notions avancées dont je dois vous parler est la manière dont l'ordinateur gère la mémoire dans le cadre des fonctions.

Prenons une fonction simple qui ajoute simplement 2 à l'argument fourni. Vous commencez à bien la connaître. Je l'ai donc modifiée un poil.

int ajouteDeux(int a)
{
    a+=2;
    return a;
}

Testons donc cette fonction. Je pense ne rien vous apprendre en vous proposant le code suivant :

#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;
}

Cela donne sans surprise :

Le nombre original vaut : 4
Le resultat vaut : 6

L'étape intéressante est bien sûr ce qui se passe à la ligneresultat = ajouteDeux(nombre);. Vous vous rappelez les 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 trouve4.

  2. Le programme alloue un nouvel espace dans la mémoire et y écrit la valeur4. 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 ajoute2 à la variable a.

  5. La valeur de aest ensuite copiée et affectée à la variable resultat, qui vaut donc maintenant6.

  6. On sort alors de la fonction.

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

État de la mémoire dans la fonction après un passage par valeur
État 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 nombrereste inchangée.

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

Passage par référence

Vous vous rappelez les références ? Oui, oui, ces choses bizarres dont je vous ai parlé il y a quelques chapitres. Si vous n'êtes pas sûrs de vous, n'hésitez-pas à vous rafraîchir 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 nombredans la variablea, 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.

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 simplement un alias à la variablenombre. Jetons un coup d'œil à la mémoire dans ce cas :

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

Cette fois, la variable aet la variable nombresont confondues. On dit que l'argument aest passé par référence.

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

Cela permet à la fonction ajouteDeux()de modifier ses arguments ! Elle pourra ainsi 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 :

Le nombre original vaut : 6
Le resultat vaut : 6

Que s'est-il passé ? C'est à la fois simple et compliqué.
Comme aet la variable nombrecorrespondent à la même case mémoire, faire a+=2a modifié la valeur denombre!
Utiliser des références peut donc être très dangereux. C'est pour cela qu'on ne les utilise que lorsqu'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 :

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 donne le résultat suivant :

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, ce seraient alors des copies des arguments qui seraient échangées, et non 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 section plus tard si les choses ne sont pas encore vraiment claires dans votre esprit.

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 unstring. Si votre chaîne de caractères contient un très long texte (la totalité de ce livre 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. Eh oui, c'est justement dans ce but que les références existent.

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.

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 programmation orientée objet et nous utiliserons très souvent cette technique. Vous pourrez toujours revenir lire ces lignes à ce moment-là.

Utilisez plusieurs fichiers

Dans l'introduction, 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 fonctionmain(). 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: il contient le code source de la fonction ;

  • un fichier d'en-tête dont l'extension est.h: 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 célèbre fonctionajouteDeux():

int ajouteDeux(int nombreRecu)
{
    int valeur(nombreRecu + 2);

    return valeur;
}
Le fichier source

C'est le plus simple des deux. Passez par les menusFile>New>File. Choisissez ensuiteC/C++ source(figure suivante).

Créer un nouveau fichier source
Un fichier source

Cliquez ensuite surGo. Comme lors de la création du projet, le programme vous demande alors si vous faites du C ou du C++. ChoisissezC++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.cppet 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
Sélection des options pour le fichier source

Cliquez surFinish. 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. Passez par les menusFile>New>File. Sélectionnez ensuiteC/C++ header(figure suivante).

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

Dans la fenêtre suivante, indiquez le nom du fichier à créer. Il est conseillé de lui donner le même nom qu'au fichier source mais avec une extension .hau lieu de.cpp. Dans notre cas, ce sera donc 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
Sélection des options pour le fichier d'en-tête

Cliquez surFinish. 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
Les nouveaux fichiers du projet

Déclarez 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.cppet.hont un lien entre eux. Il faut donc commencer le fichier par la ligne suivante :

#include "math.h"

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

Le fichiermath.cppau complet est donc :

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

#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 donc 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.

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.

C'est donc très court. Voici ce que nous obtenons pour notre fonction :

#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

int ajouteDeux(int nombreRecu);

#endif // MATH_H_INCLUDED

Et c'est tout dans le cas le plus simple. Si vous utilisez des variables plus compliquées en argument comme des strings (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. On aura ainsi:

#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, concept qui sera abordé plus loin dans ce cours. Si vous êtes attentifs, vous aurez remarqué que stdapparaît dans tous nos fichiers sources via la ligne using namespace std. Comme il n'y a pas de tel 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 stringqui est std::string. Vous verrez d'autres exemples de types de variables aux noms complexes plus loin dans le cours. Pour l'instant, std::stringest le seul cas spécial. :)

Il ne nous reste qu'une seule chose à faire : inclure tout cela 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 :

#include "math.h"

au début de notre programme. Cela donne :

#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 fonctionajouteDeux()dans un autre projet, il vous suffira de copier les fichiersmath.cppetmath.h.

Documenter son code

Avant de terminer ce chapitre, je veux juste vous présenter un point qui peut sembler futile (mais qui ne l'est pas). 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 y décrire ce que font les fonctions. On y fait généralement figurer trois choses :

  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 la fonction ajouteDeux():

#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.hqu'il existe des systèmes quasi-automatiques qui génèrent des sites web ou des livres à partir de ces commentaires.

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

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. À 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.
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 en fonction d'un nombre d'heures, de minutes et de secondes qu'on lui transmet. Rien de bien compliqué !

Les variablesheures,minutesetsecondessont les paramètres de la fonctionnombreDeSecondes(). Ce sont des valeurs qu'elle reçoit, celles avec lesquelles elle va travailler.
Mais cela, 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 des fonctions. Ainsi, lorsqu'on appelle une fonction, on ne sera pas obligé d'indiquer à chaque fois tous les paramètres !

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ètreheuressera 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 lemain()). 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 valeur1correspond à 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. En effet, je vous l'ai dit plus haut, 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;

C'est interdit ! Si vous le faites, le compilateur vous fera comprendre qu'il n'apprécie guère vos manœuvres. C'est ainsi : 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 :

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.

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 (c'est-à-dire à droite).

En résumé

  • Une fonction est une portion de code contenant des instructions et ayant un rôle précis.

  • Tous les programmes ont au moins une fonction :main(). C'est celle qui s'exécute au démarrage du programme.

  • Découper son programme en différentes fonctions ayant chacune un rôle différent permet une meilleure organisation.

  • Une même fonction peut être appelée plusieurs fois au cours de l'exécution d'un programme.

  • Une fonction peut recevoir des informations en entrée (appelées arguments) et renvoyer un résultat en sortie grâce à return.

  • Les fonctions peuvent recevoir des références en argument pour modifier directement une information précise en mémoire.

  • Lorsque le programme grossit, il est conseillé de créer plusieurs fichiers regroupant des fonctions. Les fichiers .cppcontiennent les définitions des fonctions et les fichiers.h, plus courts, contiennent leurs prototypes. Les fichiers .hpermettent d'annoncer l'existence des fonctions à l'ensemble des autres fichiers du programme.

Exemple de certificat de réussite
Exemple de certificat de réussite