• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

Ce cours existe en livre papier.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 17/10/2022

Découpez votre programme en fonctions

Créez et appelez une fonction

Écrivez une fonction simple

Dans la vidéo suivante, nous allons créer ensemble une fonction  ajouteDeux  et on va voir comment l'appeler dans le  main  :

Créez une fonction qui prend plusieurs paramètres

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 :

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.

Créez une fonction sans arguments

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

Mais à quoi ça sert ?

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

Ce type de fonction est plus rare, mais je suis sûr que vous trouverez plein d'exemples par la suite !

Créez une fonction qui ne renvoie 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.

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

Passons à la pratique, et voyons quelques exemples d'applications !

Pratiquez avec ces exemples d'application

Exemple 1 : une fonction qui calcule le carré d'un nombre

Commençons avec un exemple simple : calculer le carré d'un nombre. Cette fonction reçoit un nombre x en 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;
}

Voyons ce qui se passe dans ce programme, et dans quel ordre sont exécutées les lignes :

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 !

Exemple 2 : des fonctions différentes avec des variables de même nom

#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 nombre dans 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 de même nom.

Mais pourquoi faire ça ?

Exemple 3 : une fonction à deux arguments

Je vous propose une fonction qui a besoin de deux arguments pour dessiner un rectangle d'étoiles * dans la console : 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
****************
****************
****************

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 :

  1. Afficher un message d'erreur si la hauteur ou la largeur est négative.

  2. Ajouter un argument pour le symbole à utiliser dans le dessin.

Allez plus loin avec ces notions avancées

La fin de ce chapitre est consacrée à trois notions un peu plus avancées :

  1. Le passage par valeur.

  2. Le passage par référence.

  3. Le passage par référence constante.

Vous pourrez toujours y revenir plus tard si nécessaire.

Notion avancée 1 : le 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 :

#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

Qu'est-ce qu'il se passe à la ligne resultat = ajouteDeux(nombre);  ?

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 ajoute 2 à la variable a  .

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

  6. On sort alors de la fonction.

Lorsque le programme se situe dans la fonction, la mémoire ressemble donc à ça :

La variable nombre est copiée dans une nouvelle case mémoire. On dit que l'argument a est passé par valeur. Et on se retrouve avec 3 cases dans la mémoire.
É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.

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

Notion avancée 2 : le passage par référence

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 :

int ajouteDeux(int& a) //Notez le petit & !!!
{
    a+=2;
    return a;
}

Jetons un coup d'œil à la mémoire dans ce cas :

Lorsque l'on appelle la fonction, il n'y a plus de copie. Le programme donne simplement un alias à la variable nombre. Cette fois, la variable a et la variable nombre sont confondues. On dit que l'argument a est passé par référence.
État de la mémoire dans la fonction après un passage 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é ?

Comme a et la variable nombre correspondent à la même case mémoire, faire a+=2  a modifié la valeur de nombre  !

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

J'y viens ! L'exemple classique, c'est la fonction echange() ; elle é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

À priori, le passage par référence peut vous sembler obscur et compliqué. Vous verrez par la suite qu'il est souvent utilisé. Je vous propose donc une vidéo qui récapitule les différences entre le passage par valeur et par référence, afin de vous aider à mieux comprendre cette notion :

Notion avancée 3 : le passage par référence constante

Imaginez une fonction recevant un argument de type string : si votre chaîne de caractères contient un très long texte (la totalité d'un 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 !"

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

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.

  • Toutes les fonctions ont la forme suivante :

    type nomFonction(arguments)
    {
        //Instructions effectuées par la fonction
    }
  • 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.

Dans le prochain chapitre, vous allez voir comment utiliser plusieurs fichiers qui contiennent vos fonctions. Allez, c'est parti !

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