363 lines
15 KiB
Plaintext
363 lines
15 KiB
Plaintext
[set
|
||
title = "Fonction membres"
|
||
partAs = chapitre
|
||
translator = Hassan Azi
|
||
]
|
||
|
||
Bien que ce chapitre se concentre principalement sur les structures, une grande partie des informations détaillées ci-après peuvent être également appliquées aux classes également.
|
||
|
||
Au cours de ce chapitre, nous couvrirons les méthodes des structures, notamment la méthode spéciale [c toString()] qui est utilisée afin de représenter les objets sous forme d'une chaîne de caractères, ou [c string].
|
||
|
||
Lorsqu'une structure ou une classe est définie, on définit aussi souvent un certain nombre de fonctions avec elle. Nous avions déjà vu de exemples dans le chapitres précédents: [c ajouterDuree()] et la surcharge de [c info()] ont été écrites spécialement pour être utilisées avec le type [c TempsDuJour]. En quelque sorte, ces deux fonctions forment l'[i interface] de [c TempsDuJour].
|
||
|
||
[c ajouterDuree()] et [c info()] avaient pris pour premier paramètre un objet [c TempsDuJour] sur lequel elles allaient opérer. En plus de cela, comme c'est le cas pour toutes les fonctions jusque-là étudiées, nos deux fonctions avaient été définies au [i niveau du module] (''module level''), en dehors de toute autre portée.
|
||
|
||
Le fait qu'un ensemble de fonctions définisse l'interface d'une structure est un concept très répandu. Pour cette raison, les fonctions ayant une relation proche à un certain type peuvent être définies au sein de celui-ci.
|
||
|
||
[ = Définir des méthodes
|
||
|
||
Les fonctions définies à l'intérieur des accolades d'une [c struct] sont dites des [i méthodes]~ :
|
||
|
||
[code=d <<<
|
||
struct UneStructure
|
||
{
|
||
void methode(/* les paramètres de la fonction */)
|
||
{
|
||
// ... la définition de la fonction ...
|
||
}
|
||
|
||
// ... les autres membres de la structure ...
|
||
}
|
||
>>>]
|
||
|
||
Les méthodes peuvent être accédées de la même façon que les champs, en les séparant du nom de l'objet par un point:
|
||
|
||
[code=d <<<
|
||
[i objet.methode(arguments));
|
||
>>>]
|
||
|
||
Nous avions déjà utilisé les méthodes quand on avait pris soin de spécifier [c stdin] and [c stdout] lors des opérations d'entrée et de sortie~ :
|
||
|
||
|
||
[code=d <<<
|
||
stdin.readf(" %s", &nombre);
|
||
stdout.writeln(nombre);
|
||
>>>]
|
||
|
||
Dans ce code, nous avons effectué des appels aux méthodes [c readf()] et [c writeln()] qui opèrent respectivement sur les objets [c stdin] et [c stdout].
|
||
|
||
|
||
Définissions maintenant [c info()] en tant que méthode. Auparavant, nous l'avions définie ainsi~ :
|
||
|
||
|
||
[code=d <<<
|
||
void info(in TempsDuJour temps)
|
||
{
|
||
writef("%02s:%02s", temps.heure, temps.minute);
|
||
}
|
||
>>>]
|
||
|
||
Afin de transformer [c info()] en une méthode, il faudra non seulement déplacer sa définition à l'intérieur de la structure, mais aussi lui faire subir deux modifications~ :
|
||
|
||
[code=d <<<
|
||
struct TempsDuJour
|
||
{
|
||
int heure;
|
||
int minute;
|
||
|
||
void info() // (1)
|
||
{
|
||
writef("%02s:%02s", heure, minute); // (2)
|
||
}
|
||
}
|
||
>>>]
|
||
|
||
# La méthode ne prend plus l'objet comme paramètre.
|
||
# Suite à ceci, ele accède aux champs directement par [c heure] et [c minute].
|
||
|
||
La raison pour cela est que les méthodes sont toujours appelées sur un objet existant. Ce dernier est implicitement disponible pour la méthode~ :
|
||
|
||
[code=d <<<
|
||
auto temps = TempsDuJour(10, 30);
|
||
temps.info();
|
||
>>>]
|
||
|
||
La méthode [c info()] est appelée sur l'objet [c temps] ci-avant. Les variables [c heure] et [c minute] qui sont indiquées à l'intérieur de la définition de la fonction correspondent aux champs de l'objet [c temps], plus précisément, [c temps.heure] et [c temps.minute].
|
||
|
||
Ici, l'appel à la méthode est presque l'équivalent de l'appel à une fonction normale~ :
|
||
|
||
[code=d <<<
|
||
temps.info(); // méthode
|
||
info(temps); // fonction habituelle (la définition précédente)
|
||
>>>]
|
||
|
||
Dès qu'une méthode est appelée sur un objet, les champs de celui-ci sont implicitement accessibles pour la fonction~ :
|
||
|
||
[code=d <<<
|
||
auto matin = TempsDuJour(10, 0);
|
||
auto apresmidi = TempsDuJour(22, 0);
|
||
|
||
matin.info();
|
||
write('-');
|
||
apresmidi.info();
|
||
writeln();
|
||
>>>]
|
||
|
||
Appelée sur [c matin], les variables [c heure] et [c minute] utilisées à l'intérieur de la méthode ne sont autre que [c matin.heure] et [c matin.minute]. De même, en appelant [c apresmidi.info()], elles font référence à [c apresmidi.heure] et [c apresmidi.minute]~ :
|
||
|
||
[output <<<
|
||
10:00-22:00
|
||
>>>]
|
||
|
||
[ = [c toString()] pour les représentations sous formes de chaînes de caractères
|
||
|
||
Dans le chapitre précédent, nous venons de parler des limitations de la fonction [c info()]. Mais malgré tout ce que nous venons de voir, la fonction garde au moins un autre inconvénient: Bien qu'elle affiche le temps dans un format lisible, le programmeur devra toujours prendre soin d'expliciter le caractère [c '-'] ainsi que le caractère de fin de ligne.
|
||
|
||
Il serait plus pratique si les objets [c TempsDuJour] pouvaient être utilisés comme des types fondamentaux dans le code ci-après~ :
|
||
|
||
[code=d <<<
|
||
writefln("%s-%s", matin, apresmidi);
|
||
>>>]
|
||
|
||
En plus d'avoir réduit quatre lignes de code en seulement une, cela aurait aussi pour effet de pouvoir écrire l'objet à n'importe quel flux~ :
|
||
|
||
[code=d <<<
|
||
auto fichier = File("temps_information", "w");
|
||
fichier.writefln("%s-%s", matin, apresmidi);
|
||
>>>]
|
||
|
||
La méthode [c toString()] des types définis par l'utilisateur est un peu spéciale : elle est automatiquement appelée pour produire la représentation [c string] des objets. [c toString()] doit impérativement retourner la représentation [c string] de l'objet en question.
|
||
|
||
Avant de plonger dans les détails, étudions d'abord la définition de la fonction [c toString()]~ :
|
||
|
||
[code=d <<<
|
||
import std.stdio;
|
||
|
||
struct TempsDuJour
|
||
{
|
||
int heure;
|
||
int minute;
|
||
|
||
string toString()
|
||
{
|
||
return "a_faire";
|
||
}
|
||
}
|
||
|
||
void main()
|
||
{
|
||
auto matin = TempsDuJour(10, 0);
|
||
auto apresmidi = TempsDuJour(22, 0);
|
||
|
||
writefln("%s-%s", matin, apresmidi);
|
||
}
|
||
>>>]
|
||
|
||
Pour le moment, [c toString()] ne produit pas grand chose, mais l'exécution du code ci-avant montre qu'elle a effectivement été appelée à deux reprises par [c writefln()]~ :
|
||
|
||
[output <<<
|
||
a_faire-a_faire
|
||
>>>]
|
||
|
||
Remarquez aussi que [c info()] est désormais redondante vu que [c toString()] offre la même fonctionnalité.
|
||
|
||
La façon la plus simple d'implémenter [c toString()] serait d'appeler la fonction [c format()], celle-ci est disponible dans le module [c std.string]. Le fonctionnement de [c format()] est similaire à celui des fonctions d'affichage formaté comme [c writef()]. La seule différence réside dans le fait qu'au lieu d'afficher directement les variables, [c format()] retourne le résultat sous la forme d'une [c string].
|
||
|
||
|
||
[c toString()] peut donc directement retourner le résultat de [c format()]~ :
|
||
|
||
[code=d <<<
|
||
import std.string;
|
||
// ...
|
||
struct TempsDuJour
|
||
{
|
||
// ...
|
||
string toString()
|
||
{
|
||
return format("%02s:%02s", heure, minute);
|
||
}
|
||
}
|
||
>>>]
|
||
|
||
Remarquez que [c toString()] retourne uniquement la représentation textuelle de l'objet [i this]. [c writefln()] se charge d'afficher le reste: elle appelle la méthode [c toString()] pour chacun des deux objets tout en prenant soin d'afficher le caractère [c '-'] entre les deux appels, puis termine la ligne~ :
|
||
|
||
[output <<<
|
||
10:00-22:00
|
||
>>>]
|
||
|
||
La définition de [c toString()] qui a été expliquée ci-avant ne prend aucun paramètre; elle produit simplement une [c string] et la retourne. Cependant, il existe une autre façon de définir [c toString()], celle-ci prend en paramètre un [c delegate]. Nous étudierons cette définition plus tard dans le chapitre sur les pointeurs de fonctions, les delegates et les lambdas.
|
||
]
|
||
[ = Exemple: la méthode [c increment())]
|
||
|
||
Définissons une méthode qui ajoute une certaine durée aux objets [c TempsDuJour].
|
||
|
||
Avant d'aller plus loin, corrigeons d'abord un défaut de conception qui était là tout le temps. Dans le chapitre sur des structures, nous avions vu que l'addition de deux objets [c TempsDuJour] dans [c ajouterDuree] n'avait pas de sens~ :
|
||
|
||
[code=d <<<
|
||
TempsDuJour ajouterDuree(in TempsDuJour start,
|
||
in TempsDuJour duree) // absurde
|
||
{
|
||
// ...
|
||
}
|
||
>>>]
|
||
|
||
Il serait plus naturel d'ajouter une [i durée] à un point dans le temps. En ajoutant par exemple la durée d'un voyage à son temps de départ, on obtiendrait le temps d'arrivée.
|
||
|
||
D'un autre côté, soustraire deux points dans le temps est une opération naturelle, et dans ce cas le résultat serait une [i durée].
|
||
|
||
Le programme ci-après définit une structure [c Duree] d'une précision allant à la minute, ainsi qu'une fonction [c ajouterDuree()] qui l'utilise:
|
||
|
||
|
||
[code=d <<<
|
||
struct Duree
|
||
{
|
||
int minute;
|
||
}
|
||
|
||
TempsDuJour ajouterDuree(in TempsDuJour debut,
|
||
in Duree duree)
|
||
{
|
||
// Commençons par faire une copie de start
|
||
TempsDuJour resultat = debut;
|
||
|
||
// Ajoutons-y la durée
|
||
resultat.minute += duree.minute;
|
||
|
||
// Puis prenons soin de régler les débordements
|
||
resultat.heure += resultat.minute / 60;
|
||
resultat.minute %= 60;
|
||
resultat.heure %= 24;
|
||
|
||
return resultat;
|
||
}
|
||
|
||
unittest
|
||
{
|
||
// Un test banal
|
||
assert(ajouterDuree(TempsDuJour(10, 30), Duree(10))
|
||
== TempsDuJour(10, 40));
|
||
|
||
// Un temps à minuit
|
||
assert(ajouterDuree(TempsDuJour(23, 9), Duree(51))
|
||
== TempsDuJour(0, 0));
|
||
|
||
// Un temps dans le jour suivant
|
||
assert(ajouterDuree(TempsDuJour(17, 45), Duree(8 * 60))
|
||
== TempsDuJour(1, 45));
|
||
}
|
||
>>>]
|
||
|
||
Maintenant, redéfinissons cette fonction sous la forme d'une méthode. Jusqu'à présent, [c ajouterDuree()] retournait un nouvel objet à chaque appel. Définissons donc une méthode [c incrementer()] qui modifiera directement l'objet [i this]~ :
|
||
|
||
[code=d <<<
|
||
struct Duree
|
||
{
|
||
int minute;
|
||
}
|
||
|
||
struct TempsDuJour
|
||
{
|
||
int heure;
|
||
int minute;
|
||
|
||
string toString()
|
||
{
|
||
return format("%02s:%02s", heure, minute);
|
||
}
|
||
|
||
void $(HILITE incrementer)(in Duree duree)
|
||
{
|
||
minute += duree.minute;
|
||
|
||
heure += minute / 60;
|
||
minute %= 60;
|
||
heure %= 24;
|
||
}
|
||
|
||
unittest
|
||
{
|
||
auto temps = TempsDuJour(10, 30);
|
||
|
||
// Un test banal
|
||
temps$(HILITE .incrementer)(Duree(10));
|
||
assert(temps == TempsDuJour(10, 40));
|
||
|
||
// 15 heures plus tard devra être dans le jour suivant
|
||
temps$(HILITE .incrementer)(Duree(15 * 60));
|
||
assert(temps == TempsDuJour(1, 40));
|
||
|
||
// 22 heures et 20 minutes devra être à minuit
|
||
temps$(HILITE .incrementer)(Duree(22 * 60 + 20));
|
||
assert(temps == TempsDuJour(0, 0));
|
||
}
|
||
}
|
||
>>>]
|
||
|
||
[c incrementer()] incrémente la valeur de l'objet dépendamment de la durée qu'on lui passe. Plus tard, nous étudierons comment la surcharge d'opérateur ([i operator overloading]) va nous permettre d'ajouter une durée à l'aide de l'opérateur [c +=]~ :
|
||
|
||
[code=d <<<
|
||
temps += Duree(10); // sera expliqué dans un chapitre ultérieur
|
||
>>>]
|
||
|
||
Notez également que les blocs de tests unitaires [c unittest] peuvent être écrits à l'intérieur des définitions des [c struct] aussi, c'est surtout le cas lorsque l'on souhaite tester ses méthodes. Mais il reste tout de même possible de déplacer de tels blocs [c unittest] en dehors du corps de la structure:
|
||
|
||
[code=d <<<
|
||
struct TempsDuJour
|
||
{
|
||
// ... définition de la structure ...
|
||
}
|
||
|
||
unittest
|
||
{
|
||
// ... tests de la structure ...
|
||
}
|
||
>>>]
|
||
]
|
||
]
|
||
|
||
[ = Exercices
|
||
|
||
# Ajoutez une méthode [c decrementer()] à [c TempsDuJour] qui en retrancherait la durée de temps spécifiée. Tout comme pour [c incrementer()], elle devrait repasser au jour précédent s'il n'y a pas assez de temps dans le jour actuel. Par exemple, soustraire 10 minutes de 00:05 devra donner pour résultat 23:55.
|
||
|
||
En d'autres termes, implémentez [c decrementer()] de façon à ne pas échouer les tests unitaires suivant~ :
|
||
|
||
[code=d <<<
|
||
struct TempsDuJour
|
||
{
|
||
// ...
|
||
|
||
void decrementer(in Duree duree)
|
||
{
|
||
// ... veuillez implémenter cette fonction ...
|
||
}
|
||
|
||
unittest
|
||
{
|
||
auto temps = TempsDuJour(10, 30);
|
||
|
||
// Un test banal
|
||
temps.decrementer(Duree(12));
|
||
assert(temps == TempsDuJour(10, 18));
|
||
|
||
// 3 jours and 11 heures plus tôt
|
||
temps.decrementer(Duree(3 * 24 * 60 + 11 * 60));
|
||
assert(temps == TempsDuJour(23, 18));
|
||
|
||
// 23 heures and 18 minutes plus tôt devra être à minuit
|
||
temps.decrementer(Duree(23 * 60 + 18));
|
||
assert(temps == TempsDuJour(0, 0));
|
||
|
||
// 1 minute plus tôt
|
||
temps.decrementer(Duree(1));
|
||
assert(temps == TempsDuJour(23, 59));
|
||
}
|
||
}
|
||
>>>]
|
||
|
||
# Convertissez également les surcharges de [c infos()] en méthodes [c toString()] des structures [c Reunion], [c Repas] et [c MomentDeLaJournee]. (Consulter [[doc:corrections/surcharge_fonctions | les solutions du chapitre sur les surcharges de fonctions]] pour voir comment les différentes implémentations de [c info()]).
|
||
|
||
Vous remarquerez qu'en plus de rendre leurs structures respectives plus pratiques, les implémentations des méthodes [c toString()] ne prendront chacune qu'une ligne de code.
|
||
|
||
[[part:corrections/fonctions_membres | … Les solutions]]
|
||
]
|