Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Officiels > Programmation > Apprenez à programmer en C++ ! > [Théorie] La Programmation Orientée Objet > Nouveautés pour les fonctions > Lecture du tutoriel

Nouveautés pour les fonctions

Avatar
Auteur : M@teo21
Note : 18 / 20 (10 votes)
Visualisations : 106 470


Plus d'informations Plus d'informations
Nous avons vu que le C++ proposait de nombreuses nouveautés relatives pour les variables.
Ce chapitre est la suite du précédent, mais est cette fois axé sur les nouveautés relatives aux fonctions.

Ce chapitre sera un peu plus court car il y a assez peu de changements au final. Ne vous endormez pas pour autant parce que vous allez découvrir les valeurs par défaut et les fonctions surchargées, deux éléments très importants que nous réutiliserons largement dans la suite.

Courage, c'est le dernier chapitre avant la POO (ou plutôt devrais-je dire : "Profitez-en bien mes petits ! :diable: ").
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Des valeurs par défaut pour les paramètres

Si je vous dis "paramètre de fonction", vous voyez de quoi je parle n'est-ce pas ?
Je l'espère, parce qu'il serait temps de le savoir à votre niveau maintenant ^^


Bon allez, un petit rappel !



Comme un petit rappel ne fait jamais de mal, voici un exemple de fonction :

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.
Il est facile de reconnaître les paramètres d'une fonction, car ceux-ci se trouvent toujours écrits entre les parenthèses ;)


Les valeurs par défaut



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

Pour bien voir comment on doit procéder, on va regarder le code complet. J'aimerais que vous le copiez dans votre IDE pour faire les tests en même temps que moi :

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é 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és uniquement dans le prototype, PAS dans la définition de la fonction ! 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


Du temps 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 manoeuvres. 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 :


La surcharge des fonctions

Ca, c'est probablement la nouveauté la plus importante des fonctions ! Cela nous aidera énormément lorsque nous ferons de la POO un peu plus loin :)

De quoi s'agit-il ? D'un nouveau système en C++ qui permet de surcharger des fonctions.
En gros, et pour faire simple, c'est une technique qui nous permet de créer plusieurs fonctions ayant le même nom... sans que le compilateur crie au loup :p


La signature d'une fonction



Avant toute chose, il faut que je vous parle de ce qu'on appelle la signature d'une fonction. C'est un peu sa carte d'identité, ce qui permet au compilateur de différencier les fonctions entre elles.

Chaque fonction est constituée de 3 éléments, ni plus ni moins :



On va représenter ça sur un schéma pour être sûr qu'on voie bien la même chose ^^

Image utilisateur


Le compilateur se moque complètement des noms des variables passées en paramètre. Ce qui compte pour lui, c'est juste le type de ces paramètres. Je vous l'avais d'ailleurs dit dans le chapitre sur la compilation modulaire ;)
Voilà donc pourquoi j'ai marqué (int, int, int) pour les paramètres. C'est ce que le compilateur "voit", le nom des variables est donc ignoré pour l'identification de la fonction.


Bon, qu'est-ce qui permet d'identifier une fonction d'après vous ? Comment le compilateur fait-il pour vérifier si une fonction est bien différente d'une autre ?

En C, le compilateur se basait sur le nom, et uniquement sur le nom. Si 2 fonctions avaient le même nom, la compilation plantait. L'identification était donc faite sur le nom.
En C++, le compilateur se base sur le nom et les paramètres ! On peut avoir du coup 2 fonctions avec le même nom, à condition que celles-ci reçoivent des paramètres différents.

Le nom et les paramètres de la fonction constituent ce qu'on appelle la signature de la fonction. C'est ce qui permet au compilateur de l'identifier en C++.

Image utilisateur


Le type de retour n'est donc pas pris en compte pour identifier la fonction.


La surcharge d'une fonction



La surcharge consiste à créer des fonctions qui ont le même nom, mais qui ont des paramètres différents (donc une signature différente).

Voici ce qui peut varier :



Encore une fois, je le rappelle, le nom que l'on donne à chacun des paramètres, le compilo il s'en fout complètement :p

Prenons un exemple pour bien comprendre ce que ça va nous permettre de faire. Imaginez une fonction addition. On peut additionner des entiers (int), mais aussi des décimaux (double).

En C, il aurait fallu nommer les deux fonctions différemment (par exemple sommeEntiers et sommeDecimaux). En C++, on peut leur donner le même nom et ça va grandement simplifier leur utilisation, vous allez voir.

Code : C++
1
2
3
4
5
6
7
8
9
int somme(int nb1, int nb2)
{
    return nb1 + nb2;
}

double somme(double nb1, double nb2)
{
    return nb1 + nb2;
}


Les prototypes de ces fonctions sont donc :

Code : C++
1
2
int somme(int nb1, int nb2);
double somme(double nb1, double nb2);


Leurs signatures sont :

Code : C++
1
2
somme(int, int)
somme(double, double)


Ces fonctions ont des signatures différentes et portent le même nom. Ce sont des fonctions surchargées :)


Maintenant, dans le main on peut faire appel à la fonction somme pour additionner indifféremment des entiers ou des décimaux. C'est le compilateur qui décide quelle fonction il appelle en fonction du nombre et du type des paramètres.

Voici un code complet que vous pouvez tester :

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
#include <iostream>

using namespace std;


int somme(int nb1, int nb2);
double somme(double nb1, double nb2);

int main()
{
    cout << somme(10, 15) << endl << somme(2.5, 0.3) << endl;

    return 0;
}

