Cours complet pour apprendre à programmer en D


précédentsommairesuivant

60. Exercices

60-1. Le programme Hello world - Correction

 
Sélectionnez
import std.stdio;
 
void main()
{
    writeln("Autre chose... :p");
}
 
Sélectionnez
import std.stdio;
 
void main()
{
    writeln("Une ligne...");
    writeln("Une autre ligne...");
}
  1. Le programme suivant ne peut pas être compilé parce qu'il manque le point-virgule à la fin de la ligne du writeln :
 
Sélectionnez
import std.stdio;
 
void main()
{
    writeln("Hello world!")
}

60-2. writeln et write - Correction

  1. Une méthode est d'utiliser un autre paramètre entre les deux :

     
    Sélectionnez
    writeln("Hello world!", " ", "Hello fish!");
  2. write peut elle aussi prendre plusieurs paramètres :
 
Sélectionnez
write("one", " two", " three");

60-3. Types Fondamentaux - Correction

On peut utiliser d'autres types à la place de int :

 
Sélectionnez
import std.stdio;
 
void main()
{
  writeln("Type             : ", short.stringof);
  writeln("Taille en octets : ", short.sizeof);
  writeln("Valeur minimale  : ", short.min);
  writeln("Valeur maximale  : ", short.max);
 
  writeln();
 
  writeln("Type             : ", ulong.stringof);
  writeln("Taille en octets : ", ulong.sizeof);
  writeln("Valeur minimale  : ", ulong.min);
  writeln("Valeur maximale  : ", ulong.max);
}

60-4. Affectation et ordre d'évaluation - Correction

Les valeurs de a, b et c sont affichées à droite de chaque opération. La valeur qui change est à chaque opération surlignée

 

initialement

→ a 1, b 2, c sans importance

c = a

→ a 1, b 2,

a = b

→ , b 2, c 1

b = c

→ a 2, , c 1

 

À la fin, les valeurs de a et de b ont été échangées.

60-5. Variable - Correction

 
Sélectionnez
import std.stdio;
 
void main()
{
    int montant = 20;
    double taux = 2.11;
 
    writeln("J'ai échangé ", montant,
            " euros au taux ", taux);
}

60-6. Entrée standard et flux de sortie - Correction

 
Sélectionnez
import std.stdio;
 
void main()
{
    writeln(1, ",", 2);
    stdout.writeln(3, ",", 4);
}

60-7. Expressions logiques - Correction

  1. Parce que le compilateur reconnaît 10 < valeur comme une expression, il s'attend à avoir une virgule après elle pour l'accepter comme un argument valable de writeln. Ajouter des parenthèses autour de l'expression ne marche pas non plus parce que, cette fois, une parenthèse fermante serait attendue après la même expression.
  2. Grouper l'expression de cette manière : (10 < value) < 20 supprime l'erreur de compilation, parce que dans ce cas, 10 < value est évaluée et son résultat est utilisé avec < 20.
    Nous savons que la valeur d'une expression logique comme 10 < value est soit fausse, soit vraie. Dans des expressions entières, false et true sont convertis en 0 et 1 respectivement (nous verrons les conversions de types automatiques dans un chapitre suivant). De ce fait, l'expression complète est l'équivalent de 0 < 20 ou 1 < 20 , qui sont vraies toutes les deux.
  3. L'expression « plus grand que la valeur basse et plus petit que la valeur haute » peut être écrite de cette façon :

     
    Sélectionnez
    writeln("Est entre : ", (valeur > 10) && (valeur < 20));
  4. Il y a un vélo pour tout le monde peut être codée comme nombrePersonnes <= nombreVelos ou nombreVelos >= nombrePersonnes. Le reste de l'expression logique peut directement être traduit en code depuis l'énoncé :

     
    Sélectionnez
    writeln("Nous allons à la plage : ",
           ((distance < 10) && (nombreVelos >= nombrePersonnes))
           ||
           ((nombrePersonnes <= 5) && existeUneVoiture && existeUnPermis)
        );
  5. Notez le placement de l'opérateur || pour faciliter la lecture en séparant les deux conditions principales.

60-8. Lire depuis l'entrée - Correction

Quand les caractères ne peuvent pas être convertis au type voulu, stdin devient inutilisable. Par exemple, en entrant « abc » quand un entier est attendu rendrait stdin inutilisable.

60-9. L'instruction if - Correction

  1. L'instruction writeln("Laver l'assiette") est indentée comme si elle était dans le bloc else. Cependant, du fait que le bloc de ce else n'est pas écrit avec des accolades, seule l'instruction writeln("Manger la tarte") est considérée comme étant dans la portée de ce else.
    Comme les caractères blancs ne sont pas pris en compte dans le langage D, l'instruction « assiette » est en fait une instruction indépendante située au niveau de main et exécutée de façon inconditionnelle. Cela embrouille le lecteur à cause de l'indentation plus grande que normale. Si l'instruction « assiette » doit vraiment être dans le bloc else, il doit y avoir des accolades autour de ce bloc :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
      bool ilyaDeLaLimonade = true;
     
      if (ilyaDeLaLimonade) {
          writeln("Boire de la limonade");
          writeln("Laver le verre");
     
      } else {
          writeln("Manger la tarte");
          writeln("Laver l'assiette");
      }
    }
  2. On peut trouver plusieurs solutions pour les conditions de ce jeu. Je vais montrer deux exemples. Dans la première, on fait une traduction directe depuis l'énoncé :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        write("Quelle est la valeur du  ? ");
        int dé;
        readf(" %s", &dé);
     
        if (== 1) {
            writeln("Vous avez gagné");
     
        } else if (== 2) {
            writeln("Vous avez gagné");
     
        } else if (== 3) {
            writeln("Vous avez gagné");
     
        } else if (== 4) {
            writeln("J'ai gagné");
     
        } else if (== 5) {
            writeln("J'ai gagné");
     
        } else if (== 6) {
            writeln("J'ai gagné");
     
        } else {
            writeln("ERREUR : ", dé, " est invalide.");
        }
    }
  3. Malheureusement, ce programme comporte beaucoup de répétitions. On peut arriver au même résultat d'une autre manière. En voici une :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        write("Quelle est la valeur du  ? ");
        int dé;
        readf(" %s", &dé);
     
        if ((== 1) || (== 2) || (== 3)) {
            writeln("Vous avez gagné");
     
        } else if ((== 4) || (== 5) || (== 6)) {
            writeln("J'ai gagné");
     
        } else {
            writeln("ERREUR : ", dé, " est invalide");
        }
    }
  4. Les précédentes manières de faire ne peuvent pas être utilisées dans ce cas. Il n'est pas pratique de taper 1000 valeurs différentes dans un programme et s'attendre à ce qu'elles soient toutes correctes ou lisibles. Pour cette raison, il est mieux de déterminer si la valeur du dé est à l'intérieur d'un intervalle ou non :
 
