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

Cours complet pour apprendre à programmer en D


précédentsommairesuivant

52. Nombre variable de paramètres

Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité aux paramètres lors des appels de fonctions :

  • les valeurs par défaut de paramètres ;
  • les fonctions variadiques.

52-1. Les valeurs par défaut de paramètres

Il peut être commode de spécifier des valeurs par défaut aux paramètres d'une fonction. Cela est similaire aux valeurs initiales par défaut des membres d'une structure.

Certains paramètres de fonctions sont la plupart du temps utilisés avec les mêmes valeurs. Par exemple, imaginons une fonction qui affiche les éléments d'un tableau associatif de type

string[string].

. Considérons que cette fonction prenne également les caractères séparateurs en paramètres :

 
Sélectionnez
void afficheTA(in char[] titre,
            in string[string] ta,
            in char[] separateurCle,
            in char[] separateurElement)
{
    writeln("-- ", titre, " --");
 
    auto cles = ta.keys.sort;
 
    foreach (i, cle; cles) {
        // Pas de séparateur avant le premier élément
        if (i != 0) {
            write(separateurElement);
        }
 
        write(cle, separateurCle, ta[cle]);
    }
 
    writeln();
 
}

Cette fonction est appelée ci-dessous avec « : » comme séparateur de clé et « , » comme séparateur d'éléments :

 
Sélectionnez
string[string] dictionnaire = [ "bleu":"blue", "rouge":"red", "gris":"gray" ];
afficheTA("Dictionnaire des couleurs", dictionnaire, ":", ", ");

Ce qui produit le résultat suivant :

 
Sélectionnez
-- Dictionnaire des couleurs --
bleu:blue, gris:gray, rouge:red

Si les séparateurs sont presque toujours les deux même, ils peuvent être définis avec des valeurs par défaut :

 
Sélectionnez
void afficheTA(in char[] titre,
            in string[string] ta,
            in char[] separateurCle = ":",
            in char[] separateurElement = ", ")
{
    // ...
}

Les paramètres par défaut n'ont pas besoin d'être spécifiés lors de l'appel d'une fonction :

 
Sélectionnez
afficheTA("Dictionnaire des couleurs", dictionnaire); /* ← pas de séparateur spécifié. Les deux paramètres prendront leur valeur par défaut */

Au besoin, les valeurs des paramètres peuvent toujours être spécifiées, pas nécessairement dans leur intégralité :

 
Sélectionnez
afficheTA("Dictionnaire des couleurs", dictionnaire, "=");

Ce qui produit le résultat suivant :

 
Sélectionnez
-- Dictionnaire des couleurs --
bleu=blue, gris=gray, rouge=red

L'appel ci-dessous spécifie les deux paramètres :

 
Sélectionnez
afficheTA("Dictionnaire des couleurs", dictionnaire, "=", "\n");

Le résultat :

 
Sélectionnez
-- Dictionnaire des couleurs --
bleu=blue
gris=gray
rouge=red

Les valeurs par défaut ne peuvent être définies que pour les paramètres en fin de liste.

52-2. Mots-clés spéciaux comme arguments par défaut

Les mots-clés suivants fonctionnent comme des littéraux ayant des valeurs dépendant de l'endroit où ils apparaissent dans le code :

  • MODULE : nom du module ;
  • FILE : nom du fichier source ;
  • LINE : numéro de la ligne ;
  • FUNCTION : nom de la fonction ;
  • PRETTY_FUNCTION : signature complète de la fonction.

Bien qu'ils puissent être utiles n'importe où dans le code, ils fonctionnement différemment quand ils sont utilisés en tant qu'arguments par défaut. Quand ils sont utilisés dans du code classique, leurs valeurs font référence à l'endroit où ils apparaissent dans le code :

 
Sélectionnez
import std.stdio;
 
void fonct(int parametre) {
    writefln("Dans la fonction%s du fichier %s, ligne %s.",
            __FUNCTION__, __FILE__, __LINE__);    // ← ligne 6
}
 
void main() {
    fonct(42);
}

La ligne 6 rapportée est dans la fonction.

Sortie :

 
Sélectionnez
Dans la fonction test.fonct du fichier test.d, ligne 6.

Cependant, il est parfois plus intéressant de déterminer la ligne d'où la fonction a été appelée plutôt que l'endroit où elle a été définie. Quand ces mots-clés spéciaux sont donnés en paramètres par défaut, leur valeur fait référence à l'endroit où la fonction est appelée :

 
Sélectionnez
import std.stdio;
 
void fonct(int parametre,
        string nomFonction = __FUNCTION__,
        string fichier = __FILE__,
        size_t line = __LINE__) {
    writefln("Appelé depuis la fonction %s du fichier %s, ligne %s.",
            nomFonction, fichier, ligne);
}
 