int somme(int nb1, int nb2)
{
    return nb1 + nb2;
}

double somme(double nb1, double nb2)
{
    return nb1 + nb2;
}


Résultat :

Code : Console
25

2.8


On a appelé 2 fois la fonction "somme", mais c'est en fait une fonction différente qui a été appelée à chaque fois ^^

Vous pouvez surcharger la fonction autant de fois que vous le désirez. On pourrait donc aussi rajouter par exemple la fonction qui fait la somme d'un entier et d'un décimal :

Code : C++
1
2
3
4
double somme(int nb1, double nb2)
{
    return nb1 + nb2;
}


... ou encore celle qui fait la somme de 3 entiers :

Code : C++
1
2
3
4
int somme(int nb1, int nb2, int nb3)
{
    return nb1 + nb2 + nb3;
}


Les possibilités sont infinies ^^

Bien entendu, on fait de la surcharge de fonction pour des choses plus "intéressantes" que des sommes, mais ça on le découvrira petit à petit en fonction de nos besoins.

Les fonctions inline

Ce que nous allons voir ici ressemble à beaucoup aux macros (relisez le chapitre sur le préprocesseur si vous avez oublié ce que c'est ;) ).

Les macros sont un bon moyen, utilisées intelligemment, d'accélérer la vitesse d'exécution du programme si certains bouts de code sont souvent réutilisés.
Toutefois, les macros sont assez délicates à manipuler et impliquent l'utilisation du préprocesseur.

En C++, on a inventé le mot-clé inline qui permet de faire, grosso modo, la même chose que les macros sans cette fois passer par le préprocesseur. C'est donc le compilateur qui se charge de faire le "remplacement de code" au moment de la compilation. L'avantage, c'est qu'on peut faire plus de vérifications (notamment sur les types des paramètres).


Exemple d'utilisation d'une fonction inline



Prenons l'exemple suivant (on le discutera ensuite) :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
inline int carre(int nombre);

int main()
{
    cout << carre(10) << endl;

    return 0;
}

inline int carre(int nombre)
{
    return nombre * nombre;
}


Vous voyez que j'ai ajouté le mot-clé inline au début du prototype ET au début de la définition de la fonction. Cela signifie pour le compilateur "A chaque fois qu'on fera appel à la fonction carre, je placerai directement le code de cette fonction à l'endroit de l'appel".

En clair, après compilation voici ce qu'il restera dans votre exécutable :

Code : C++
1
2
3
4
5
6
int main()
{
    cout << 10 * 10 << endl;

    return 0;
}


La fonction inline disparaît complètement après compilation. Tout son code se trouve placé à l'endroit de l'appel (la ligne du cout dans notre cas).

L'avantage est que l'exécution du programme sera plus rapide, surtout si la fonction est appelée plusieurs fois. En effet, lors d'un appel "classique" de fonction, le processeur va sauter à l'adresse de la fonction en mémoire, retenir l'adresse où il en était pour revenir à la fonction appelante une fois l'autre fonction terminée... Bref, c'est très rapide, mais si la fonction est amenée à être appelée très souvent, il est préférable d'en faire une inline (on dit l'inliner :-° ) pour éviter de répéter tout ce processus.
Le défaut, c'est que le programme risque de grossir un peu une fois compilé (le même code étant répété dans l'exécutable). Mais bon, en général cette différence est quand même négligeable ;)

En règle générale, les fonctions inline sont donc des fonctions très courtes que l'on est susceptible de réutiliser souvent, comme c'est le cas de la fonction carre ici.


En pratique, on utilise quand même assez peu les fonctions inline, sachez-le (c'est comme les macros, je ne pense pas que vous vous en soyez beaucoup servis jusqu'ici ;) ). Ca reste cependant une des nouveautés du C++ relatives aux fonctions que je devais vous présenter :)


pssst, puisqu'on y est, serez-vous capables de surcharger la fonction inlinée carre pour qu'elle calcule le carré d'un nombre décimal ? :-°

Q.C.M.

Quelle est la signature de cette fonction ?


Code : C++
1
inline int carre(int nombre);
Quels sont les paramètres facultatifs de cette fonction ?


Code : C++
1
void ouvrirFenetre(const char *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);
Toujours avec la même fonction, est-il possible de centrer une fenêtre dont le titre serait "Ma fenêtre" sans préciser de dimensions ?
Qu'est-ce que j'ai fait de mal dans ma surcharge de fonction qui m'empêche de compiler ici ?


Code : C++
1
2
3
int division(int dividende, int diviseur);
double division(double dividende, double diviseur);
double division(int nombre1, int nombre2);

Statistiques de réponses au QCM


Bon, vous êtes capables de surcharger des fonctions inlinées avec des paramètres par défaut, qu'est-ce qui pourrait bien vous faire peur maintenant ? :pirate:
Oh, mais ne faites pas les malins, tout ceci n'était qu'une misérable mise en bouche comparé à ce qui vous attend ^^

En effet, dans le prochain chapitre on va rentrer en plein dans le coeur du C++ : on va découvrir la programmation orientée objet. Bien sûr, on va y aller pas à pas, en douceur, sinon ça risque d'être un peu... violent :-°

Quand vous êtes prêts, rendez-vous au proch... bon, je suis déjà dans le chapitre suivant moi, qu'est-ce que vous attendez ? :p
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 18/09/2007 à 17:13:58
Modifié : le 23/10/2008 à 14:17:50
Avancement : 100%
Licence : Copie non autorisée

22 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 554 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.1523s (0.1282s)