Sélectionnez
if ((>= 1) && (<= 500))

60-10. La boucle while - Correction

  1. À cause de la valeur initiale de nombre qui est 0, l'expression logique de la boucle while est fausse depuis le tout début, et cela empêche de rentrer dans le corps de la boucle. Une solution est d'utiliser une valeur initiale qui permettra à la condition du while d'être vraie au tout début :

     
    Sélectionnez
    int number = 3;
  2. Toutes les variables dans le programme suivant sont initialisées par défaut à 0. Cela permet d'entrer dans les deux boucles au moins une fois :
 
Sélectionnez
import std.stdio;
 
void main()
{
    int nombreSecret;
 
    while ((nombreSecret < 1) || (nombreSecret > 10)) {
        write("Veuillez entrer un nombre entre 1 et 10 : ");
        readf(" %s", &nombreSecret);
    }
 
    int deviner;
 
    while (deviner != nombreSecret) {
        write("Deviner le nombre secret : ");
        readf(" %s", &deviner);
    }
 
    writeln("C'est le bon nombre !");
}

60-11. Nombres entiers et opérations arithmétiques - Correction

  1. On peut utiliser l'opérateur / pour la division et l'opérateur % pour le reste :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        int premier;
        write("Veuillez entrer le premier nombre : ");
        readf(" %s", &premier);
     
        int second;
        write("Veuillez entrer le second nombre : ");
        readf(" %s", &second);
     
        int quotient = premier / second;
        int reste = premier % second;
     
        writeln(premier, " = ",
                second, " * ", quotient, " + ", reste);
    }
  2. On peut déterminer si le reste est 0 ou non avec une instruction if :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        int premier;
        write("Veuillez entrer le premier nombre : ");
        readf(" %s", &premier);
     
        int second;
        write("Veuillez entrer le second nombre : ");
        readf(" %s", &second);
     
        int quotient = premier / second;
        int reste = premier % second;
     
        // Nous ne pouvons pas appeler writeln tout de suite avant de déterminer si
        // le reste est 0 ou non. On doit terminer la ligne plus loin
        // avec writeln.
        write(premier, " = ", second, " * ", quotient);
     
        // Le reste doit être affiché seulement si non nul.
        if (reste != 0) {
            write(" + ", reste);
        }
     
        // On peut maintenant terminer la ligne.
        writeln();
    }
  3. Code :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        while (true) {
            write("0: Quitter, 1: Ajouter, 2: Soustraire, 3: Multiplier,",
                  " 4: Diviser - Veuillez entrer l'opération : ");
     
            int operation;
            readf(" %s", &operation);
     
            // Vérifions qu'une opération valide a été entrée
            if ((operation < 0) || (operation > 4)) {
                writeln("Je ne connais pas cette opération");
                continue;
            }
     
            if (operation == 0){
                writeln("Au revoir !");
                break;
            }
     
            // Si nous sommes ici, nous savons que nous avons affaire à l'une
            // des 4 opérations. Maintenant, il est temps de demander
            // deux entiers à l'utilisateur:
     
            int premier;
            int second;
     
            write(" Premier nombre : ");
            readf(" %s", &premier);
     
            write("Second nombre : ");
            readf(" %s", &second);
     
            int resultat;
     
            if (operation == 1) {
                resultat = premier + second;
     
            } else if (operation == 2) {
                resultat = premier - second;
     
            } else if (operation == 3) {
                resultat = premier * second;
     
            } else if (operation == 4) {
                resultat = premier / second;
     
            }  else {
                writeln(
                    "erreur ! ",
                    "Cette condition ne devrait jamais avoir eu lieu.");
                break;
            }
     
            writeln("       Résultat : ", resultat);
        }
    }
  4. Code :
 
Sélectionnez
import std.stdio;
 
void main()
{
    int valeur = 1;
 
    while (valeur <= 10) {
        if (valeur != 7) {
            writeln(valeur);
        }
 
        ++valeur;
    }
}

60-12. Types à virgule flottante - Correction

  1. Il suffit de remplacer les trois int par trois double.

     
    Sélectionnez
    double first;
    double second;
     
    // ...
     
       double result;
  2. Le programme suivant laisse entrevoir comment il deviendrait compliqué si plus de cinq variables étaient nécessaires :
 
Sélectionnez
import std.stdio;
 
void main()
{
    double valeur_1;
    double valeur_2;
    double valeur_3;
    double valeur_4;
    double valeur_5;
 
    write("Value 1: ");
    readf(" %s", &valeur_1);
    write("Value 2: ");
    readf(" %s", &valeur_2);
    write("Value 3: ");
    readf(" %s", &valeur_3);
    write("Value 4: ");
    readf(" %s", &valeur_4);
    write("Value 5: ");
    readf(" %s", &valeur_5);
 
    writeln("Le double des valeurs :");
    writeln(valeur_1 * 2);
    writeln(valeur_2 * 2);
    writeln(valeur_3 * 2);
    writeln(valeur_4 * 2);
    writeln(valeur_5 * 2);
 
    writeln("Le cinquième des valeurs :");
    writeln(valeur_1 / 5);
    writeln(valeur_2 / 5);
    writeln(valeur_3 / 5);
    writeln(valeur_4 / 5);
    writeln(valeur_5 / 5);
}

