51. Structures▲
Comme nous l'avons vu plusieurs fois dans les précédents chapitres de ce livre, les types fondamentaux ne sont pas suffisants pour représenter des concepts de plus haut niveau. Par exemple, même si une valeur de type int peut être utilisée pour représenter une heure de la journée, deux variables int seraient ensemble plus appropriées pour représenter un point dans le temps : une pour l'heure, une pour la minute.
Les structures sont la fonctionnalité qui permet de définir de nouveaux types en combinant d'autres types déjà existants. Le nouveau type est défini par le mot-clé struct. Le gros de ce chapitre est également directement applicable aux classes ; en particulier, l'idée de combiner les types existant pour définir un nouveau type est exactement la même pour celles-ci.
Pour comprendre l'utilité des structures, considérons la fonction ajouterDuree que nous avons définie plus tôt dans le chapitre sur les assertions et enforceassert et enforce. La définition suivante est tirée des solutions de l'exercice de ce chapitre :
void
ajouterDuree
(
in
int
heuresDepart, in
int
minutesDepart,
in
int
dureeHeures, in
int
dureeMinutes,
out
int
heuresResultat, out
int
minutesResultat)
{
heuresResultat =
heuresDepart +
dureeHeures;
minutesResultat =
minutesDepart +
dureeMinutes;
heuresResultat +=
minutesResultat /
60
;
minutesResultat %=
60
;
heuresResultat %=
24
;
}
Je vais ignorer les blocs in, out et unittest dans ce chapitre pour garder les codes exemples courts.
Même s'il est clair que cette fonction prend six paramètres, quand on regarde les trois paires de paramètres de plus près, elle ne prend que trois informations : le moment de départ, la durée et le résultat.
51-1. Définition▲
Le mot-clé struct définit un nouveau type en combinant des variables qui ont un certain rapport :
struct
MomentDeLaJournee
{
int
heure;
int
minute;
}
Ce code définit un nouveau type nommé MomentDeLaJournee, qui consiste en deux variables nommées heure et minute. Cette définition permet d'utiliser le nouveau type MomentDeLaJournee dans le programme comme n'importe quel autre type. Le code suivant démontre comment son utilisation est similaire à celle d'un int :
int
nombre; // une variable
nombre =
autreNombre; // prenant la valeur de autreNombre
MomentDeLaJournee moment; // un objet
moment =
autreMoment; // prenant la valeur de autreMoment
La syntaxe de la définition struct est la suivante :
struct
NomType
{
// ... fonctions et variables membres
}
Nous verrons les fonctions membres dans des chapitres ultérieurs.
Les variables qu'une structure combine sont appelées ses membres. Selon cette définition, MomentDeLaJournee a deux membres : heure et minute.
51-1-1. struct définit un type, pas une variable▲
Il y a une distinction importante ici : surtout après les chapitres sur les espaces de nomsEspace de nom (name space) et sur les Durées de Vie et Opérations FondamentalesDurées de vie et opérations fondamentales, les accolades des définitions de structures peuvent donner la fausse impression que les membres des structures commencent et finissent leurs vies à l'intérieur de ce bloc. Ce n'est pas le cas.
Les définitions de membres ne sont pas des définitions de variables :
struct
MomentDeLaJournee
{
int
heure; // ← pas une définition de variable !
int
minute; // ← pas une définition de variable !
}
La définition d'une structure détermine les types et les noms des membres que les objets de cette structure auront :
struct
MomentDeLaJournee
{
int
heure; // ← fera partie d'une variable
int
minute; // ← fera partie d'une variable
}
Ces variables membres seront construites comme parties des objets MomentDeLaJournee qui seront utilisés dans le programme :
MomentDeLaJournee sieste; // Cet objet contient ses propres
// variables membres heure et minute.
MomentDeLaJournee reveil; // Cet objet contient également ses propres
// variables membres heure et minute.
// Les variables membres de cet objet
// n'ont pas de rapport avec les variables
// membres de l'objet précédent.
51-1-2. Confort de programmation▲
La possibilité de combiner les idées d'heure et de minute ensemble dans un nouveau type est très pratique. Par exemple, la fonction précédente peut être réécrite de façon plus parlante en prenant trois paramètres de type MomentDeLaJournee au lieu des six paramètres existants :
void
ajouterDuree
(
in
MomentDeLaJournee depart,
in
MomentDeLaJournee duree,
out
MomentDeLaJournee resultat)
{
// ...
}
Il n'est pas normal d'ajouter deux variables qui représentent deux points du temps. Par exemple, il n'y a pas de sens à ajouter le moment du déjeuner (12h00) au moment du petit-déjeuner (7h30). Il serait plus sensé de définir un autre type, convenablement appelé Duree, et d'ajouter les objets de ce type aux objets MomentDeLaJournee. Malgré cette erreur de conception, on continuera d'utiliser seulement MomentDeLaJournee dans ce chapitre et on introduira Duree dans un chapitre ultérieur.
Comme nous l'avons vu, les fonctions retournent au plus une valeur. Ceci était précisément la raison pour laquelle la fonction ajouterDuree prenait deux paramètres résultats (out) : elle ne pouvait pas retourner à la fois l'heure et la minute.
Les structures enlèvent cette limitation : comme de multiples valeurs peuvent être combinées en un seul type struct, les fonctions peuvent retourner un objet d'une telle structure, en retournant effectivement plusieurs valeurs à la fois. La fonction ajouterDuree peut maintenant retourner son résultat :
MomentDeLaJournee ajouterDuree
(
in
MomentDeLaJournee depart,
in
MomentDeLaJournee duree)
{
// ...
}
Par conséquent, ajouterDuree devient une fonction qui produit une valeur, au lieu d'être une fonction qui a des effets de bord. Comme vu dans le chapitre sur les fonctionsFonctions, il est préférable de produire des valeurs plutôt qu'avoir des effets de bord.
Les structures peuvent être des membres d'autres structures. Par exemple, la structure suivante a deux membres de type MomentDeLaJournee :
struct
Reunion
{
string sujet;
size_t nombreDeParticipants;
MomentDeLaJournee debut;
MomentDeLaJournee fin;
}
La structure Reunion peut à son tour servir de membre d'une autre structure. En supposant qu'on ait aussi une structure Repas :
struct
planningDeLaJournee
{
Reunion reunionDeProjet;
Repas dejeuner;
Reunion reunionBudget;
}
51-2. Accéder aux membres▲
Les membres de structures peuvent être utilisés comme n'importe quelle autre variable. La seule différence est que le nom de la structure et un point doivent être indiqués avant le nom du membre :
depart.heure =
10
;
La ligne ci-dessus affecte la valeur 10 au membre heure de l'objet depart.
Réécrivons la fonction ajouterDuree avec ce que nous avons vu jusqu'ici :
MomentDeLaJournee ajouterDuree
(
in
MomentDeLaJournee depart,
in
MomentDeLaJournee duree)
{
MomentDeLaJournee resultat;
resultat.minute =
depart.minute +
duree.minute;
resultat.heure =
depart.heure +
duree.heure;
resultat.heure +=
resultat.minute /
60
;
resultat.minute %=
60
;
resultat.heure %=
24
;
return
resultat;
}
On remarque que les noms des variables sont maintenant beaucoup plus courts dans cette version de la fonction : depart, duree et resultat. De plus, au lieu d'utiliser des noms complexes comme heuresDepart, il est possible d'accéder aux membres des structures à travers leurs structures respectives comme dans depart.heure.
Voici un code qui utilise la nouvelle fonction ajouterDuree. Étant donné le moment de début et la durée, le code suivant calcule l'horaire de fin d'un cours dans une école :
void
main
(
)
{
MomentDeLaJournee debutCours;
debutCours.heure =
8
;
debutCours.minute =
30
;
MomentDeLaJournee dureeCours;
dureeCours.heure =
1
;
dureeCours.minute =
15
;
immutable finCours =
ajouterDuree
(
debutCours, dureeCours);
writefln
(
"Fin du cours : %sh%s"
, finCours.heure, finCours.minute);
}
La sortie :
Fin du cours : 9h45
La fonction main a été écrite en utilisant uniquement ce que nous avions vu jusqu'à maintenant. Nous rendrons bientôt ce code encore plus clair et succinct.
51-3. Construction▲
Les trois premières lignes de main servent à construire l'objet debutCours et les trois lignes suivantes servent à construire l'objet dureeCours. Dans chacun de ces blocs de trois lignes, un objet est d'abord défini, et ensuite ses membres heure et minute sont affectés.
Pour qu'une variable soit utilisée de manière sûre, cette variable doit d'abord être construite dans un état cohérent. La construction étant une action très commune, il existe une syntaxe spéciale pour construire les structures :
MomentDeLaJournee debutCours =
MomentDeLaJournee
(
8
, 30
);
MomentDeLaJournee dureeCours =
MomentDeLaJournee
(
1
, 15
);
Les valeurs sont automatiquement affectées aux membres dans l'ordre dans lequel ils sont définis : comme heure est défini en premier dans la structure, la valeur 8 est affectée à debutCours.heure et 30 est affecté à debutCours.minute.
Comme nous le verrons dans un chapitre ultérieurConversions de Types, cast , la syntaxe de construction peut aussi être utilisée pour d'autres types :
auto
u =
ubyte
(
42
); // u est un ubyte
auto
i =
int
(
u); // i est un int
51-3-1. Construire des objets immutable ▲
Pouvoir construire l'objet en spécifiant les valeurs de ses membres au même moment permet de définir des objets immutable :
immutable debutCours =
MomentDeLaJournee
(
8
, 30
);
immutable dureeCours =
MomentDeLaJournee
(
1
, 15
);
Autrement, il ne serait pas possible de marquer un objet comme immutable et ensuite de modifier ses membres :
immutable MomentDeLaJournee debutCours;
debutCours.heure =
8
; // ERREUR de compilation
debutCours.minute =
30
; // ERREUR de compilation
51-3-2. Les membres restants n'ont pas besoin d'être spécifiés▲
Il peut y avoir moins de valeurs spécifiées qu'il n'y a de membres. Dans ce cas, les membres restants sont initialisés par les valeurs .init de leur type respectif.
Le programme suivant construit des objets Test avec un paramètre de moins à chaque construction. Les assertions indiquent que les membres non spécifiés sont automatiquement initialisés par leur valeur .init (l'utilisation de la fonction isNaN est expliquée après le programme) :
import
std.math;
struct
Test
{
char
c;
int
i;
double
d;
}
void
main
(
)
{
// Les valeurs initiales de tous les membres sont spécifiées
auto
t1 =
Test
(
'a'
, 1
, 2
.3
);
assert
(
t1.c ==
'a'
);
assert
(
t1.i ==
1
);
assert
(
t1.d ==
2
.3
);
// Il manque la dernière
auto
t2 =
Test
(
'a'
, 1
);
assert
(
t2.c ==
'a'
);
assert
(
t2.i ==
1
);
assert
(
isNaN
(
t2.d));
// Il manque les deux dernières
auto
t3 =
Test
(
'a'
);
assert
(
t3.c ==
'a'
);
assert
(
t3.i ==
int
.init));
assert
(
isNaN
(
t3.d));
// Aucune valeur initiale n'est spécifiée
auto
t4 =
Test
(
);
assert
(
t4.c ==
char
.init);
assert
(
t4.i ==
int
.init);
assert
(
isNaN
(
t4.d));
// Identique à la précédente
Test t5;
assert
(
t5.c ==
char
.init);
assert
(
t5.i ==
int
.init);
assert
(
isNaN
(
t5.d));
}
Comme nous l'avons vu dans le chapitre sur les virgules flottantesTypes à virgule flottante, la valeur initiale de double est double.nan. Comme la valeur .nan n'est pas ordonnée, l'utiliser dans des comparaisons d'égalité n'a pas de sens. C'est pourquoi appeler std.math.isNaN est la manière correcte de déterminer si une valeur est égale à .nan ou non.
51-3-3. Spécifier les valeurs par défaut des membres▲
Il est important que les variables membres soient automatiquement initialisées avec des valeurs initiales connues. Ceci évite au programme de continuer avec des valeurs indéterminées. Cependant, la valeur .init de leurs types respectifs peut ne pas convenir pour tous les types. Par exemple, char.init n'est même pas une valeur valide.
Les valeurs initiales des membres d'une structure peuvent être spécifiées lors de la définition de la structure. Ceci peut servir, par exemple, à initialiser les membres à virgule flottante à 0.0, plutôt qu'à .nan qui est souvent inexploitable.
Les valeurs par défaut sont spécifiées avec la syntaxe de l'affectation lors de la définition des membres :
struct
Test
{
char
c =
'A'
;
int
i =
11
;
double
d =
0
.25
;
}
Veuillez noter que cette syntaxe n'est pas vraiment une affectation. Le code se contente de déterminer les valeurs par défaut qui seront utilisées quand les objets de cette structure seront construits plus tard dans le programme.
Par exemple, l'objet Test suivant est construit sans valeur spécifique :
Test t; // aucune valeur n'est spécifiée pour les membres de t
writefln
(
"%s,%s,%s"
, t.c, t.i, t.d);
Tous les membres sont initialisés avec leur valeur par défaut :
A,11
,0
.25
51-3-4. Construction avec la syntaxe { } ▲
Les structures peuvent aussi être construites avec la syntaxe suivante :
MomentDeLaJournee debutCours =
{
8
, 30
}
;
Comme avec la syntaxe précédente, les valeurs indiquées sont affectées aux membres dans l'ordre dans lequel ceux-ci sont définis. Les membres restants seront initialisés à leurs valeurs par défaut.
Cette syntaxe est héritée du langage C :
auto
debutCours =
MomentDeLaJournee
(
8
, 30
); // ← recommandé
MomentDeLaJournee finCours =
{
9
, 30
}
; // ← façon C
Cette syntaxe autorise les initialisateurs désignés. Les initialisateurs désignés sont utilisés pour spécifier le membre associé à une valeur d'initialisation. Il est même possible d'initialiser les membres dans un ordre différent de celui dans lequel ils sont définis dans la structure :
MomentDeLaJournee m =
{
minute: 42
, heure: 7
}
;
51-4. Copie et affectation▲
Les structures sont des types valeur. Comme décrit dans le chapitre sur les types valeur et types référenceTypes valeur et types référence, ceci veut dire que chaque objet struct a sa propre valeur. Les objets reçoivent leurs propres valeurs quand ils sont construits et leurs valeurs changent quand on leur affecte de nouvelles valeurs.
auto
votreHeureDuDejeuner =
MomentDeLaJournee
(
12
, 0
);
auto
monHeureDuDejeuner =
votreHeureDuDejeuner;
// Seul monHeureDuDejeuner passe à 12h05 :
monHeureDuDejeuner.minute +=
5
;
// ... votreHeureDuDejeuner reste inchangé :
assert
(
yourLunchTime.minute ==
0
);
Pendant une copie, tous les membres de l'objet source sont automatiquement copiés vers les membres correspondants dans l'objet de destination. De manière similaire, l'affectation implique l'affectation de chaque membre de la source au membre de destination correspondant.
Les membres de structures qui sont d'un type référence demandent une attention particulière.
51-4-1. Attention avec les membres qui sont de types référence !▲
Comme vous le savez, le fait de copier ou d'affecter les variables de types référence ne modifie aucune valeur, cela ne fait que changer l'objet effectivement référencé. Ainsi, copier ou affecter une structure qui a des membres de type référence va créer dans l'objet de destination des références supplémentaires aux objets référencés dans l'objet source. Ceci implique que des membres de deux structures distinctes peuvent donner accès à la même valeur.
Pour voir un exemple de ceci, regardons une structure dont l'un des membres est de type référence. Cette structure est utilisée pour stocker le numéro et les notes d'un étudiant :
struct
Etudiant
{
int
numero;
int
[] notes;
}
Le code suivant construit un second objet Etudiant en copiant un objet existant :
// On construit le premier objet :
auto
etudiant1 =
Etudiant
(
1
, [ 70
, 90
, 85
]);
// On construit le deuxième objet en copiant le premier
// et en changeant le numéro :
auto
etudiant2 =
etudiant1;
etudiant2.numero =
2
;
// AVERTISSEMENT: Les notes sont maintenant partagées par les deux objets!
// Changer les notes du premier étudiant...
etudiant1.notes[0
] +=
5
;
// ... affecte les notes du second étudiant:
writeln
(
etudiant2.notes[0
]);
Quand etudiant2 est construit, ses membres prennent les valeurs des membres de etudiant1. Comme int est un type valeur, le second objet obtient sa propre valeur numero.
Les deux objets Etudiant ont aussi un membre notes chacun. Cependant, comme les tranches sont des types référence, les éléments que les tranches partagent sont les mêmes. Ainsi, une modification faite depuis une des tranches sera reflétée sur l'autre tranche.
La sortie du code indique que la note du second étudiant a été augmentée également :
75
Pour cette raison, une meilleure approche serait de construire le deuxième objet en copiant les notes du premier :
// Le deuxième Etudiant est construit en copiant les notes du premier :
auto etudiant2 =
Etudiant
(
2
, etudiant1.notes.dup);
// Changer les notes du premier étudiant...
etudiant1.notes[0
] +=
5;
// ... n`affecte pas les notes du deuxième :
writeln(etudiant2.notes[0]);
Comme les notes ont été copiées avec .dup, les notes du deuxième étudiant ne sont cette fois pas affectées :
0
Il existe néanmoins une possibilité de faire en sorte que les membres référence soient également copiés. Nous verrons comment plus tard, quand on abordera les membres fonction.
51-4-2. Littéraux structure▲
De la même manière que l'on peut utiliser des valeurs littérales entières (comme 10) dans les expressions sans avoir besoin de définir de variable, des littéraux de structures peuvent également être utilisés :
Les littéraux de structures sont construits avec la syntaxe de construction d'objets.
MomentDeLaJournee
(
8
, 30
) // ← littéral de structure
Réécrivons la dernière fonction main() que nous avons définie, avec ce qu'on a appris depuis sa dernière version. Les variables sont construites avec la syntaxe de construction et sont cette fois immuables :
void
main
(
)
{
immutable debutCours =
MomentDeLaJournee
(
8
, 30
);
immutable dureeCours =
MomentDeLaJournee
(
1
, 15
);
immutable finCours =
ajouterDuree
(
debutCours,
dureeCours);
writefln
(
"Fin du cours : %s:%s"
,
finCours.heure, finCours.minute);
}
Notez qu'il n'est pas nécessaire de définir debutCours et dureeCours comme des variables nommées dans ce cas. Elles sont en fait des variables temporaires dans ce programme simple, qui sont utilisées seulement pour calculer la variable finCours. Elles pourraient être passées à ajouterDuree() comme des valeurs littérales :
void
main
(
)
{
immutable finCours =
ajouterDuree
(
MomentDeLaJournee
(
8
, 30
),
MomentDeLaJournee
(
1
, 15
));
writefln
(
"Fin du cours : %s:%s"
,
finCours.heure, finCours.minute);
}
51-4-3. Membres static ▲
Même si les objets ont surtout besoin de copies individuelles de leurs membres, il peut être utile pour un type de structure particulier de partager certaines variables. Ce peut être nécessaire quand, par exemple, une information générale sur ce type de structure a besoin d'être maintenue.
Par exemple, imaginons un type qui affecte un identificateur différent pour chaque objet de ce type :
struct
Point
{
// L'identificateur de chaque objet
size_t id;
int
ligne;
int
colonne;
}
Afin de pouvoir affecter un identificateur différent à chaque objet, il faut une variable distincte pour garder la prochaine valeur que l'on peut utiliser, variable qui sera incrémentée à chaque fois qu'un nouvel objet sera créé. Supposons que idSuivant est définie autre part et accessible à la fonction suivante :
Point creerPoint
(
int
ligne, int
colonne)
{
size_t id =
idSuivant;
++
idSuivant;
return
Point
(
id, ligne, colonne);
}
Une décision doit être prise quant à l'endroit où l'on définit la variable commune idSuivant. Les membres statiques sont utiles dans de tels cas. Une information de cette sorte est définie comme un membre statique (static) de la structure. Contrairement aux membres classiques, il y a une seule variable pour chaque membre statique dans tout le fil d'exécution. Cette variable unique est partagée par tous les objets de cette structure :
import
std.stdio;
struct
Point
{
// L'identificateur de chaque objet
size_t id;
int
ligne;
int
colonne;
// L'identificateur de l'objet suivant
static
size_t idSuivant;
}
Point creerPoint
(
int
ligne, int
colonne)
{
size_t id =
Point.idSuivant;
++
Point.idSuivant;
return
Point
(
id, ligne, colonne);
}
void
main
(
)
{
auto
haut =
creerPoint
(
7
, 0
);
auto
milieu =
creerPoint
(
8
, 0
);
auto
bas =
creerPoint
(
9
, 0
);
writeln
(
haut.id);
writeln
(
milieu.id);
writeln
(
bas.id);
}
Comme idSuivant est incrémentée lors de la construction de chaque objet, les objets ont chacun un identificateur unique :
0
1
2
Comme les membres statiques appartiennent au type lui-même, il n'y a pas besoin d'objet pour y accéder. Comme nous l'avons vu ci-dessus, on peut accéder à de tels objets par le nom du type, mais on peut aussi bien le faire à travers le nom de tout objet de ce type :
++
Point.idSuivant;
++
bas.idSuivant; // même effet que la ligne précédente
51-4-4. static this() pour l'initialisation et static ~this() pour la finalisation▲
Au lieu d'affecter explicitement une valeur initiale à idSuivant ci-dessus, nous avons utilisé sa valeur initiale par défaut, zéro. Nous aurions pu utiliser n'importe quelle autre valeur :
static
size_t idSuivant =
1000
;
Cependant, une telle initialisation est possible seulement quand la valeur initiale est connue lors de la compilation. De plus, certains codes spéciaux peuvent nécessiter d'être exécutés avant qu'une structure soit utilisée dans un fil d'exécution. De tels codes peuvent être écrits dans des portées static this().
Par exemple, le code suivant lit la valeur initiale dans un fichier s'il existe :
import
std.file;
struct
Point {
// ...
enum
fichierIdSuivant =
"Point_fichier_id_suivant"
static
this
(
) {
if
(
exists
(
fichierIdSuivant)) {
auto
fichier =
File
(
fichierIdSuivant, "r"
);
fichier.readf
(
" %s"
, &
idSuivant);
}
}
}
Le contenu des blocs static this() est exécuté une fois par fil d'exécution avant que le type de struct soit utilisé dans ce fil d'exécution. Le code qui devrait être exécuté seulement une fois durant toute l'exécution du programme (par exemple, pour initialiser des variables shared ou immutable) doit être défini dans des blocs shared static this() et shared static this(), qui seront vus dans le chapitre sur les données partagées et la concurrenceConcurrence par messages.
De manière similaire, static this() est utilisé pour les opérations finales d'un fil d'exécution et shared static this() est utilisé pour les opérations finales du programme entier.
L'exemple suivant complète le bloc static this() en écrivant la valeur de idSuivant dans le même fichier, ce qui permet de rendre les identifiants des objets persistants entre les exécutions du programme :
struct
Point {
// ...
static
~
this
(
) {
auto
fichier =
File
(
fichierIdSuivant, "w"
);
file.writeln
(
idSuivant);
}
}
Le programme initialise maintenant idSuivant à partir de l'identifiant de l'exécution précédente. Par exemple, ce qui suit est la sortie de la deuxième exécution du programme :
3
4
5
51-5. Exercices▲
-
Concevez une structure Carte pour représenter une carte à jouer.
Sélectionnezauto
carte=
Carte
(
'♣'
,'2'
);Sélectionnezstruct
Carte{
// ... définir la structure ...
}
void
afficherCarte
(
in
Carte carte){
// ... écrire le corps de la fonction ...
}
void
main
(
){
auto
carte=
Carte
(
/* ... */
);afficherCarte
(
carte);}
-
Par exemple, la fonction peut afficher le deux de trèfle de cette manière :
Sélectionnez♣
2
-
L'implémentation de cette fonction peut dépendre des choix de types des membres.
SélectionnezCarte[]
nouveauJeu
(
)out
(
resultat){
assert
(
resultat.length==
52
);}
body
{
// ... écrire le corps de la fonction ...
}
-
Il doit être possible d'appeler nouveauJeu() comme dans le code suivant :
Sélectionnezvoid
main
(
){
Carte[] jeu=
nouveauJeu
(
);foreach
(
carte; jeu){
afficherCarte
(
carte);write
(
' '
);}
writeln
(
);}
- La sortie devrait ressembler à ce qui suit, avec 52 cartes distinctes :
♠2
♠3
♠4
♠5
♠6
♠7
♠8
♠9
♠0
♠V ♠D ♠R ♠A ♡2
♡3
♡4
♡5
♡6
♡7
♡8
♡9
♡0
♡V ♡D ♡R ♡A ♢2
♢3
♢4
♢5
♢6
♢7
♢8
♢9
♢0
♢V ♢D ♢R ♢A ♣2
♣3
♣4
♣5
♣6
♣7
♣8
♣9
♣0
♣V ♣D ♣R ♣A
-
Écrivez une fonction qui mélange le jeu. Une manière de le faire est de prendre deux cartes au hasard avec std.random.uniform, les échanger et répéter ce processus un nombre de fois suffisant. La fonction devrait prendre le nombre de répétitions en paramètre :
Sélectionnezvoid
melanger
(
Carte[] jeu,in
int
repetitions){
// ... écrire le corps de la fonction ...
}
-
Voici comment elle doit pouvoir être utilisée :
Sélectionnezvoid
main
(
){
Carte[] jeu=
nouveauJeu
(
);melanger
(
jeu,1
);foreach
(
carte; jeu){
afficherCarte
(
carte);write
(
' '
);}
writeln
(
);}
-
La fonction doit échanger les cartes repetitions fois. Par exemple, un appel avec 1 doit donner une sortie similaire à ceci :
Sélectionnez♠
2
♠3
♠4
♠5
♠6
♠7
♠8
♠9
♠0
♠V ♠D ♠R ♠A ♡2
♡3
♡4
♡5
♡6
♡7
♡8
♣4
♡0
♡V ♡D ♡R ♡A ♢2
♢3
♢4
♢5
♢6
♢7
♢8
♢9
♢0
♢V ♢D ♢R ♢A ♣2
♣3
♡9
♣5
♣6
♣7
♣8
♣9
♣0
♣V ♣D ♣R ♣A -
Une valeur plus grande de repetitions devrait résulter en un jeu plus mélangé :
Sélectionnezmelanger
(
jeu,100
); -
La sortie :
Sélectionnez♠
4
♣7
♢9
♢6
♡2
♠6
♣6
♢A ♣5
♢8
♢3
♡D ♢V ♣R ♣8
♣4
♡V ♣D ♠D ♠9
♢0
♡A ♠A ♡9
♠7
♡3
♢R ♢2
♡0
♠V ♢7
♡7
♠8
♡4
♣V ♢4
♣0
♡6
♢5
♡5
♡R ♠3
♢D ♠2
♠5
♣2
♡8
♣A ♠R ♣9
♠0
♣3
Note : une meilleure façon de mélanger le jeu est expliquée dans les solutions.