void main() {
    fonct(42);    // ← ligne 14
}

Cette fois, les mots-clés spéciaux font référence à main(), l'appelant de la fonction.

Sortie :

 
Sélectionnez
Appelé depuis la fonction test.main du fichier test.d, ligne 14.

En plus des mots-clés précédents, il y a aussi les mots-clés suivants, qui prennent leur valeur dépendant du compilateur et du moment de la compilation :

  • DATE : date de la compilation ;
  • TIME : moment de la compilation (heures, minutes, secondes) ;
  • TIMESTAMP : date et moment de la compilation ;
  • VENDOR : auteurs du compilateur (par ex. Digital Mars D) ;
  • VERSION : version du compilateur, en tant qu'entier (par ex. la valeur 2069 pour la version 2.069)

52-3. Fonctions variadiques

Malgré les apparences, les valeurs par défaut des paramètres ne changent pas le nombre de paramètres reçus par une fonction. Par exemple, même si certains paramètres peuvent se voir assigner leur valeur par défaut, afficheTA() prend toujours quatre paramètres et les utilise selon son implémentation.

Les fonctions variadiques quant à elles peuvent s'appeler avec un nombre d'arguments non défini. Nous nous sommes déjà servis de cette fonctionnalité avec des fonctions comme writeln(). writeln() peut être appelée avec un nombre quelconque de paramètres :

 
Sélectionnez
writeln("hello", 7, "world", 9.8); /* et autant d'autres arguments que nécessaire */

Il y a quatre manières de définir des fonctions variadiques en D :

  • celle qui ne fonctionne que pour les fonctions marquées comme extern (C). Cette fonctionnalité définit une variable cachée _argptr qui est utilisée pour accéder aux paramètres. Ce livre ne la traite pas parce qu'elle n'est pas sûre ;
  • la manière des fonctions D normales, qui utilise également la variable cachée _argptr ainsi que la variable _arguments, cette dernière étant de type TypeInfo[], ce livre ne la couvre pas non plus, car elle requiert la notion de pointeurs que nous n'avons pas encore vue et qu'elle peut être utilisée de manière non sûre ;
  • une fonctionnalité sûre, qui nécessite que tous les paramètres soient du même type. C'est de cela que nous allons parler dans cette section ;
  • un nombre non spécifié de paramètres templates. Cette fonctionnalité sera expliquée plus tard dans le chapitre sur les modèles (templates).

Les paramètres des fonctions variadiques sont passés sous la forme d'une tranche. Les fonctions variadiques sont définies avec un seul paramètre d'un certain type de tranche immédiatement suivi par les caractères … :

 
Sélectionnez
double somme(in double[] nombres ...)
{
    double resultat = 0.0;
 
    foreach (nombre; nombres) {
        resultat += nombre;
    }
 
    return resultat;
}

Cette définition donne une fonction somme variadique, c'est-à-dire qu'elle peut recevoir un nombre quelconque d'arguments du moment qu'ils sont du type double ou implicitement convertibles en double :

 
Sélectionnez
writeln(somme(1.1, 2.2, 3.3));

Le seul paramètre tranche et les caractères … représentent tous les arguments. Par exemple, la tranche contiendrait 5 éléments si la fonction était appelée avec 5 doubles.

En fait, les arguments peuvent aussi être passés sous la forme d'une seule tranche :

 
Sélectionnez
writeln(somme([1.1, 2.2, 3.3])); // identique au code précédent

Les fonctions variadiques peuvent aussi avoir des paramètres obligatoires, qui doivent être définis en premier dans la liste des paramètres. Par exemple, la fonction suivante affiche un nombre quelconque de paramètres entre parenthèses. Bien que la fonction soit ouverte quant au nombre d'éléments, elle requiert que les parenthèses soient toujours spécifiées :

 
Sélectionnez
char[] parentheser(
  in char[] ouverture,  // ← Les deux premiers paramètres doivent toujours être
  in char[] fermeture,  //   spécifiés quand la fonction est appelée.
  in char[][] mots ...) { // ← Pas obligatoire
 
    char[] resultat;
 
    foreach (mot; mots) {
        resultat ~= ouverture;
        resultat ~= mot;
        resultat ~= fermeture;
    }
 
    return resultat;
}

Les deux premiers paramètres sont obligatoires :

 
Sélectionnez
parentheser("{"); // ← ERREUR de compilation

À partir du moment où les deux premiers paramètres sont spécifiés, les autres sont optionnels :

 
Sélectionnez
writeln(parentheser("{", "}","pomme", "poire", "banane"));

En sortie :

 
Sélectionnez
{pomme}{poire}{banane}

52-4. Les paramètres des fonctions variadiques ont une durée de vie courte

