IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Cours complet pour apprendre à programmer en D


précédentsommairesuivant

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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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) :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
A,11,0.25

51-3-4. Construction avec la syntaxe { }

Les structures peuvent aussi être construites avec la syntaxe suivante :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
struct Etudiant
{
    int numero;
    int[] notes;
}

Le code suivant construit un second objet Etudiant en copiant un objet existant :

 
Sélectionnez
// 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 :

 
Sélectionnez
75

Pour cette raison, une meilleure approche serait de construire le deuxième objet en copiant les notes du premier :

 
Sélectionnez
// 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 :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
++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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
3
4
5

51-5. Exercices

  • Concevez une structure Carte pour représenter une carte à jouer.

     
    Sélectionnez
    auto carte = Carte('♣', '2');
     
    Sélectionnez
    struct 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électionnez
    Carte[] 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électionnez
    void main()
    {
        Carte[] jeu = nouveauJeu();
     
        foreach (carte; jeu) {
            afficherCarte(carte);
            write(' ');
        }
     
        writeln();
    }
  • La sortie devrait ressembler à ce qui suit, avec 52 cartes distinctes :
 
Sélectionnez
234567890 ♠V ♠D ♠R ♠A ♡234567890 ♡V ♡D ♡R ♡A ♢234567890 ♢V ♢D ♢R ♢A ♣234567890
♣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électionnez
    void melanger(Carte[] jeu, in int repetitions)
    {
        // ... écrire le corps de la fonction ...
    }
  • Voici comment elle doit pouvoir être utilisée :

     
    Sélectionnez
    void 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
    234567890 ♠V ♠D ♠R ♠A ♡234567840 ♡V ♡D ♡R ♡A ♢234567890 ♢V ♢D ♢R ♢A ♣239567890
    ♣V ♣D ♣R ♣A
  • Une valeur plus grande de repetitions devrait résulter en un jeu plus mélangé :

     
    Sélectionnez
    melanger(jeu, 100);
  • La sortie :

     
    Sélectionnez
    4796266 ♢A ♣583 ♡D ♢V ♣R ♣84
    ♡V ♣D ♠D ♠90 ♡A ♠A ♡973 ♢R ♢20 ♠V ♢7784 ♣V ♢40655 ♡R ♠3 ♢D ♠2528 ♣A
    ♠R ♣903

  • Note : une meilleure façon de mélanger le jeu est expliquée dans les solutions.

Les solutionsStructures - Correction.


précédentsommairesuivant