60-13. Tableaux - Correction

  1. Code :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
      write("Combien de valeurs seront entrées ? ");
      int nombre;
      readf(" %s", &nombre);
     
      double[] valeurs;
      valeurs.length = nombre;
     
      // Le compteur est souvent appelé 'i'
      int i;
      while (i < nombre) {
          write("Valeur ", i, " : ");
          readf(" %s", &valeurs[i]);
          ++i;
      }
     
      writeln("Dans l'ordre:");
      valeurs.sort;
     
      i = 0;
      while (i < nombre) {
          write(valeurs[i], " ");
          ++i;
      }
      writeln();
     
      writeln("Dans l'ordre inverse:");
      valeurs.reverse;
     
      i = 0;
      while (i < nombre) {
          write(valeurs[i], " ");
          ++i;
      }
      writeln();
    }
  2. Les explications sont fournies dans les commentaires :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
      // On utilise des tableaux dynamiques parce qu'on ne connaît pas
      // le nombre de valeurs qui vont être lues depuis l'entrée
      int[] pairs;
      int[] impairs;
     
      writeln("Merci d'entrer des entiers (-1 pour finir) :");
     
      while (true) {
     
          // Lecture de la valeur
          int valeur;
          readf(" %s", &valeur);
     
          // La valeur spéciale de -1 casse la boucle
          if (valeur == -1) {
              break;
          }
     
          // Ajout au tableau correspondant, selon
          // si la valeur est paire ou impaire. C'est un nombre
          // pair s'il n'y a pas de reste quand on le divise par 2.
          if ((valeur % 2) == 0) {
              pairs ~= valeur;
     
          } else {
              impairs ~= valeur;
          }
      }
     
      // Les tableaux impairs et pairs sont triés séparément
      impairs.sort;
      pairs.sort;
     
      // Les deux tableaux sont ensuite concaténés dans un nouveau tableau
      int[] resultat;
      resultat = impairs ~ pairs;
     
      writeln("D'abord les impairs, ensuite les pairs, triés :");
     
      // Afficher les tableaux dans une boucle
      int i;
      while (i < resultat.length) {
          write(resultat[i], " ");
          ++i;
      }
     
      writeln();
    }
  3. Il y a trois erreurs (bogues) dans ce programme. Les deux premières concernent à la boucle while : les deux conditions de boucle utilisent l'opérateur <= au lieu de l'opérateur <. Du coup, le programme utilise des indices invalides et essaie d'accéder à des éléments qui ne sont pas dans le tableau.
    Comme il est plus bénéfique pour vous de déboguer la troisième erreur vous-même, il serait souhaitable que vous lanciez d'abord le programme après avoir corrigé les deux premiers bogues avant de lire le paragraphe suivant. Vous remarquerez que le programme n'affichera pas les résultats. Pourquoi ? …
    … La valeur de i est 5 quand la première boucle while se termine et cette valeur entraîne que l'expression logique de la deuxième boucle est fausse, ce qui en fait empêche l'exécution de la deuxième boucle. La solution est de remettre i à 0 avant la seconde boucle while, par exemple avec l'instruction i = 0.

60-14. Tranches et autres fonctionnalités des tableaux - Correction

Itérer sur les éléments d'une tranche depuis le début de la tranche est une idée intéressante. Cette méthode est aussi la base des intervalles de Phobos que nous verrons dans un chapitre ultérieur.

 
Sélectionnez
import std.stdio;
 
void main()
{
    double[] tableau = [ 1, 20, 2, 30, 7, 11 ];
 
    double[] tranche = tableau;     // On démarre avec une tranche
                                    // qui donne accès à l'ensemble
                                    // des éléments du tableau
 
    while (tranche.length) {        // Tant qu'il y a au moins
                                    // un élément dans cette tranche
 
        if (tranche[0] > 10) {      // On travaille toujours sur le premier
            tranche[0] /= 2;        // élément de la tranche dans ces expressions
        }
 
        tranche = tranche[1 .. $];  // On raccourcit la tranche par
                                    // son début
    }
 
    writeln(tableau);               // Les éléments du tableau
                                    // ont été mis à jour
}

60-15. Chaînes de caractères - Correction

  1. Même si certaines fonctions des modules de Phobos sont faciles à utiliser avec les chaînes, les documentations des bibliothèques sont souvent laconiques comparées aux tutoriels. Vous pouvez en particulier trouver les intervalles (ranges) de Phobos confus, à ce moment du cours. Nous verrons les intervalles de Phobos dans un chapitre ultérieur.
  2. Beaucoup d'autres fonctions peuvent aussi être chaînées :

     
    Sélectionnez
    import std.stdio;
    import std.string;
     
    void main()
    {
        write("Prénom : ");
        string prenom = capitalize(chomp(readln()));
     
        write("Nom : ");
        string nom = capitalize(chomp(readln()));
     
        string nomComplet = prenom ~ " " ~ nom;
        writeln(nomComplet);
    }
  3. Ce programme utilise deux indices pour créer une tranche :
 
Sélectionnez
import std.stdio;
import std.string;
 
void main()
{
    write("Veuillez entrer une ligne : ");
    string line = chomp(readln());
 
    sizediff_t premier_e = indexOf(line, 'e');
 
    if (premier_e == -1) {
        writeln("Il n'y a pas de lettre e dans cette ligne.");
 
    } else {
        sizediff_t dernier_e = lastIndexOf(line, 'e');
        writeln(line[premier_e .. dernier_e + 1]);
    }
}

60-16. Rediriger l'entrée standard et les flux de sortie - Correction

Rediriger l'entrée standard et la sortie des programmes est souvent utilisé, spécialement dans les consoles des systèmes de type Unix. Certains programmes sont conçus pour fonctionner correctement quand ils sont combinés avec d'autres programmes.

Par exemple, un fichier nommé essai.d peut être cherché dans une arborescence en combinant find avec grep :

 
Sélectionnez
find | grep essai.d

find affiche le nom de tous les fichiers sur sa sortie. grep reçoit cette sortie dans son entrée et affiche les lignes qui contiennent essai.d sur sa propre sortie.

60-17. Fichiers - Correction

 
Sélectionnez
import std.stdio;
import std.string;
 
void main()
{
    write("Veuillez saisir le nom du fichier à lire : ");
    string nomFichierEntree = chomp(readln());
    File fichierEntree = File(nomFichierEntree, "r");
 
    string nomFichierSortie = nomFichierEntree ~ ".out";
    File fichierSortie = File(nomFichierSortie, "w");
 
    while (!fichierEntree.eof()) {
        string ligne = chomp(fichierEntree.readln());
 
        if (ligne.length != 0) {
            fichierSortie.writeln(ligne);
        }
    }
 
    writeln(nomFichierSortie, " a été créé.");
}

60-18. auto et typeof - Correction

On peut utiliser typeof() pour déterminer le type du littéral et .stringof pour avoir le nom de ce type en tant que string :

 
Sélectionnez
import std.stdio;
 
void main()
{
    writeln(typeof(1.2).stringof);
}

La sortie :

 
Sélectionnez
double

60-19. La boucle for - Correction

  1. Code :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        for (int ligne = 0; ligne != 9; ++ligne) {
            for (int colonne = 0; colonne != 9; ++colonne) {
                write(ligne, ',', colonne, ' ');
            }
     
            writeln();
        }
    }
  2. Triangle :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        for (int ligne = 0; ligne != 9; ++ligne) {
            int longueur = ligne + 1;
     
            for (int i = 0; i != longueur; ++i) {
                write('*');
            }
     
            writeln();
        }
    }
  3. Parallélogramme :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        for (int ligne = 0; ligne != 9; ++ligne) {
            for (int i = 0; i != ligne; ++i) {
                write(' ');
            }
     
            writeln("********");
        }
    }
  4. Pouvez-vous produire la forme du diamant ?

