53. Surcharge de fonctions▲
On appelle surcharger une fonction le fait de définir plusieurs fonctions ayant toutes le même nom. Pour pouvoir les différencier, leurs paramètres doivent être différents.
Le code suivant montre plusieurs surcharges de la fonction info(), chacune prenant un type de paramètre différent :
import
std.stdio;
void
info
(
in
double
nombre)
{
writeln
(
"Virgule flottante~ : "
, nombre);
}
void
info
(
in
int
nombre)
{
writeln
(
"Entier~ : "
, nombre);
}
void
info
(
in
char
[] chaine)
{
writeln
(
"Chaîne de caractères~ : "
, chaine);
}
void
main
(
)
{
info
(
1
.2
);
info
(
3
);
info
(
"bonjour"
);
}
Bien que toutes les fonctions soient nommées info(), le compilateur choisit celle qui correspond à l'argument qui est utilisé lors de l'appel. Ici, la fonction info() prenant un double est appelée parce que la constante littérale 1.2 est de type double.
Le choix de la fonction à appeler est fait à la compilation, ce qui n'est pas toujours facile ou évident. Par exemple, puisque le type int peut être implicitement converti en type double et en type real, le compilateur ne peut choisir quelle fonction employer dans le programme suivant :
real
septFois
(
in
real
valeur)
{
return
7
*
valeur;
}
double
septFois
(
in
double
valeur)
{
return
7
*
valeur;
}
void
main
(
)
{
int
valeur =
5
;
auto
resultat =
septFois
(
valeur); // ← ERREUR de compilation
}
Il n'est généralement pas nécessaire d'écrire des fonctions séparées quand les corps des fonctions sont identiques. Nous verrons plus tard dans le chapitre des templates comment écrire une définition de fonction unique qui peut être utilisée avec des types différents.
Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre long, alors l'ambiguïté est résolue, car long correspond mieux à int que double ou real :
long
septFois
(
in
long
valeur)
{
return
7
*
valeur;
}
// ...
auto
resultat =
septFois
(
valeur); // compile maintenant
53-1. Résolution de surcharge▲
Le compilateur choisit la surcharge qui correspond le mieux aux arguments. C'est ce que l'on appelle la résolution de surcharge.
Bien que la résolution soit simple et intuitive la plupart du temps, ce n'est pas toujours le cas. Voici les règles de résolution de surcharge. Elles sont présentées de manière simplifiée dans ce livre.
Il y a quatre niveaux de correspondance, du pire au meilleur :
- pas de correspondance ;
- correspondance via conversion automatique de type ;
- correspondance via qualification const ;
- correspondance exacte.
Le compilateur considère toutes les surcharges d'une fonction pendant la résolution de surcharge. Si la fonction a plusieurs paramètres, le niveau de correspondance global d'une fonction est le plus mauvais des niveaux de correspondance de tous les paramètres.
Après que tous les niveaux de correspondance ont été déterminés, la surcharge avec la meilleure correspondance est choisie. S'il y a plusieurs surcharges qui ont la même correspondance, alors des règles plus complexes sont appliquées. Je ne rentrerai pas dans les détails de ces règles dans ce livre. Si votre programme en est au point où il dépend de règles complexes de résolution de surcharge de fonctions, c'est peut-être un signe qu'il est temps d'en revoir la conception. Une autre option est de profiter d'autres fonctionnalités de D comme des templates. Une approche encore plus simple serait de nommer chaque fonction différemment pour chaque type comme septFois_real() et septFois_double().
53-2. Surcharge de fonctions pour les types définis par l'utilisateur▲
La surcharge de fonctions s'utilise également avec les structures et les classes. Qui plus est, les ambiguïtés liées à la surcharge de fonctions sont bien moins fréquentes avec les types définis par l'utilisateur. Surchargeons la fonction info() précédente avec quelques-uns des types que nous avons définis dans le chapitre sur les structures :
struct
MomentDeLaJournee
{
int
heure;
int
minute;
}
void
info
(
in
MomentDeLaJournee moment)
{
writef
(
"%02s:%02s"
, moment.heure, moment.minute);
}
La surcharge permet aux objets MomentDeLaJournee d'être utilisés avec info(). Finalement, les types définis par l'utilisateur peuvent être affichés de la même manière que les types fondamentaux :
auto
momentPetitDejeuner =
MomentDeLaJournee
(
7
, 0
);
info
(
momentPetitDejeuner);
Les objets MomentDeLaJournee correspondent à cette nouvelle surcharge de info() :
00
:00
:00
Le code suivant est une surcharge de info() pour le type Reunion :
struct
Reunion
{
string sujet;
size_t nombreDeParticipants;
MomentDeLaJournee debut;
MomentDeLaJournee fin;
}
void
info
(
in
Reunion reunion)
{
info
(
reunion.debut);
write
(
'-'
);
info
(
reunion.fin);
writef
(
" réunion \"%s\" avec %s personnes"
, reunion.sujet,
reunion.nombreDeParticipants);
}
Veuillez noter que cette surcharge utilise la surcharge déjà définie pour MomentDeLaJournee. Les objets Reunion peuvent maintenant être affichés comme le sont les types fondamentaux :
auto
reunionCyclisme =
Reunion
(
"Cyclisme"
, 3
, MomentDeLaJournee
(
9
, 0
),
MomentDeLaJournee
(
9
, 10
));
Résultat :
09
:00
-09
:10
réunion "Cyclisme"
avec 3
personnes
53-3. Limitations▲
Bien que nos surcharges de la fonction info() soient très pratiques, cette méthode présente quelques limitations :
-
info() écrit systématiquement sur la sortie standard. Elle serait nettement plus utile si elle pouvait écrire dans n'importe quel fichier. Une solution pour cela serait de passer également le flux de sortie en paramètre. Par exemple, pour le type MomentDeLaJournee :
Sélectionnezvoid
info
(
File fichier,in
MomentDeLaJournee temps){
fichier.writef
(
"%02s:%02s"
, temps.heure, temps.minute);}
-
Cela permettrait d'écrire des objets MomentDeLaJournee dans n'importe quel fichier, y compris la sortie standard :
Sélectionnezinfo
(
stdout, momentPetitDejeuner);auto
fichier=
File
(
"un_fichier"
,"w"
);info
(
fichier, momentPetitDejeuner); -
Note : les objets spéciaux stdin, stdout et stderr sont de type File.
-
Plus important : info() ne permet pas de produire une représentation textuelle de variables. Par exemple, elle ne permet pas de passer des types utilisateur à writeln() :
Sélectionnezwriteln
(
momentPetitDejeuner);// Écrit avec le format générique, inutile.
-
Le code qui précède affiche l'objet dans un format générique qui contient son type et les valeurs de ses membres, d'une manière qui n'est pas pertinente pour le programme :
SélectionnezMomentDeLaJournee
(
7
,0
) - Il serait beaucoup plus utile d'avoir une fonction qui convertisse des objets MomentDeLaJournee en string dans leur format spécial "12:34". Nous verrons comment définir des représentations textuelles d'objets structures dans le prochain chapitre.
53-4. Exercice▲
Surchargez la fonction info() pour les structures suivantes :
struct
Repas
{
MomentDeLaJournee moment;
string adresse;
}
struct
planningDeLaJournee
{
Reunion reunionMatin;
Repas repas;
Reunion reunionApresMidi;
}
Puisque Repas a seulement un moment de début, ajoutez une heure et demie pour déterminer sa fin. Vous pouvez utiliser la fonction ajouterDuree que nous avons définie précédemment dans le chapitre sur les structures :
MomentDeLaJournee ajouterDuree
(
in
MomentDeLaJournee debut,
in
MomentDeLaJournee duree)
{
MomentDeLaJournee resultat;
resultat.minute =
debut.minute +
duree.minute;
resultat.heure =
debut.heure +
duree.heure;
resultat.heure +=
resultat.minute /
60
;
resultat.minute %=
60
;
resultat.heure %=
24
;
return
resultat;
}
Une fois que les moments de fin des objets repas seront calculés au moyen de ajouterDuree(), les objets planningDeLaJournee devront être affichés ainsi :
10
:30
-11
:45
Réunion "Cyclisme"
avec 4
personnes
12
:30
-14
:00
Repas, adresse : Istamboul
15
:30
-17
:30
Réunion "Budget"
avec 8
personnes