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

Cours complet pour apprendre à programmer en D


précédentsommairesuivant

36. Fonctions

Alors que les types fondamentaux sont les briques élémentaires des données d'un programme, les fonctions sont les briques élémentaires du comportement de ce programme.

Les fonctions sont également très liées au savoir-faire du programmeur. Les fonctions qui sont écrites par des programmeurs expérimentés sont succinctes, simples et claires. Cela est vrai dans les deux sens : le simple fait d'essayer d'identifier et d'écrire des briques élémentaires plus petites rend un programmeur meilleur.

Nous avons vu des expressions et instructions basiques dans les chapitres précédents. Même s'il y en aura encore beaucoup dans les chapitres à venir, celles que nous avons vues jusqu'à maintenant sont des fonctionnalités couramment utilisées du D. Cependant, à elles seules, elles ne suffisent pas à écrire de grands programmes. Les programmes que nous avons écrits jusqu'à maintenant sont très petits, chacun montrant une seule fonctionnalité simple du langage. Essayer d'écrire un programme un tant soit peu complexe sans fonction serait très difficile et propice aux bogues.

Les fonctions regroupent des instructions et des expressions pour former des unités d'exécution de programme. On donne un nom à ce regroupement d'instructions et d'expressions qui décrit la tâche que celles-ci accomplissent. Elles peuvent être appelées (ou exécutées) par ce nom.

L'idée de donner des noms à un groupe d'étapes est courante dans la vie de tous les jours. Par exemple, l'action de cuisiner une omelette peut être décrite plus en détail par les étapes suivantes :

  • prendre une poêle ;
  • prendre du beurre ;
  • prendre un œuf ;
  • allumer la cuisinière ;
  • mettre la poêle sur le feu ;
  • mettre le beurre dans la poêle quand celle-ci est chaude ;
  • mettre l'œuf dans le beurre quand celui-ci est fondu ;
  • enlever la poêle du feu quand l'œuf est cuit ;
  • éteindre la cuisinière.