Image non disponible

60-20. L'opérateur ternaire « ? »: - Correction

Même s'il pourrait être plus sensé d'utiliser une instruction if-else dans cet exercice, le programme suivant utilise deux opérateurs ?: :

 
Sélectionnez
import std.stdio;
 
void main()
{
    write("Veuillez entrer la valeur nette : ");
    int valeur;
    readf(" %s", &valeur);
 
    writeln(valeur < 0 ? -valeur : valeur,
            " ",
            valeur < 0 ? "perdus" : "gagnés");
}

60-21. Littéraux - Correction

  1. Le problème ici est que la valeur à droite de l'affectation est trop grande pour être contenue dans un int. Selon les règles sur les littéraux entiers, son type est long. Pour cette raison, il ne peut pas être contenu dans la variable à gauche du signe égal. Il y a au moins deux solutions.
    Une première solution est de laisser le compilateur déduire le type de la variable avec le mot-clé auto :

     
    Sélectionnez
    auto quantite = 10_000_000_000;
  2. Le type de quantite est déduit comme long.
    Une autre solution est de spécifier explicitement le type de la variable en tant que long :

     
    Sélectionnez
    long quantite = 10_000_000_000;
  3. Nous pouvons utiliser le caractère spécial '\r' qui ramène le curseur d'affichage au début de la ligne.

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        for (int compteur = 0; ; ++compteur) {
            write("\rNombre: ", compteur);
        }
    }
  4. La sortie de ce programme peut être erratique à cause de ses interactions avec le tampon de sortie. Le programme suivant force l'affichage du contenu du tampon de sortie avec la fonction flush et attend une milliseconde après chaque write :

     
    Sélectionnez
    import std.stdio;
    import core.thread;
     
    void main()
    {
        for (int compteur = 0; ; ++compteur) {
            write("\rNombre : ", compteur);
            stdout.flush();
            Thread.sleep(Thread.sleep(10.msecs));
        }
    }

  5. Forcer l'affichage du tampon de sortie n'est normalement pas nécessaire, car c'est fait automatiquement avant d'aller sur une nouvelle ligne (par exemple par writeln, ou avant de lire depuis stdin).

60-22. Sortie formatée - Correction

  1. Nous avons déjà vu que c'est trivial avec les indicateurs de format :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
      writeln("(Entrez 0 pour quitter le programme.)");
     
      while (true) {
          write("Veuillez entrer un nombre : ");
          long nombre;
          readf(" %s", &nombre);
     
          if (nombre == 0) {
              break;
          }
     
          writefln("%1$d <=> %1$#x", nombre);
      }
    }
  2. En se souvenant que le caractère % doit apparaître deux fois dans la chaîne de formatage pour être affiché une fois :
 
Sélectionnez
import std.stdio;
 
void main()
{
  write("Veuillez entrer la valeur en pourcentage : ");
  double pourcentage;
  readf(" %s", &pourcentage);
 
  writefln("%.2f%%", pourcentage);
}

60-23. Entrée formatée - Correction

Il suffit d'utiliser une chaîne de formatage où les parties de date sont remplacées par %s :

 
Sélectionnez
import std.stdio;
 
void main()
{
    int annee;
    int mois;
    int jour;
 
    readf("%s.%s.%s", &annee , &mois , &jour);
 
    writeln("Mois : ", mois);
}

60-24. La boucle do-while - Correction

Ce programme n'est pas directement lié à la boucle do-while puisque tout problème qui peut être résolu avec une boucle do-while peut également être résolu par les autres types de boucle.

Le programme peut deviner le nombre auquel l'utilisateur pense en réduisant l'intervalle candidat des deux côtés selon les réponses de l'utilisateur. Par exemple, si sa première tentative est 50 et que l'utilisateur répond que le nombre secret est plus grand, le programme sait alors que le nombre doit être dans l'intervalle [51, 100]. Si le programme tente alors un autre nombre dans le milieu de cet intervalle, cette fois on saura que le nombre est soit dans l'intervalle [51, 75], soit dans l'intervalle [76, 100].

Quand la taille de l'intervalle est 1, le programme est sûr que le nombre qui est dedans est le nombre que l'utilisateur a choisi.

60-25. La boucle foreach - Correction

Pour obtenir un tableau associatif qui marche dans le sens opposé de noms, les types des clés et des valeurs doivent être échangés. Le nouveau tableau associatif doit être de type int[string].

En itérant sur les clés et les valeurs du tableau associatif original et en utilisant les clés comme les valeurs et les valeurs comme les clés, on peuple le tableau valeurs :

 
Sélectionnez
import std.stdio;
 
void main()
{
        string[int] noms = [ 1:"un", 7:"sept", 20:"vingt" ];
 
        int[string] valeurs;
 
        foreach (cle, valeur; noms) {
           valeurs[valeur] = cle;
        }
 
        writeln(valeurs["vingt"]);
}

60-26. Tableaux associatifs - Correction

  1. La propriété .keys retourne une tranche (c.-à-d. un tableau dynamique) qui inclut toutes les clés du tableau associatif. Itérer sur cette tranche et supprimer les éléments de chaque clé en appelant .remove résultera en un tableau associatif vide :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        string[int] noms =
        [
            1   : "un",
            10  : "dix",
            100 : "cent",
        ];
     
        writeln("Taille initiale : ", noms.length);
     
        int[] clés = noms.keys;
     
        for (int i = 0; i != clés.length; ++i) {
            writefln("suppression de l'élément %s", clés[i]);
            noms.remove(clés[i]);
        }
     
        writeln("Taille finale : ", noms.length);
    }

  2. Cette solution peut être lente, surtout pour des tableaux larges. Les méthodes suivantes vident le tableau en une seule étape.
  • Une autre solution est d'assigner un tableau vide :

     
    Sélectionnez
    string[int] AAvide;
    noms = AAvide;
  • Comme la valeur initiale d'un tableau est de toute manière un tableau vide, la méthode suivante aura le même effet :

     
    Sélectionnez
    noms = noms.init;
  • Le but est de stocker de multiples notes par étudiant. Comme plusieurs notes peuvent être stockées dans un tableau dynamique, un tableau associatif qui associe des chaînes à des int[] fonctionne. Les notes peuvent être ajoutées aux tableaux dynamiques qui sont stockés dans le tableau associatif :

     
    Sélectionnez
    import std.stdio;
     
    void main()
    {
        /* Le type de clé de ce tableau associatif est string et
         * le type de valeur est int[], c.-à-d. un tableau d'int.
         * le tableau associatif est défini avec un espace en plus
         * pour aider à distinguer le type de valeur : */
        int[] [string] notes;
     
        /* Le tableau d'int qui correspond à "pierre" est
         * utilisé pour ajouter la nouvelle note à ce tableau : */
        notes["pierre"] ~= 18;
        notes["pierre"] ~= 17;
     
        /* afficher les notes de "pierre": */
        writeln(notes["pierre"]);
    }
  • Les notes peuvent aussi être assignées d'un coup via un tableau littéral :
 