La tranche qui est automatiquement générée pour un paramètre variadique pointe vers un tableau temporaire qui a une durée de vie courte. Ce fait n'importe pas si la fonction n'utilise les arguments que pendant son exécution. Cependant, si la fonction gardait une tranche de ces éléments pour usage ultérieur, ce serait un bogue :

 
Sélectionnez
int[] nombresPourUsageUlterieur;
 
void foo(int[] nombres...) {
    nombresPourUsageUlterieur = nombres;    // ← BOGUE
}
 
struct S {
    string[] nomsPourUsageUlterieur;
 
    void foo(string[] noms...) {
        nomsPourUsageUlterieur = noms;    // ← BOGUE
    }
}
 
void bar() {
    foo(1, 10, 100);  /* Le tableau temporaire [ 1, 10, 100 ] n'est
                       * plus valide après cet endroit. */
 
    auto s = S();
    s.foo("bonjour", "le", "monde");  /* Le tableau temporaire
                                       * [ "bonjour", "le", "monde" ] n'est
                                       * plus valide après cet endroit. */
 
    // ...
}
 
void main() {
    bar();
}

La fonction foo() aussi bien que la fonction membre S.foo() sont boguées parce qu'elles stockent des tranches vers des tableaux temporaires générés automatiquement qui vivent sur la pile. Ces tableaux ne sont valides que pendant l'exécution des fonctions variadiques.

Pour cette raison, si une fonction a besoin de stocker une tranche vers les éléments d'un paramètre variadique, elle doit d'abord faire une copie de ces éléments :

 
Sélectionnez
void foo(int[] nombres...) {
    nombresPourUsageUlterieur = nombres.dup;    // ← correct
}
 
// ...
 
    void foo(string[] noms...) {
        nomsPourUsageUlterieur = noms.dup;    // ← correct
    }

Cependant, comme les fonctions variadiques peuvent aussi être appelées avec des tranches de tableaux classiques, copier les éléments ne serait pas nécessaire dans ces cas.

Une solution qui serait à la fois correcte et efficace est de définir deux fonctions qui ont le même nom, une prenant un paramètre variadique et l'autre prenant une tranche classique. Si l'appelant passe un nombre variable d'arguments, la version variadique de la fonction est appelée, et si l'appelant passe une tranche classique, la fonction qui prend une tranche est appelée :

 
Sélectionnez
int[] nombresPourUsageUlterieur;
 
void foo(int[] nombres...) {
    /* Since this is the variadic version of foo(), we must
    * first take a copy of the elements before storing a
    * slice to them. */
    nombresPourUsageUlterieur = nombres.dup;
}
 
void foo(int[] nombres) {
    /* Since this is the non-variadic version of foo(), we can
    * store the slice as is. */
    nombresPourUsageUlterieur = nombres;
}
 
struct S {
    string[] nomsPourUsageUlterieur;
 
    void foo(string[] noms...) {
        /* Comme il s'agit de la version variadique de S.foo(), nous
        * devons d'abord faire une copie des éléments avant
        * de stocker une tranche vers eux. */
        nomsPourUsageUlterieur = noms.dup;
    }
 
    void foo(string[] noms) {
        /* Comme il s'agit de la version non-variadique de S.foo(),
        * nous pouvons stocker la tranche telle quelle. */
        nomsPourUsageUlterieur = noms;
    }
}
 
void bar() {
    // Cet appel est propagé vers la fonction variadique.
    foo(1, 10, 100);
 
    // Cet appel est propagé vers la fonction qui prend une tranche.
    foo([ 2, 20, 200 ]);
 
    auto s = S();
 
    // Cet appel est propagé vers la fonction variadique.
    s.foo("salut", "le", "monde");
 
    // Cet appel est propagé vers la fonction qui prend une tranche.
    s.foo([ "salut", "la", "lune" ]);
 
    // ...
}
 
void main() {
    bar();
}

52-5. Exercice

Considérez que l'énumération suivante est définie :

 
Sélectionnez
enum Operation { addition, soustraction, multiplication, division }

Considérez également qu'il existe une structure qui représente le résultat d'un calcul et ses deux opérandes :

 
Sélectionnez
struct Calcul {
    Operation op;
    double premier;
    double second;
}

Par exemple, l'objet Calcul(Operation.division, 7.7, 8.8) représenterait la division de 7.7 par 8.8.

Concevez une fonction qui reçoive un nombre non spécifié de ces objets struct, calcule le résultat de chacun et retourne ceux-ci comme une tranche de type double[].

Par exemple, il doit être possible d'appeler cette fonction comme ceci :

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

Le résultat de ce code doit être le suivant :

 
Sélectionnez
[3.3, -1.1, 36.3, 0.875]

La solution.Nombre variable de paramètres - Correction


précédentsommairesuivant