Comme une telle précision est évidemment excessive, les étapes qui sont liées entre elles peuvent être regroupées sous un nom unique :

  • faire les préparations (prendre la poêle, le beurre et l'œuf) ;
  • allumer la cuisinière ;
  • cuire l'œuf (mettre la poêle sur le feu, etc.) ;
  • éteindre la cuisinière.

En allant plus loin, il peut y avoir un seul nom pour toutes les étapes :

  • faire une omelette avec un œuf (toutes les étapes).

Les fonctions sont basées sur la même idée : les étapes qui peuvent être regroupées sous un même nom sont mises ensemble pour former une fonction. Par exemple, commençons avec les lignes de code suivantes qui affichent un menu :

 
Sélectionnez
writeln(" 0 Quitter");
writeln(" 1 Ajouter");
writeln(" 2 Soustraire");
writeln(" 3 Multiplier");
writeln(" 4 Diviser");

Comme rassembler ces lignes sous le nom d'afficherMenu aurait un sens, on peut les mettre ensemble pour former une fonction avec la syntaxe suivante :

 
Sélectionnez
void afficherMenu()
{
    writeln(" 0 Quitter");
    writeln(" 1 Ajouter");
    writeln(" 2 Soustraire");
    writeln(" 3 Multiplier");
    writeln(" 4 Diviser");
}

Le contenu de cette fonction peut maintenant être exécuté depuis main() simplement par son nom :

 
Sélectionnez
void main()
{
    afficherMenu();
 
    /* ... */
}

On peut noter, en observant les similarités entre les définitions de afficherMenu() et main() que main() est également une fonction. L'exécution des programmes D commence par la fonction nommée main() et exécute les autres fonctions depuis là.

36-1. Paramètres

Une des forces des fonctions vient du fait que l'on peut ajuster leur comportement à travers des paramètres.

Continuons avec l'exemple de l'omelette en le modifiant pour faire une omelette de cinq œufs au lieu d'un. Les étapes sont exactement les mêmes, la seule différence étant le nombre d'œufs à utiliser. On peut changer la description précédente en conséquence :

  • faire les préparations (prendre la poêle, le beurre et cinq œufs) ;
  • allumer la cuisinière ;
  • cuire les œufs (mettre la poêle sur le feu, etc.) ;
  • éteindre la cuisinière.

De même, l'étape la plus générale deviendrait :

  • préparer une omelette avec cinq œufs (toutes les étapes).

Cette fois, il y a une information de plus qui concerne certaines étapes : « prendre cinq œufs », « cuisiner les œufs » et « préparer une omelette avec cinq œufs ».

Le comportement des fonctions peut être ajusté de la même manière que dans l'exemple de l'omelette. Les données que les fonctions utilisent pour ajuster leur comportement sont appelées paramètres. Les paramètres sont indiqués dans la liste des paramètres de la fonction, séparés par des virgules. La liste des paramètres est écrite entre parenthèses après le nom de la fonction.

La fonction afficherMenu() ci-dessus était définie par une liste de paramètres vide parce que cette fonction affichait toujours le même menu. Supposons que, parfois, on veuille que le menu soit affiché différemment dans différents contextes. Par exemple, il pourrait être préférable d'afficher la première entrée comme « Revenir » plutôt que « Quitter », selon l'endroit du programme dans lequel on se trouve.

Dans une telle situation, la première entrée du menu peut être paramétrée en étant définie dans la liste des paramètres. La fonction utilise alors la valeur de ce paramètre au lieu du littéral "Quitter" :

 
Sélectionnez
void afficherMenu(string premiereEntree)
{
    writeln(" 0 ", premiereEntree);
    writeln(" 1 Ajouter");
    writeln(" 2 Soustraire");
    writeln(" 3 Multiplier");
    writeln(" 4 Diviser");
}

Notez que comme l'information que premiereEntree contient est un bout de texte, son type a été indiqué comme string dans la liste des paramètres. Cette fonction peut maintenant être appelée avec différentes valeurs de paramètre pour afficher des menus qui ont une première entrée différente. Il suffit pour cela d'utiliser des valeurs de chaînes appropriées selon l'endroit où la fonction est appelée :

 
Sélectionnez
// À un endroit du programme :
afficherMenu("Quitter");
// ...
// À un autre endroit du programme :
afficherMenu("Revenir");

Quand vous écrivez vos propres fonctions, vous pouvez rencontrer des erreurs de compilation avec des paramètres de type string. La fonction afficherMenu() ci-dessus a un problème de ce genre. Telle qu'elle est écrite, elle ne peut pas être appelée avec des paramètres de type char[]. Par exemple, le code suivant donnerait une erreur de compilation :

 
Sélectionnez
char[] uneEntrée;
uneEntrée ~= "Prendre la racine carrée";
afficherMenu(uneEntrée);  // ← ERREUR de compilation

En revanche, si afficherMenu() avait été définie pour prendre un paramètre char[], alors elle n'aurait pas pu être appelée avec des chaînes du type "Quitter". Ceci est lié à l'idée d'immuabilité et du mot-clé immutable, que nous verrons tous deux dans le chapitre suivant.

Continuons avec la fonction du menu et supposons qu'il n'est pas approprié de toujours commencer la numérotation des entrées par zéro. Dans ce cas, le numéro de début peut également être passé à la fonction en deuxième paramètre. Les paramètres de la fonction doivent être séparés par des virgules :

 
Sélectionnez
void afficherMenu(string premiereEntree, int premierNumero)
{
    writeln(' ', premierNumero, ' ', premiereEntree);
    writeln(' ', premierNumero + 1, " Ajouter");
    writeln(' ', premierNumero + 2, " Soustraire");
    writeln(' ', premierNumero + 3, " Multiplier");
    writeln(' ', premierNumero + 4, " Diviser");
}

Il est maintenant possible de dire à la fonction à partir de quel numéro commencer :

 
Sélectionnez
afficherMenu("Revenir", 1);

36-2. Appeler une fonction

Démarrer une fonction pour qu'elle effectue sa tâche est « appeler une fonction ». La syntaxe d'appel d'une fonction est la suivante :

 
Sélectionnez
nom_de_la_fonction(valeurs_des_parametres)

Les valeurs réelles des paramètres qui sont passées aux fonctions sont appelées arguments de la fonction. Même si les termes paramètre et argument sont parfois interchangés dans la littérature, ils désignent des concepts différents.

Les arguments sont mis en correspondance avec les paramètres un à un dans l'ordre de définition des paramètres. Par exemple, le dernier appel à afficherMenu() ci-dessus utilise les arguments "Revenir" et 1, qui correspondent aux paramètres premiereEntree et premierNombre, respectivement.

Le type de chaque argument doit correspondre au type du paramètre correspondant.

36-3. Faire le travail

Dans les chapitres précédents, nous avons défini les expressions comme des entités qui effectuent une tâche. Les appels de fonctions sont aussi des expressions : elles effectuent une certaine tâche. Effectuer une tâche peut signifier une combinaison de deux choses.

  • Avoir des effets de bord : les effets de bord sont n'importe quel changement d'état du programme ou de son environnement. Seules certaines opérations ont des effets de bord. Un exemple est la modification de stdout quand afficherMenu() affiche le menu. Un autre exemple d'effet de bord serait une fonction qui ajouterait un objet Etudiant à un conteneur étudiant : cela entraînerait l'agrandissement du conteneur.
    En résumé, les opérations qui modifient l'état du programme ont des effets de bord.
  • Produire une valeur : certaines opérations ne font que produire une valeur. Par exemple, une fonction qui ajoute des nombres produirait le résultat de cette opération. Un autre exemple serait une fonction qui créerait un objet Etudiant à partir du nom et de l'adresse d'un étudiant produirait un objet Etudiant.
  • Avoir des effets de bord et produire une valeur : certaines opérations font les deux. Par exemple, une fonction qui lit deux valeurs depuis stdin et calcule leur somme a des effets de bord parce qu'elle modifie l'état de stdin et produit également la somme des deux valeurs.
  • Aucune opération : même si toute fonction fait partie d'une des trois catégories ci-dessus, il arrive que certaines fonctions ne fassent rien, dans certaines conditions lors de la compilation ou de l'exécution.

36-4. La valeur de retour

Le résultat qu'une fonction produit est appelé sa valeur de retour. Ce terme vient de l'observation qu'une fois que l'exécution du programme passe dans une fonction, elle finit par revenir là où elle a été appelée. Les fonctions sont appelées et elles retournent des valeurs.

Comme n'importe quelle autre valeur, les valeurs de retour ont un type. Le type de la valeur de retour est indiqué juste avant le nom de la fonction à l'endroit où la fonction est définie. Par exemple, une fonction qui ajoute deux valeurs int et qui retourne leur somme en tant qu'int serait définie de cette manière :

 
Sélectionnez
int ajouter(int premier, int second)
{
    // ...  le travail de la fonction ...
}

La valeur que la fonction retourne vient en place de l'appel de la fonction lui-même. Par exemple, en supposant que l'appel de fonction ajouter(5, 7) produise la valeur 12, les deux lignes suivantes sont équivalentes :

 
Sélectionnez
writeln("Résultat: ", ajouter(5, 7));
writeln("Résultat: ", 12);

À la première ligne, la fonction ajouter() est appelée avec les arguments 5 et 7 avant que writeln() soit appelée. La valeur 12 que la fonction retourne est à son tour passée à writeln() en deuxième argument.

Ceci permet de passer les valeurs de retour des fonctions aux autres fonctions pour former des expressions complexes :

 
Sélectionnez
writeln("Résultat : ", ajouter(5, diviser(100, nombreEtudiants())));

Dans cette ligne, la valeur de retour de nombreEtudiants() est passée à diviser() en second argument, la valeur de retour de diviser() est passée à ajouter() en second argument, et finalement la valeur de retour de ajouter() est passée à writeln() en second argument.

36-5. L'instruction return

La valeur de retour d'une fonction est indiquée par le mot-clé return :

 
Sélectionnez
int ajouter(int premier, int second)
{
    int resultat = premier + second;
    return resultat;
}

Une fonction produit sa valeur de retour en utilisant des instructions, des expressions et éventuellement en appelant d'autres fonctions. La fonction retourne ensuite cette valeur avec le mot-clé return, point auquel l'exécution de la fonction se finit.

Il est possible d'avoir plus d'une instruction de retour dans une fonction. La valeur de la première instruction de retour qui est exécutée détermine la valeur de retour de la fonction pour un appel particulier :

 
Sélectionnez
int calculComplexe(int unParametre, int unAutreParametre)
{
    if (unParametre == unAutreParametre) {
        return 0;
    }
 
    return unParametre * unAutreParametre;
}

La fonction ci-dessus retourne 0 quand les deux paramètres sont égaux et le produit de leurs valeurs quand ils sont différents.

36-6. Les fonctions void

Le type de retour des fonctions qui ne produisent pas de valeur est conventionnellement void. Nous l'avons vu de nombreuses fois avec la fonction main(), de même qu'avec la fonction afficherMenu définie plus haut. Comme elles ne retournent pas de valeur aux fonctions qui les appellent, leur valeur de retour a été définie comme void. (Note : main() peut également être définie comme retournant int. Nous le verrons dans un chapitre ultérieur.)

36-7. Le nom de la fonction

Le nom d'une fonction doit être choisi de manière à indiquer clairement son rôle. Par exemple, les noms ajouter et afficherMenu étaient appropriés parce que leur rôle était respectivement d'ajouter deux valeurs et d'afficher un menu.

Une règle courante pour les noms de fonctions est de contenir un verbe comme ajouter ou afficher. Selon cette règle, des noms comme addition() et menu() seraient moins bons.

Cependant, il est acceptable de simplement nommer les fonctions avec un substantif si ces fonctions n'ont pas d'effet secondaire. Par exemple, une fonction qui retourne la température actuelle peut être nommée temperatureActuelle() au lieu de obtenirTemperatureActuelle().

Trouver des noms clairs, concis et cohérents relève un peu de l'art de la programmation.

36-8. Qualité de code à travers les fonctions

Les fonctions peuvent améliorer la qualité du code. Des fonctions plus petites avec des responsabilités plus légères permettent d'écrire des programmes qui sont plus faciles à maintenir.

36-8-1. La duplication de code est nuisible

Une pratique hautement néfaste à la qualité d'un programme est la duplication de code. La duplication de code est ce qui se produit quand il y a plus d'un bout de code dans le programme qui réalise la même tâche.

Même si cela arrive parfois en copiant des lignes de code à un endroit différent, cela peut également arriver en écrivant des lignes de codes similaires sans le vouloir.

Un des problèmes avec la duplication de code est qu'un éventuel bogue serait présent dans chaque copie, nécessitant d'effectuer sa correction à plusieurs endroits. Inversement, quand le code apparaît une seule fois dans le programme, le corriger à un seul endroit permet de se débarrasser du bogue une fois pour toutes.

Comme je l'ai mentionné précédemment, les fonctions sont très liées au savoir-faire du programmeur. Les programmeurs expérimentés sont toujours sur leurs gardes à propos de la duplication de code. Ils essayent toujours d'identifier les similarités dans le code et déplacent les bouts de codes communs dans des fonctions à part (ou dans des structures, classes, modèles… communs, comme nous le verrons dans des chapitres ultérieurs).

Commençons par un programme qui contient du code dupliqué. Voyons comment cette duplication peut être éradiquée en déplaçant du code dans des fonctions (c.-à-d. en refactorisant le code). Le programme suivant lit des nombres depuis l'entrée et les affiche d'abord dans l'ordre dans lequel ils sont arrivés et ensuite dans l'ordre croissant :

 
Sélectionnez
import std.stdio;
 
void main()
{
    int[] nombres;
 
    int compte;
    write("Combien de nombres allez-vous saisir ? ");
    readf(" %s", &compte);
 
    // Lecture des nombres
    foreach (i; 0 .. compte) {
        int nombre;
        write("Nombres ", i, "? ");
        readf(" %s", &nombre);
 
        nombres ~= nombre;
    }
 
    // affichage des nombres
    writeln("Avant de trier :");
    foreach (i, nombre; nombres) {
        writefln("%3d:%5d", i, nombre);
    }
 
    nombres.sort;
 
    // affichage des nombres
    writeln("Après avoir trié :");
    foreach (i, nombre; nombres) {
        writefln("%3d:%5d", i, nombre);
    }
}

Certaines lignes dupliquées sont évidentes dans ce code. Les deux dernières boucles qui sont utilisées pour afficher les nombres sont exactement les mêmes. Définir une fonction qui peut être judicieusement nommée afficher() éviterait cette duplication. La fonction pourrait prendre une tranche en paramètre et l'afficher :

 
Sélectionnez
void afficher(int[] tranche)
{
    foreach (i, element; tranche) {
        writefln("%3s:%5s", i, element);
    }
}

Notez que le nom du paramètre est défini de façon plus générique comme tranche plutôt que nombres. La raison est que la fonction n'a pas spécialement à savoir ce que les éléments de la tranche représentent. Cela n'est connu qu'à l'endroit où la fonction est appelée. Les éléments peuvent être des numéros d'étudiants, des bouts d'un mot de passe, etc. Comme cela peut ne pas être connu par la fonction afficher(), des noms généraux comme tranche et element sont utilisés dans son implémentation.

La nouvelle fonction peut être appelée depuis les deux endroits où la tranche doit être affichée :

 
Sélectionnez
import std.stdio;
 
void afficher(int[] tranche)
{
    foreach (i, element; tranche) {
        writefln("%3s:%5s", i, element);
    }
}
 
void main()
{
    int[] nombres;
 
    int compte;
    write("Combien de nombres allez-vous saisir ? ");
    readf(" %s", &compte);
 
    // Lecture des nombres
    foreach (i; 0 .. compte) {
        int nombre;
        write("Nombre ", i, "? ");
        readf(" %s", &nombre);
 
        nombres ~= nombre;
    }
 
    // Affichage des nombres
    writeln("Avant le tri :");
    afficher(nombres);
 
    nombres.sort;
 
    // Affichage des nombres
    writeln("Après le tri :");
    afficher(nombres);
}

On peut encore faire mieux. Notez qu'il y a toujours une ligne de titre affichée juste avant l'affichage des éléments de la tranche. Même si le titre est différent, la tâche est la même. Si afficher le titre peut être vu comme faisant partie de l'affichage de la tranche, le titre peut également être passé en paramètre. Voici les nouvelles modifications :

 
Sélectionnez
void afficher(string titre, int[] tranche)
{
    writeln(titre, " :");
 
    foreach (i, element; tranche) {
        writefln("%3s:%5s", i, element);
    }
}
 
// ...
 
    // Afficher les nombres
    afficher("Avant le tri", nombres);
 
// ...
 
    // Afficher les nombres
    afficher("Après le tri", nombres);

Cette étape a l'avantage de rendre évidents les commentaires qui apparaissent juste avant les deux appels d'afficher(). Comme le nom de la fonction communique déjà clairement ce qu'elle fait, ces commentaires ne sont plus nécessaires :

 
Sélectionnez
afficher("Avant le tri", nombres);
nombres.sort;
afficher("Apres le tri", nombres);

Même si c'est subtil, il y a encore de la duplication de code dans ce programme : les valeurs de compte et nombre sont lues exactement de la même manière. Les seules différences sont le message qui est affiché à l'utilisateur et le nom de la variable :

 
Sélectionnez
int compte;
write("Combien de nombres allez-vous saisir ? ");
readf(" %s", &compte);
 
// ...
 
    int nombre;
    write("Nombre ", i, "? ");
    readf(" %s", &nombre);

Le code serait encore mieux s'il utilisait une nouvelle fonction nommée de façon appropriée lire_entier(). La nouvelle fonction peut prendre le message en paramètre, afficher ce message, lire un entier depuis l'entrée et le retourner :

 
Sélectionnez
int lire_entier(string message)
{
    int resultat;
    write(message, " ? ");
    readf(" %s", &resultat);
    return resultat;
}

compte peut maintenant être initialisé directement avec la valeur de retour d'un appel à cette nouvelle fonction :

 
Sélectionnez
int compte = lire_entier("Combien de nombres allez-vous saisir");

nombre ne peut pas être initialisé aussi simplement parce qu'il se trouve que le compteur de boucle i fait partie du message à afficher quand on lit le nombre.

 
Sélectionnez
import std.string;
// ...
        int nombre = lire_entier(format("Nombre %s", i));

De plus, comme nombre n'est utilisé qu'à un endroit dans la boucle foreach, sa définition peut être complètement supprimée et la valeur de retour de lire_entier() peut directement être utilisée à sa place :

 
Sélectionnez
foreach (i; 0 .. compte) {
    nombres ~= lire_entier(format("Nombre %s", i));
}

Apportons une dernière modification à ce programme en déplaçant les lignes qui lisent les nombres dans une fonction à part. Ceci éliminera la nécessité du commentaire « Lecture des nombres » parce que le nom de la nouvelle fonction portera déjà cette information.

La nouvelle fonction lireLesNombres() n'a besoin d'aucun paramètre pour effectuer sa tâche. Elle lit quelques nombres et les retourne dans une tranche. Voici la version finale du programme :

 
Sélectionnez
import std.stdio;
import std.string;
 
void afficher(string titre, int[] tranche)
{
    writeln(titre, " :");
 
    foreach (i, element; tranche) {
        writefln("%3s:%5s", i, element);
    }
}
 
int lire_entier(string message)
{
    int resultat;
    write(message, " ? ");
    readf(" %s", &resultat);
    return resultat;
}
 
int[] lireLesNombres()
{
    int[] resultat;
 
    int compte = lire_entier("Combien de nombres allez-vous saisir");
 
    foreach (i; 0 .. compte) {
        resultat ~= lire_entier(format("Nombre %s", i));
    }
 
    return resultat;
}
 
void main()
{
    int[] nombres = lireLesNombres();
    afficher("Avant le tri", nombres);
    nombres.sort;
    afficher("Après le tri", nombres);
}

Comparez cette version du programme à la première. Les étapes principales du programme sont très claires dans la fonction main() du nouveau programme alors qu'il fallait examiner avec attention la fonction main() de la première version du programme pour en comprendre le but.

Même si ici le nombre total de lignes non triviales des deux versions du programme reste similaire, les fonctions rendent généralement les programmes plus courts. Ceci n'est pas apparent dans ce programme simple. Par exemple, avant que la fonction lire_entier() n'ait été définie, lire un entier depuis l'entrée demandait trois lignes de code. Après la définition de cette fonction, le même but est atteint avec une seule ligne de code. De plus, la définition de lire _entier() a permis de se débarrasser complètement de la définition de la variable nombre.

36-8-2. Lignes de codes commentées en tant que fonctions

Parfois, le besoin d'écrire un commentaire pour décrire le rôle d'un groupe de lignes de code est un indice sur le fait que ces lignes pourraient être mieux dans une nouvelle fonction. Si le nom de la fonction est assez explicite, il n'y aurait même pas besoin de commentaire.

Les trois groupes de lignes commentés dans la première version du programme ont été utilisés pour définir de nouvelles fonctions qui effectuent la même tâche.

Un autre gros avantage à supprimer des lignes de commentaires est que les commentaires tendent à devenir obsolètes par rapport au code qui reçoit des modifications. La mise à jour des commentaires lors de la modification du code est parfois oubliée, ce qui rend ces commentaires inutiles, voire trompeurs. Pour cette raison, écrire des programmes qui n'ont pas besoin d'être beaucoup commentés est bénéfique.

36-8-3. Exercices

  1. Modifiez la fonction afficherMenu() pour qu'elle prenne l'ensemble des éléments du menu dans un paramètre. Par exemple, les éléments du menu peuvent être passés à la fonction comme dans le code suivant :

     
    Sélectionnez
    string[] items =
       [ "Noir", "Rouge", "Vert", "Bleu", "Blanc" ];
    afficherMenu(items, 1);
  2. Le programme devra produire la sortie suivante :

     
    Sélectionnez
    1 Noir
    2 Rouge
    3 Vert
    4 Bleu
    5 Blanc
  3. Le programme suivant utilise un tableau bidimensionnel pour représenter une toile. Partez de ce programme et améliorez-le en lui ajoutant des fonctionnalités nouvelles :
 
Sélectionnez
import std.stdio;
 
enum nbLignesTotal = 20;
enum nbColonnesTotal = 60;
 
/*
 * L´'alias' dans la ligne suivante fait de 'Ligne' un alias de
 * dchar[nbColonnesTotal]. À partir de cet endroit, chaque
 * 'Ligne' qui est utilisée dans le reste du programme voudra
 * dire dchar[nbColonnesTotal] .
 *
 * Notez également que 'Ligne' est un tableau à taille fixe.
 */
alias dchar[nbColonnesTotal] Ligne;
 
/*
 * 'Toile' est un alias de 'tableau dynamique de Lignes'.
 */
alias Ligne[] Toile;
 
/*
 * Affiche la toile ligne par ligne.
 */
void afficher(Toile toile)
{
     foreach (ligne; toile) {
        writeln(ligne);
     }
}
 
/*
 * Place un point à l'endroit indiqué sur la toile.
 * En un sens, "peint" la toile.
 */
void placerPoint(Toile toile, int ligne, int colonne)
{
    toile[ligne][colonne] = '#';
}
 
/*
 * Dessine une ligne verticale de la taille indiquée et à
 * l'endroit indiqué.
 */
void dessinerLigneVerticale(Toile toile,
                            int ligne,
                            int colonne,
                            int taille)
{
    foreach (ligneApeindre; ligne .. ligne + taille) {
        placerPoint(toile, ligneApeindre, colonne);
    }
}
 
void main()
{
    Ligne ligneVide = '.';
 
    /* An empty toile */
    Toile toile;
 
    /* Construction de la toile en ajoutant des lignes vides */
    foreach (i; 0 .. nbLignesTotal) {
        toile ~= ligneVide;
    }
 
    /* Utilisation de la toile */
    placerPoint(toile, 7, 30);
    dessinerLigneVerticale(toile, 5, 10, 4);
 
    afficher(toile);
}

Les solutionsLes fonctions - Correction.


précédentsommairesuivant