Sélectionnez
import std.stdio;
 
void main()
{
    int[][string] notes;
 
    notes["pierre"] = [ 18, 17, 19 ];
 
    writeln(notes["pierre"]);
}

60-27. switch et case - Correction

 
Sélectionnez
import std.stdio;
import std.string;
 
void main()
{
    string op;
    double premier;
    double second;
 
    write("Veuillez entrer l'opération :");
    op = chomp(readln());
 
    write("Veuillez entrer deux valeurs séparées par un espace :");
    readf(" %s %s", &premier, &second);
 
    double resultat;
 
    final switch (op) {
    case "ajouter":
        resultat = premier + second;
        break;
 
    case "soustraire":
        resultat = premier - second;
        break;
 
    case "multiplier":
        resultat = premier * second;
        break;
 
    case "diviser":
        resultat = premier / second;
        break;
    }
 
    writeln(resultat);
}
  1. En utilisant les valeurs distinctes :

     
    Sélectionnez
    final switch (op) {
    case "ajouter", "+":
        resultat = premier + second;
        break;
     
    case "soustraire", "-":
        resultat = premier - second;
        break;
     
    case "multiplier", "*":
        resultat = premier * second;
        break;
     
    case "diviser", "/":
        resultat = premier / second;
        break;
    }
  2. Comme la section default est nécessaire pour lever l'exception, on ne peut plus utiliser le final switch. Voici les parties modifiées du programme :
 
Sélectionnez
// ...
 
    switch (op) {
 
    // ...
 
    default:
        throw new Exception("Opération invalide : " ~ op);
    }
 
// ...

60-28. Les énumérations (enum) - Correction

 
Sélectionnez
import std.stdio;
import std.conv;
 
enum Operation { sortir, ajouter, soustraire, multiplier, diviser }
 
void main()
{
    // Afficher les opérations prises en charge
    write("Opérations - ");
    for (Operation operation;
        operation <= Operation.max;
        ++operation) {
 
        writef("%d:%s ", operation, operation);
    }
    writeln();
 
    // Boucle infinie jusqu'à ce que l'utilisateur veuille sortir
    while (true) {
        write("Opération ? ");
 
        // L'entrée doit être lue dans le type réel (int) de l'énumération
        int codeOperation;
        readf(" %s", &codeOperation);
 
        /*
        À partir d'ici, on utilise les valeurs de l'énumération
        plutôt que les constantes magiques. Le code de l'opération
        qui a été lu doit donc être converti vers sa valeur énumérée
        correspondante.
 
        (Les conversions de types seront traitées plus en détail dans un
        chapitre ultérieur.)
        */
        Operation operation = cast(Operation)codeOperation;
 
        if ((operation < Operation.min) ||
            (operation > Operation.max)) {
            writeln("ERREUR : Opération invalide");
            continue;
        }
 
        if (operation == Operation.sortir) {
            writeln("Au revoir !");
            break;
        }
 
        double premier;
        double second;
        double resultat;
 
        write("Premier opérande ? ");
        readf(" %s", &premier);
 
        write("Second opérande  ? ");
        readf(" %s", &second);
 
        switch (operation) {
 
        case Operation.ajouter:
            resultat = premier + second;
            break;
 
        case Operation.soustraire:
            resultat = premier - second;
            break;
 
        case Operation.multiplier:
            resultat = premier * second;
            break;
 
        case Operation.diviser:
            resultat = premier / second;
            break;
 
        default:
            throw new Exception(
                "ERREUR: Cette ligne n'aurait jamais  être atteinte.");
        }
 
        writeln("        resultat : ", resultat);
    }
}

60-29. Les fonctions - Correction

 
Sélectionnez
import std.stdio;
 
void afficherMenu(string[] elements, int premierNombre)
{
    foreach (i, element; elements) {
        writeln(' ', i + premierNombre, ' ', element);
    }
}
 
void main()
{
    string[] elements =
        [ "Noir", "Rouge", "Vert", "Bleu", "Blanc" ];
    afficherMenu(elements, 1);
}
  1. Voici quelques idées :
 
Sélectionnez
void placerPoint(Toile toile, int ligne, int colonne, dchar point)
{
   toile[ligne][colonne] = point;
}

60-30. Paramètre des fonctions - Correction

Parce que les paramètres de cette fonction sont du genre de ceux qui sont copiés depuis les arguments (types valeur), ce sont ces copies qui sont échangées à l'intérieur de la fonction.
Pour faire en sorte que la fonction échange les arguments, les deux paramètres doivent être passés par référence :

 
Sélectionnez
void echanger(ref int premier, ref int second)
{
    immutable int temp = premier;
    premier = second;
    second = temp;
}

Avec ce changement, les variables dans main() sont échangées :

 
Sélectionnez
2 1

Même si ce n'est pas lié au problème original, notez également que temp est spécifié comme immutable, car il n'a pas à être modifié dans la fonction après son initialisation.

60-31. Environnement du Programme - Correction

 
Sélectionnez
import std.stdio;
import std.conv;
 
int main(string[] args)
{
    if (args.length != 4) {
        stderr.writeln(
            "ERREUR ! Usage : \n    ", args[0],
            " un_nombre operateur un_autre_nombre");
        return 1;
    }
 
    immutable premier = to!double(args[1]);
    string op = args[2];
    immutable second = to!double(args[3]);
 
    switch (op) {
 
    case "+":
        writeln(premier + second);
        break;
 
    case "-":
        writeln(premier - second);
        break;
 
    case "x":
        writeln(premier * second);
        break;
 
    case "/":
        writeln(premier / second);
        break;
 
    default:
        throw new Exception("Opérateur invalide : " ~ op);
    }
 
    return 0;
}
 
Sélectionnez
import std.stdio;
import std.process;
 
