Aller au menu - Aller au contenu

Icône Nouveautés pour les fonctions

Mise à jour : 22/02/2011
Difficulté : Facile Facile
1 016 visites depuis 7 jours, dont 108 sur ce chapitre classé 124/786
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 du C++.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire

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ées 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 :

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

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 :

  • Un type de retour
  • Un nom
  • Une liste de paramètres


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 :

  • Le nombre de paramètres
  • Le type de chacun de ces paramètres


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

Chapitre précédent Sommaire

Partager

32 commentaires pour "Nouveautés pour les fonctions"
Note moyenne : 3.72 / 4 (43 votes)
Pseudo Commentaire
Hors ligne i00k # Posté le 18/09/2010 à 14:50:33
Waboooooow
Avatar

Ville : Sèvres
Pays : France métropolitaine

Ta fait l'école du rire toi...
 
Hors ligne sibemol38 # Posté le 10/11/2010 à 10:15:15

Je lis ça dans le cours : "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)."

Pour quand quelque chose surcharge quelque chose d'autre, il le remplace entièrement, c'est à dire qu'il n'y plus possibilité directement d'accéder à la première chose.

C'est le cas lorsque dans une classe dérivée, on redéfinit une fonction héritée de la classe mère. Dans ce cas l'appel de la fonction d'un objet dérivé appellera la fonction redéfini et non plus celui de la classe mère. La fonction mère s’efface au profit de la fonction fille.

Le fait d'avoir une fonction qui a le même nom, mais des paramètres différents est un polymorphisme de fonction. Aucune fonction ne s'efface, toutes les fonctions du même nom coexistent toutes en même temps. On peut bien appeler soit l'une soit l'autre, en modifiant le nombre de paramètre.

Polymorphe : qui a des formes différentes. Et toutes ses formes existent.

Pour qu'une surcharge de fonction soit effective il faut absolument qu'elle prenne la même signature que la fonction qu'elle surcharge justement.
Connecté lmghs # Posté le 11/02/2011 à 19:03:07

Je réponds au dernier post.
- surcharge == overload en VO; c'est un des 2 polymorphismes ad'hoc (l'autre étant la coercion, plus précisément: les conversions implicites). Même nom, signature différente. C'est tout. Et le choix de la bonne fonction est complètement déterminé par le type statique en C++.
- redifinition == override en VO; c'est lié à l'un des 2 polymorphismes universels : le polymorphisme d'inclusion (aka d'héritage). Même nom, même signature, et fonction virtuelle obligatoirement. La redéfinition supplante la définition de la classe mère. Le choix de la bonne fonction est déterminé par le type dynamique de l'objet en C++. Ce n'est pas une surcharge par définition (la norme est précise sur le vocabulaire employé). Sans le virtuel tout en haut de la hiérarchie, nous avons une forme dégénérée de la surcharge : le masquage.

Et pour une définition de polymorphisme intelligible, cf la FAQ de développez où les 4 polymorphismes que l'on retrouve en C++ sont également abordés.
 
Hors ligne l0lmanPH # Posté le 18/02/2011 à 19:45:34
Avance sale bourrique !
Avatar

Ville : Mougins
Pays : France métropolitaine

Il est possible que j'aie mal lu, mais si ce n'est pas le cas il serait utile de préciser que les fonctions inline ne sont utilisables que dans les fichiers ou elles sont déclarées (je me trompe ?).

Je critique je critique mais même s'il date c'est le tutoriel qui m'as appris le langage alors bravo. :p

Image utilisateur
« Un même visage, un même passé, deux destins différents ? »
Metroid Prime : Némésis, fangame de la suite du célèbre Metroid Prime 3 : Corruption.

Image utilisateur
Gros tutoriel sur Game Maker et le GML.
Atelier de Noël : voir le projet NowHell.
 
Hors ligne konfiot # Posté le 03/05/2011 à 16:21:52
Ex cczerty; RTFM = 42;
Avatar

Ville : Eaubonne
Pays : France métropolitaine

La suite arrivera-t-elle ou faut-il lire le tuto sur le C++.
En tout cas, très bon tuto, même si je pige pas a quoi servent les références (ils ont quelque chose contre les pointeurs ou quoi ???)

2.25 Go d'espace disque accessible partout et gratuitement |||||| Nom de domaine Gratuit
Citation : Moi
Mon rêve c'est de pouvoir dire, un jour : quand j'étais jeune, j'étais con

Halte aux bots |||||| Lecteur de pensées (ce n'est pas un attrape idiots)
 

Voir tous les commentaires