void main()
{
    write("Veuillez saisir la ligne de commande à exécuter : ");
    string ligneDeCommande = readln();
 
    writeln("La sortie : ", executeShell(ligneDeCommande));
}

60-32. assert et enforce - Correction

  1. Vous remarquerez que le programme termine normalement quand on saisit 06:09 et 1:2. Cependant, vous pourrez remarquer que le temps de départ ne correspond pas à ce qui a été saisi par l'utilisateur :

     
    Sélectionnez
    1 heure et 2 minutes après 09:06 donne 10:08.
  2. Comme vous pouvez le voir, même si le moment qui a été saisi est 06:09, la sortie contient 09:06. Cette erreur sera attrapée à l'aide d'une assertion dans le problème suivant.

  3. L'erreur d'assertion après la saisie de 06:09 et 15:2 nous amène à la ligne suivante :

     
    Sélectionnez
    string momentVersChaine(in int heure, in int minute)
    {
        assert((heure >= 0) && (heure <= 23));
        // ...
    }
  4. Pour que cette assertion échoue, cette fonction doit être appelée avec une heure incorrecte.
    Les deux seuls appels à momentVersChaine() dans le programme ne semblent pas avoir de problème :

     
    Sélectionnez
    writefln("%s heures et %s minutes après %s donne  %s.",
             DuréeHeures, duréeMinutes,
             momentVersChaine(heuresDepart, minutesDepart),
             momentVersChaine(endHour, endMinute));
  5. Une enquête un peu plus poussée devrait révéler la vraie cause du bogue : les variables heure et minute sont échangées lors de la lecture du moment de départ :

     
    Sélectionnez
    lireMoment("Moment de départ", minutesDepart, heuresDepart);
  6. Cet erreur de programmation provoque l'interprétation de l'heure comme 09:06 et l'incrémente par la durée 15:2 causant une valeur horaire invalide.
    Une correction évidente est de passer les variables heure et minute dans le bon ordre :

     
    Sélectionnez
    lireMoment("Start time", heuresDepart, minutesDepart);
  7. La sortie :

     
    Sélectionnez
    Moment de départ ? (HH:MM) 06:09
    Durée ? (HH:MM) 15:2
    15 heures et 2 minutes après 06:09 donne 21:11.
  8. Il s'agit de la même assertion :

     
    Sélectionnez
    assert((heure >= 0) && (heure <= 23));
  9. La raison est que ajouterMoment() peut produire des heures qui sont plus grandes que 23. Ajouter un modulo à la fin garantirait une des sorties de la fonction :

     
    Sélectionnez
    void ajouterDurée(in int heuresDepart, in int minutesDepart,
                      in int DuréeHeures, in int duréeMinutes,
                      out int heuresResultat, out int minutesResultat)
    {
        heuresResultat = heuresDepart + DuréeHeures;
        minutesResultat = minutesDepart + duréeMinutes;
     
        if (minutesResultat > 59) {
            ++heuresResultat;
        }
     
        heuresResultat %= 24;
    }
  10. Notez que la fonction a d'autres problèmes. Par exemple, minutesResultat peut être supérieure à 59. La fonction suivante calcule la valeur des minutes correctement et s'assure que la sortie de la fonction vérifie les spécifications :

     
    Sélectionnez
    void ajouterDurée(in int heuresDepart, in int minutesDepart,
                      in int DuréeHeures, in int duréeMinutes,
                      out int heuresResultat, out int minutesResultat)
    {
        heuresResultat = heuresDepart + DuréeHeures;
        minutesResultat = minutesDepart + duréeMinutes;
     
        heuresResultat += minutesResultat / 60;
        heuresResultat %= 24;
        minutesResultat %= 60;
     
        assert((heuresResultat >= 0) && (heuresResultat <= 23));
        assert((minutesResultat >= 0) && (minutesResultat <= 59));
    }
  11. Bonne chance.

60-33. Tests Unitaires - Correction

La première chose à faire est de compiler et exécuter le programme pour s'assurer que les tests fonctionnent et échouent :

 
Sélectionnez
$ dmd deneme.d -ofdeneme -w -unittest // ou: gdc -Wall -funittest deneme.d -o deneme
$ ./deneme
core.exception.AssertError@deneme(11): unittest failure

Le numéro de la ligne 11 indique que le premier test a échoué.

Pour des raisons pédagogiques, écrivons une implémentation incorrecte qui passe le premier test par accident. La fonction suivante retourne simplement une copie de l'entrée :

 
Sélectionnez
dstring toFront(dstring str, in dchar lettre)
{
    dstring resultat;
 
    foreach (c; str) {
        resultat ~= c;
    }
 
    return resultat;
}
 
unittest
{
    immutable str = "hello"d;
 
    assert(toFront(str, 'h') == "hello");
    assert(toFront(str, 'o') == "ohell");
    assert(toFront(str, 'l') == "llheo");
}
 
void main()
{}

Le premier test passe, mais le deuxième échoue :

 
Sélectionnez
$ ./deneme
core.exception.AssertError@deneme.d(17): unittest failure

Voici une implémentation correcte qui passe tous les tests :

 
Sélectionnez
dstring toFront(dstring str, in dchar lettre)
{
    dchar[] debut;
    dchar[] fin;
 
    foreach (c; str) {
        if (c == lettre) {
            debut ~= c;
 
        } else {
            fin ~= c;
        }
    }
 
    return (debut ~ fin).idup;
}
 
unittest
{
    immutable str = "hello"d;
 
    assert(toFront(str, 'h') == "hello");
    assert(toFront(str, 'o') == "ohell");
    assert(toFront(str, 'l') == "llheo");
}
 
void main()
{}

Les tests passent :

 
Sélectionnez
$ ./deneme
$

Cette fonction peut maintenant être modifiée de différentes manières avec l'assurance que ses tests devront passer. Les deux implémentations suivantes sont très différentes de la première, mais sont toutes deux correctes vis-à-vis des tests.

Une implémentation qui utilise std.algorithm.partition :

 
Sélectionnez
import std.algorithm;
 
dstring toFront(dstring str, in dchar lettre)
{
    dchar[] result = str.dup;
    partition!(c => c == lettre, SwapStrategy.stable)(result);
 
    return result.idup;
}
 
unittest
{
    immutable str = "hello"d;
 
    assert(toFront(str, 'h') == "hello");
    assert(toFront(str, 'o') == "ohell");
    assert(toFront(str, 'l') == "llheo");
}
 
void main()
{}

Note : la syntaxe => qui apparaît dans le programme ci-avant crée une fonction lambda. Nous verrons les fonctions lambda dans des chapitres ultérieurs.

L'implémentation suivante compte combien de fois la lettre spéciale apparaît dans la chaîne. Cette information est ensuite envoyée à une autre fonction nommée repete pour produire la première partie du résultat. Notez que repete a elle-même un jeu de tests unitaires :

 
Sélectionnez
dstring repete(size_t compte, dchar lettre)
{
    dstring resultat;
 
    foreach (i; 0..compte) {
        resultat ~= lettre;
    }
 
    return resultat;
}
 
unittest
{
    assert(repete(3, 'z') == "zzz");
    assert(repete(10, 'é') == "éééééééééé");
}
 
dstring toFront(dstring str, in dchar lettre)
{
    size_t nombreLettreSpeciale;
    dstring fin;
 
    foreach (c; str) {
        if (c == lettre) {
            ++nombreLettreSpeciale;
 
        } else {
            fin ~= c;
        }
    }
 
    return repete(nombreLettreSpeciale, lettre) ~ fin;
}
 
unittest
{
    immutable str = "hello"d;
 
    assert(toFront(str, 'h') == "hello");
    assert(toFront(str, 'o') == "ohell");
    assert(toFront(str, 'l') == "llheo");
}
 
void main()
{}

60-34. Programmation par contrat - Correction

Le bloc unittest peut être implémenté simplement en copiant les vérifications qui ont déjà été écrites dans la fonction main. Le seul ajout ci-dessous est le test pour le cas dans lequel la seconde équipe gagne :

 
Sélectionnez
int ajouterPoints(in int buts1,
                  in int buts2,
                  ref int points1,
                  ref int points2)
in
{
    assert(buts1 >= 0);
    assert(buts2 >= 0);
    assert(points1 >= 0);
    assert(points2 >= 0);
}
out (resultat)
{
    assert((resultat >= 0) && (resultat <= 2));
}
body
{
    int gagnante;
 
    if (buts1 > buts2) {
        points1 += 3;
        gagnante = 1;
 
    } else if (buts1 < buts2) {
        points2 += 3;
        gagnante = 2;
 
    } else {
        ++points1;
        ++points2;
        gagnante = 0;
    }
 
    return gagnante;
}
 
unittest
{
    int points1 = 10;
    int points2 = 7;
    int gagnante;
 
    // La première équipe gagne
    gagnante = ajouterPoints(3, 1, points1, points2);
    assert(points1 == 13);
    assert(points2 == 7);
    assert(gagnante == 1);
 
    // Nul
    gagnante = ajouterPoints(2, 2, points1, points2);
    assert(points1 == 14);
    assert(points2 == 8);
    assert(gagnante == 0);
 
    // La seconde équipe gagne
    gagnante = ajouterPoints(0, 1, points1, points2);
    assert(points1 == 14);
    assert(points2 == 11);
    assert(gagnante == 2);
}
 
void main()
{
    // ...
}

60-35. Structures - Correction

  1. Un des choix de conception les plus aisés est d'utiliser deux membres dchar :

     
    Sélectionnez
    struct Carte
    {
        dchar couleur;
        dchar valeur;
    }
  2. Il s'agit simplement d'afficher les deux membres côte à côte :

     
    Sélectionnez
    void afficherCarte(in Carte carte)
    {
        write(carte.couleur, carte.valeur);
    }
  3. En supposant qu'il y ait déjà une fonction appelée nouvelleCouleur(), nouveauJeu() peut être implémentée en appelant cette fonction pour chaque couleur :

     
    Sélectionnez
    Carte[] nouveauJeu()
    out (resultat)
    {
        assert(resultat.length == 52);
    }
    body
    {
        Carte[] jeu;
     
        jeu ~= nouvelleCouleur('');
        jeu ~= nouvelleCouleur('');
        jeu ~= nouvelleCouleur('');
        jeu ~= nouvelleCouleur('');
     
        return jeu;
    }
  4. Le reste du travail peut être accompli par la fonction nouvelleCouleur() suivante, qui construit la couleur en combinant le caractère de la couleur avec chaque valeur d'une chaîne :

     
    Sélectionnez
    Carte[] nouvelleCouleur(in dchar couleur)
    in
    {
        assert((couleur == '') ||
               (couleur == '') ||
               (couleur == '') ||
               (couleur == ''));
    }
    out (resultat)
    {
        assert(resultat.length == 13);
    }
    body
    {
        Carte[] cartesCouleur;
     
        foreach (valeur; "234567890VDRA") {
            cartesCouleur ~= Carte(couleur, valeur);
        }
     
        return cartesCouleur;
    }
  5. On peut noter que ces fonctions mettent en œuvre la programmation par contrat afin de réduire les risques d'erreurs de programmation.

  6. Si l'on échange deux éléments pris au hasard, cela suffit à ce que le jeu soit de plus en plus mélangé à chaque itération. Bien qu'il soit possible de tomber par hasard deux fois sur le même élément, échanger un élément avec lui-même n'a pas d'effet néfaste hormis le fait de ne pas obtenir un jeu plus mélangé après l'itération en question qu'avant son exécution.

     
    Sélectionnez
    void melanger(Carte[] jeu, in int repetitions)
    {
        /* Note : un meilleur algorithme consisterait à parcourir
         *        le jeu du début à la fin, en échangeant l'élément
         *        courant avec un tiré aléatoirement parmi les
         *        éléments situés entre l'élément courant et la fin.
         *
         * Il serait encore préférable d'appeler randomShuffle, du
         * module std.algorithm.module, qui implémente déjà cet
         * algorithme. Reportez-vous au commentaire dans main()
         * pour voir comment randomShuffle() s'utilise.
         */
     
        foreach (i; 0 .. repetitions) {
            // Choisit deux éléments au hasard
            immutable premier = uniform(0, jeu.length);
            immutable second = uniform(0, jeu.length);
     
            swap(jeu[premier], jeu[second]);
        }
    }
  7. La fonction ci-dessus appelle std.algorithm.swap qui échange tout simplement les valeurs de ses deux paramètres ref. En pratique, c'est l'équivalent de la fonction suivante :

     
    Sélectionnez
    void mySwap(ref Carte gauche,
                ref Carte droite)
    {
        immutable temporaire = gauche;
        gauche = droite;
        droite = temporaire;
    }

  8. Voici le programme au complet :
 
Sélectionnez
import std.stdio;
import std.random;
import std.algorithm;
 
struct Carte
{
    dchar couleur;
    dchar valeur;
}
 
void afficherCarte(in Carte carte)
{
    write(carte.couleur, carte.valeur);
}
 
Carte[] nouvelleCouleur(in dchar couleur)
in
{
    assert((couleur == '') ||
           (couleur == '') ||
           (couleur == '') ||
           (couleur == ''));
}
out (resultat)
{
    assert(resultat.length == 13);
}
body
{
    Carte[] cartesCouleur;
 
    foreach (valeur; "234567890JQKA") {
        cartesCouleur ~= Carte(couleur, valeur);
    }
 
    return cartesCouleur;
}
 
Carte[] nouveauJeu()
out (resultat)
{
    assert(resultat.length == 52);
}
body
{
    Carte[] jeu;
 
    jeu ~= nouvelleCouleur('');
    jeu ~= nouvelleCouleur('');
    jeu ~= nouvelleCouleur('');
    jeu ~= nouvelleCouleur('');
 
    return jeu;
}
 
void melanger(Carte[] jeu, in int repetitions)
{
    /* Note : un meilleur algorithme consisterait à parcourir
     *        le jeu du début à la fin, en échangeant l'élément
     *        courant avec un tiré aléatoirement parmi les
     *        éléments situés entre l'élément courant et la fin.
     *
     * Il serait encore préférable d'appeler randomShuffle, du
     * module std.algorithm.module, qui implémente déjà cet
     * algorithme. Reportez-vous au commentaire dans main()
     * pour voir comment randomShuffle() s'utilise.
     */
 
    foreach (i; 0 .. repetitions) {
        // Choisit deux éléments au hasard
        immutable premier = uniform(0, jeu.length);
        immutable second = uniform(0, jeu.length);
 
        swap(jeu[premier], jeu[second]);
    }
}
 
void main()
{
    Carte[] jeu = nouveauJeu();
 
    melanger(jeu, 100);
    /* Note : au lieu d'appeler melanger(), il serait préférable
     *        d'appeler randomShuffle comme ceci :
     *
     * randomShuffle(jeu);
     */
 
    foreach (carte; jeu) {
        afficherCarte(carte);
        write(' ');
    }
 
    writeln();
}

60-36. Nombre variable de paramètres - Correction

Pour que la fonction calculer puisse accepter un nombre variable de paramètres, elle doit inclure une tranche de Calcul suivie de … :

 
Sélectionnez
double[] calculer(in Calcul[] calculs ...)
    {
        double[] resultats;
 
        foreach (calcul; calculs) {
            final switch (calcul.op) {
                case Operation.addition:
                    resultats ~= calcul.premier + calcul.second;
                    break;
 
                case Operation.soustraction:
                    resultats ~= calcul.premier - calcul.second;
                    break;
 
                case Operation.multiplication:
                    resultats ~= calcul.premier * calcul.second;
                    break;
 
                case Operation.division:
                    resultats ~= calcul.premier /  calcul.second;
                    break;
            }
        }
 
        return resultats;
}

Voici le programme complet :

 
Sélectionnez
import std.stdio;
 
enum Operation { addition, soustraction, multiplication, division }
 
struct Calcul {
    Operation op;
    double premier;
    double second;
}
 
double[] calculer(in Calcul[] calculs ...)
{
    double[] resultats;
 
    foreach (calcul; calculs) {
        final switch (calcul.op) {
            case Operation.addition:
                resultats ~= calcul.premier + calcul.second;
                break;
 
            case Operation.soustraction:
                resultats ~= calcul.premier - calcul.second;
                break;
 
            case Operation.multiplication:
                resultats ~= calcul.premier * calcul.second;
                break;
 
            case Operation.division:
                resultats ~= calcul.premier /  calcul.second;
                break;
        }
    }
 
    return resultats;
}
 
void main() {
    writeln(calculer(Calcul(Operation.addition, 1.1, 2.2),
                     Calcul(Operation.soustraction, 3.3, 4.4),
                     Calcul(Operation.multiplication, 5.5, 6.6),
                     Calcul(Operation.division, 7.7, 8.8)));
}

60-37. Surcharge de fonctions - Correction

Les deux surcharges suivantes utilisent les surcharges d'info() existantes :

 
Sélectionnez
void info(in Repas repas)
{
    info(repas.moment);
    write('-');
    info(ajouterDuree(repas.moment, MomentDeLaJournee(1, 30)));
    write(" Repas, adresse : ", repas.adresse);
}
 
void info(planningDeLaJournee planning)
{
    info(planning.reunionMatin);
    writeln();
    info(planning.repas);
    writeln();
    info(planning.reunionApresMidi);
}

Voici le programme complet, qui utilise tous ces types :

 
Sélectionnez
import std.stdio;
 
struct MomentDeLaJournee
{
    int heure;
    int minute;
}
 
void info(in MomentDeLaJournee moment)
{
    writef("%02s:%02s", moment.heure, moment.minute);
}
 
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;
}
 
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);
}
 
struct Repas
{
    MomentDeLaJournee moment;
    string            adresse;
}
 
void info(in Repas repas)
{
    info(repas.moment);
    write('-');
    info(ajouterDuree(repas.moment, MomentDeLaJournee(1, 30)));
 
    write(" Repas, adresse : ", repas.adresse);
}
 
struct planningDeLaJournee
{
    Reunion reunionMatin;
    Repas   repas;
    Reunion reunionApresMidi;
}
 
void info(planningDeLaJournee planning)
{
    info(planning.reunionMatin);
    writeln();
    info(planning.repas);
    writeln();
    info(planning.reunionApresMidi);
}
 
void main()
{
    immutable reunionCyclisme = Reunion("Cyclisme", 4,
                                        MomentDeLaJournee(10, 30),
                                        MomentDeLaJournee(11, 45));
 
    immutable dejeuner = Repas(MomentDeLaJournee(12, 30), "Istamboul");
 
    immutable reunionBudget = Reunion("Budget", 8,
                                    MomentDeLaJournee(15, 30),
                                    MomentDeLaJournee(17, 30));
 
    immutable planningAujourdhui = planningDeLaJournee(reunionCyclisme,
                                                       dejeuner,
                                                       reunionBudget);
 
    info(planningAujourdhui);
    writeln();
}

La fonction main() peut aussi n'être écrite qu'avec des objets littéraux :

 
Sélectionnez
void main()
{
    info(planningDeLaJournee(Reunion("Cyclisme", 4,
                             MomentDeLaJournee(10, 30),
                             MomentDeLaJournee(11, 45)),
 
                             Repas(MomentDeLaJournee(12, 30), "Istamboul"),
 
                             Reunion("Budget", 8,
                                     MomentDeLaJournee(15, 30),
                                     MomentDeLaJournee(17, 30))));
 
    writeln();
}

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+