relecture de Stéphane

This commit is contained in:
Raphaël Jakse 2016-04-02 11:43:31 +02:00
parent 151e23dbbb
commit 1533cd6060
8 changed files with 714 additions and 473 deletions

View File

@ -2,24 +2,25 @@
title = "Conversions de Types, [c cast]"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Les variables doivent être compatibles avec les expression dans lesquelles apparaissent. Comme vous l'avez peut-être déjà remarqué dans les programmes que nous avons vus jusqu'à maintenant, D est un langage [* statiquement typé], ce qui veut dire que la compatibilité des types est vérifiée lors de la compilation.
Les variables doivent être compatibles avec les expressions dans lesquelles elles apparaissent. Comme vous l'avez peut-être déjà remarqué dans les programmes que nous avons vus jusqu'à maintenant, D est un langage [* statiquement typé], ce qui veut dire que la compatibilité des types est vérifiée lors de la compilation.
Toutes les expressions que nous avons écrites jusqu'à maintenant ont eu des types compatibles parce que sinon, le code aurait été rejeté par le compilateur. Ce qui suit est un exemple de code qui a des types incompatibles :
Toutes les expressions que nous avons écrites jusqu'à maintenant avaient des types compatibles parce que dans le cas contraire le code aurait été rejeté par le compilateur. Ce qui suit est un exemple de code qui a des types incompatibles~ :
[code=d <<<
char[] tranche;
writeln(tranche + 5); // ← ERREUR de compilation
>>>]
Le compilateur rejette le code à cause de l'incompatibilité des types [c char~[~]] et [c int] pour l'opération d'addition :
Le compilateur rejette le code à cause de l'incompatibilité des types [c char~[~]] et [c int] pour l'opération d'addition~ :
[output <<<
Error: incompatible types for ((tranche) + (5)): 'char[]' and 'int'
>>>]
L'incompatibilité des types ne veut pas dire que les types sont différents ; différents types peuvent en effet être utilisés dans les expressions en toute sécurité. Par exemple, une variable [c int] peut être utilisée à la place d'une valeur [c double] sans problème :
L'incompatibilité des types ne veut pas dire que les types sont différents~ ; des types différents peuvent en effet être utilisés dans les expressions en toute sécurité. Par exemple, une variable [c int] peut être utilisée à la place d'une valeur [c double] sans problème~ :
[code=d <<<
double somme = 1.25;
@ -27,31 +28,31 @@ L'incompatibilité des types ne veut pas dire que les types sont différents ;
somme += increment;
>>>]
Même si [c somme] et [c increment] sont de types différents, le code ci-avant est correct parce qu'incrémenter une variable [c double] avec valeur [c int] est légal.
Même si [c somme] et [c increment] sont de types différents, ce code est correct parce qu'incrémenter une variable [c double] avec une valeur [c int] est légal.
[ = Conversion automatique des types
Les conversions automatiques de types sont aussi appelées [* conversions implicites de types].
Même si [c double] et [c int] sont des types compatibles dans l'expression ci-avant, l'opération d'addition doit toujours être évaluée comme un type spécifique au niveau du microprocesseur. Comme nous l'avons vu dans le [[doc:virgule_flottante | chapitre sur les virgules flottantes]], le type 64-bits [c double] est [* plus large] que le type 32-bits [c int]. De plus, toute valeur qui peut être représentée dans une variable [c int] peut être représentée dans une variable [c double].
Même si [c double] et [c int] sont des types compatibles dans l'expression précédente, l'opération d'addition doit toujours être évaluée comme un type spécifique au niveau du microprocesseur. Comme nous l'avons vu dans le [[doc:virgule_flottante | chapitre sur les virgules flottantes]], le type 64 bits [c double] est [* plus large] que le type 32 bits [c int]. De plus, toute valeur qui peut être représentée dans une variable [c int] peut être représentée dans une variable [c double].
Quand le compilateur rencontre une expression qui implique des types incompatibles, il convertit d'abord les parties de l'expression vers un type commun et évalue ensuite l'expression entière. Les conversions automatiques qui sont effectuées par le compilateur se font dans une direction qui évite toute perte de données. Par exemple, [c double] peut contenir toute valeur que [c int] peut contenir mais l'inverse n'est pas vrai. L'opération [c +=] ci-avant peut fonctionner parce que toute valeur [c int] peut être convertie vers [c double] en toute sécurité.
Quand le compilateur rencontre une expression qui implique des types incompatibles, il convertit d'abord les parties de l'expression vers un type commun et évalue ensuite l'expression entière. Les conversions automatiques qui sont effectuées par le compilateur se font dans une direction qui évite toute perte de données. Par exemple, [c double] peut contenir toute valeur que [c int] peut contenir mais l'inverse n'est pas vrai. Dans l'exemple précédent, l'opération [c +=] peut fonctionner parce que toute valeur [c int] peut être convertie vers [c double] en toute sécurité.
La valeur résultant d'une conversion automatique est toujours une variable anonyme et elle est souvent temporaire. La valeur originale ne change pas. Par exemple, la conversion automatique lors de l'opération [c +=] ci-avant ne change pas le type de [c increment] ; celle-ci reste un [c int]. Par contre, une valeur temporaire de type [c double] est construite à partir de la valeur d'[c increment]. La conversion qui se déroule en arrière-plan est équivalente au code suivant :
La valeur résultant d'une conversion automatique est toujours une variable anonyme et elle est souvent temporaire. La valeur originale ne change pas. Par exemple, dans notre cas la conversion automatique lors de l'opération [c +=] ne change pas le type de [c increment]~ ; celui-ci reste un [c int]. Par contre, une valeur temporaire de type [c double] est construite à partir de la valeur d'[c increment]. La conversion qui se déroule en arrière-plan est équivalente au code suivant~ :
[code=d <<<
{
double $(I une_valeur_double_anonyme) = increment;
somme += $(I une_valeur_double_anonyme);
double une_valeur_double_anonyme = increment;
somme += une_valeur_double_anonyme;
}
>>>]
Le compilateur convertie la valeur [c int] vers une valeur temporaire [c double] et utilise cette valeur dans l'opération. Dans cet exemple, la variable temporaire vie uniquement pendant l'opération [c +=].
Le compilateur convertit la valeur [c int] vers une valeur temporaire [c double] et utilise cette valeur dans l'opération. Dans cet exemple, la variable temporaire vit uniquement pendant l'opération [c +=].
Les conversions automatiques ne sont pas limitées au opérations arithmétiques. Il y a d'autres cas où les types sont convertis vers d'autres types automatiquement. Tant que les conversions sont correctes, le compilateur profite des conversions de types pour utiliser les valeurs dans les expressions. Par exemple, une valeur [c byte] peut être utilisée pour un paramètre [c int] :
Les conversions automatiques ne sont pas limitées aux opérations arithmétiques. Il y a d'autres cas où les types sont convertis vers d'autres types automatiquement. Tant que les conversions de types sont correctes, le compilateur en profite pour utiliser les valeurs dans les expressions. Par exemple, une valeur [c byte] peut être passée en argument pour un paramètre [c int]~ :
[code=d <<<
void func(int nombre)
void fonc(int nombre)
{
// ...
}
@ -59,17 +60,17 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
void main()
{
byte petiteValeur = 7;
func(petiteValeur); // conversion automatique
fonc(petiteValeur); // conversion automatique
}
>>>]
Dans le code ci-avant, une valeur [c int] temporaire est d'abord construite et ensuite la fonction est appelée avec cette valeur.
Dans ce code, une valeur [c int] temporaire est d'abord construite et ensuite la fonction est appelée avec cette valeur.
[ = Promotions entières
Les valeurs des types qui sont dans la colonne de gauche du tableau suivant ne font jamais partie d'expressions arithmétiques en tant que tels. Chaque type est d'abord promu vers le type de la colonne à droite du tableau.
|= From |= To |
|= De |= Vers |
| [c bool] | [c int] |
| [c byte] | [c int] |
| [c ubyte] | [c int] |
@ -79,9 +80,9 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
| [c wchar] | [c int] |
| [c dchar] | [c uint] |
Les promotions entières sont également appliquées au valeurs [c enum].
Les promotions entières sont également appliquées aux valeurs [c enum].
Les raisons d'être des promotions entières sont aussi historiques (où les règles viennent du C) que techniques, le type arithmétique naturel du processeur étant [c int]. Par exemple, même si les deux variables suivantes sont [c ubyte], l'opération d'addition n'est effectuée qu'après que les deux valeurs aient été promues en [c int] :
Les raisons d'être des promotions entières sont aussi historiques (où les règles viennent du C) que techniques, le type arithmétique naturel du processeur étant [c int]. Par exemple, même si les deux variables suivantes sont [c ubyte], l'opération d'addition n'est effectuée qu'après que les deux valeurs aient été promues en [c int]~ :
[code=d <<<
ubyte a = 1;
@ -97,27 +98,27 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
>>>]
Notez que les types respectifs des variables [c a] et [c b] ne changent pas ; seules leurs valeurs sont temporairement promues en [c int] pour la durée de l'opération d'addition.
Notez que les types respectifs des variables [c a] et [c b] ne changent pas~ ; seules leurs valeurs sont temporairement promues en [c int] pour la durée de l'opération d'addition.
]
[ = Conversions arithmétiques
Il y a d'autres règles de conversions qui sont appliquées pour les opérations arithmétiques. En général, les conversions arithmétiques automatiques sont appliquées dans la direction sûre : du types le plus [* étroit] au type le plus [* large]. Même si cette règle est facile à retenir et est correcte dans la plupart des cas, les règles de conversions automatiques sont très compliquées et dans les cas de conversions signé vers non signé, sont sujettes à risques de bogues.
Il y a d'autres règles de conversions qui sont appliquées pour les opérations arithmétiques. En général, les conversions arithmétiques automatiques sont appliquées dans la direction sûre~ : du type le plus [* étroit] au type le plus [* large]. Même si cette règle est facile à retenir et est correcte dans la plupart des cas, les règles de conversions automatiques sont très compliquées et, dans les cas de conversion signé vers non signé, sont de potentielles causes de bogues.
Les règles de conversions arithmétiques sont les suivantes :
Les règles de conversions arithmétiques sont les suivantes~ :
# Si l'une des valeurs est [c real], alors l'autre valeurs est convertie en [c real].
# Sinon, si l'une des valeurs est [c double], alors l'autre valeurs est convertie en [c double].
# Sinon, si l'une des valeurs est [c float], alors l'autre valeurs est convertie en [c float].
# Sinon, les promotions entières sontt appliquées selon le tableau ci-avant, et les règles suivantes sont ensuite suivies :
# Si l'une des valeurs est [c real], alors l'autre valeur est convertie en [c real].
# Sinon, si l'une des valeurs est [c double], alors l'autre valeur est convertie en [c double].
# Sinon, si l'une des valeurs est [c float], alors l'autre valeur est convertie en [c float].
# Sinon, les promotions entières sont appliquées selon le tableau de la section précédente, et les règles suivantes sont ensuite appliquées~ :
# Si les deux types sont les mêmes, alors aucune autre étape n'est nécessaire
# Si le type signé est plus large que le type non signé, alors la valeurs non signée est convertie vers le type signé
# Sinon le type signé est converti vers le type non signé
# Si les deux types sont les mêmes, alors aucune autre étape n'est nécessaire.
# Si le type signé est plus large que le type non signé, alors la valeur non signée est convertie vers le type signé.
# Sinon le type signé est converti vers le type non signé.
Malheureusement, la dernière règle ci-avant peut être à l'origine de bogues subtiles :
Malheureusement, cette dernière règle peut être à l'origine de bogues subtils~ :
[code=d <<<
int a = 0;
@ -126,18 +127,18 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
writeln(a - b + c); // Résultat surprenant !
>>>]
Étonnement, le résultat n'est pas [c -1] mais [c size_t.max] :
Étonnement, le résultat n'est pas [c -1] mais [c size_t.max]~ :
[output <<<
18446744073709551615
>>>]
Même si l'on s'attendrait à ce que [c 0 - 1 + 0] donne [c -1], selon les règles ci-avant, le type de l'expression entière est [c size_t] et non [c int] ; et comme [c size_t] ne peut pas représenter des valeurs négatives, le résultat soupasse et devient size_t.max.
Même si l'on s'attendrait à ce que [c 0 - 1 + 0] donne [c -1], selon les règles ci-avant, le type de l'expression entière est [c size_t] et non [c int] ; et comme [c size_t] ne peut pas représenter des valeurs négatives, le résultat soupasse et devient [c size_t.max].
]
[ = Les conversions [c const]
[ = Conversions [c const]
Comme nous l'avons vu précédemment dans le [[doc:parametres_de_fonctions | chapitre sur les paramètres de fonctions], les types références peuvent être automatiquement converties vers le [c const] du même type. Les conversions vers [c const] sont sûres parce que la largeur du type ne change pas et [ const] est une promesse de ne pas modifier la variable :
Comme nous l'avons vu précédemment dans le [[doc:parametres_de_fonctions | chapitre sur les paramètres de fonctions], les types référence peuvent être automatiquement convertis vers le [c const] du même type. Les conversions vers [c const] sont sûres parce que la largeur du type ne change pas et [c const] est une promesse de ne pas modifier la variable~ :
[code=d <<<
dchar[] entre_accolades(const dchar[] texte)
@ -153,9 +154,9 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
}
>>>]
La variable mutable [c bienvenue] ci-avant est automatiquement convertie vers un [c const dchar~[~]] lorsqu'il est passé à la fonction [c entre_accolades].
La variable mutable [c bienvenue] est automatiquement convertie vers un [c const dchar~[~]] lorsqu'elle est passée à la fonction [c entre_accolades].
Comme nous l'avons aussi vu précédemment, la conversion inverse n'est pas automatique. Une référence [c const] n'est pas automatiquement convertie ves une référence mutable :
Comme nous l'avons aussi vu précédemment, la conversion inverse n'est pas automatique. Une référence [c const] n'est pas automatiquement convertie vers une référence mutable~ :
[code=d <<<
dchar[] entre_accolades(const dchar[] texte)
@ -165,19 +166,19 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
}
>>>]
Notez que cette partie concerne uniquement les références ; comme les variables de types valeur sont copiées, il n'est de toute façon pas possible d'assigner l'original à travers la copie :
Notez que cette partie concerne uniquement les références~ ; comme les variables de types valeur sont copiées, il n'est de toute façon pas possible d'assigner l'original à travers la copie~ :
[code=d <<<
const int nombreDeCoins = 4;
int laCopie = nombreDeCoins; // compile (type valeur)
>>>]
La conversion de [c const] vers mutable ci-avant est légale parce que la copie n'est pas une référence vers l'original.
Cette conversion de [c const] vers mutable est légale parce que la copie n'est pas une référence vers l'original.
]
[ = Conversion [c immutable]
[ = Conversions [c immutable]
[c immutable] indiquant qu'une variable ne peut jamais changer, aucune conversion depuis ou vers [c immutable] n'est automatique :
[c immutable] indiquant qu'une variable ne peut jamais changer, aucune conversion depuis ou vers [c immutable] n'est automatique~ :
[code=d <<<
string a = "salut"; // caractères immuables
@ -185,7 +186,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
string c = b; // Erreur de compilation
>>>]
Comme avec les conversions de [c const] ci-avant, cette partie concerne uniquement les types référence. Comme les variables de types valeur sont de toute façon copiées, les conversions depuis et vers [c immutable] sont valides :
Comme pour la section précédente (conversions de [c const]), cette partie ne concerne que les types référence. Comme les variables de types valeur sont de toute façon copiées, les conversions depuis et vers [c immutable] sont valides~ :
[code=d <<<
immutable a = 10;
@ -193,35 +194,35 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
>>>]
]
[ = conversion [c enum]
[ = Conversions [c enum]
Comme nous l'avons vu dans le [[doc:enum | chapitre sur les énumérations]], [c enum] définit des constantes nommées :
Comme nous l'avons vu dans le [[doc:enum | chapitre sur les énumérations]], [c enum] définit des constantes nommées~ :
[code=d <<<
enum Couleur { piques, coeurs, carreaux, trefles }
enum Couleur { pique, coeur, carreau, trefle }
>>>]
Souvenez-vous que comme aucune valeur n'est spécifié explicitement, les valeurs des membres de l'énumération commencent par zéro et sont automatiquement incrémentées par 1. Ainsi, la valeur de [c Couleur.trefles] est [c 3].
Souvenez-vous que comme aucune valeur n'est spécifiée explicitement, les valeurs des membres de l'énumération commencent par zéro et sont automatiquement incrémentées de 1 en 1. Ainsi, la valeur de [c Couleur.trefle] est [c 3].
Les valeurs [c enum] sont automatiquement converties vers des types entiers. Par exemple, la valeur de [c Couleur.coeurs] est comprise comme 1 dans le calcul suivant et le résultat est 11 :
Les valeurs [c enum] sont automatiquement converties vers des types entiers. Par exemple, la valeur de [c Couleur.coeur] est comprise comme 1 dans le calcul suivant et le résultat est 11~ :
[code=d <<<
int resultat = 10 + Couleur.coeurs;
int resultat = 10 + Couleur.coeur;
assert(resultat == 11);
>>>]
La conversion inverse n'est pas automatique : les valeurs entières ne sont pas automatiquement converties vers les valeurs [c enum] correspondantes. Par exemple, on pourrait s'attendre que la variable [c couleur] ci-après prenne pour valeur [c Couleur.carreaux], mais le code ne peut pas être compilé :
La conversion inverse n'est pas automatique~ : les valeurs entières ne sont pas automatiquement converties vers les valeurs [c enum] correspondantes. Par exemple, on pourrait s'attendre que la variable [c couleur] ci-dessous prenne pour valeur [c Couleur.carreau], mais le code ne peut pas être compilé~ :
[code=d <<<
Couleur couleur = 2; // ERREUR de compilation
>>>]
Comme nous le verrons juste après, les conversions depuis les entiers vers les valeurs [c enum] sont toujours possible mais elle doivent être explicites.
Comme nous le verrons juste après, les conversions depuis les entiers vers les valeurs [c enum] sont néanmoins possibles mais elle doivent être explicites.
]
[ = Conversions booléennes
[c false] et [c true] sont automatiquement converties vers 0 et 1, respectivement :
[c false] et [c true] sont automatiquement convertis vers 0 et 1, respectivement~ :
[code=d <<<
int a = false;
@ -231,7 +232,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
assert(b == 1);
>>>]
La conversion inverse est également automatique mais seulement pour deux valeurs spéciales. Les littéraux 0 et 1 sont convertis automatiquement vers [c false] et [c true], respectivement :
La conversion inverse est également automatique mais seulement pour deux valeurs spéciales. Les littéraux 0 et 1 sont convertis automatiquement vers [c false] et [c true], respectivement~ :
[code=d <<<
bool a = 0;
@ -241,7 +242,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
assert(b); // true
>>>]
Les autres valeurs littérales ne peuvent pas être converties vers [c bool] automatiquement :
Les autres valeurs littérales ne peuvent pas être converties vers [c bool] automatiquement~ :
[code=d <<<
bool b = 2; // ERREUR de compilation
@ -250,7 +251,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
[ = Conversions automatiques vers [c bool] dans les instructions conditionnelles
Certaines instructions utilisent des expressions logiques : [c if], [c while], etc. Pour les expressions logiques de telles instructions, en plus de [c bool], la plupart des autres types peuvent également être utilisés. La valeur zéro est automatiquement convertie vers [c false] et les valeurs non nulles sont automatiquement converties vers [c true].
Certaines instructions utilisent des expressions logiques~ : [c if], [c while], etc. Pour les expressions logiques de telles instructions, en plus de [c bool], la plupart des autres types peuvent également être utilisés. La valeur zéro est automatiquement convertie vers [c false] et les valeurs non nulles sont automatiquement converties vers [c true].
[code=d <<<
int i;
@ -264,7 +265,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
}
>>>]
De manière similaire, les références nulles ([c null]) sont automatiquement converties vers [c false] et les références non nulles sont automatiquement converties vers [c true]. Cela facilite la vérification qu'une référence n'est pas nulle avant de s'en servir :
De manière similaire, les références nulles ([c null]) sont automatiquement converties vers [c false] et les références non nulles sont automatiquement converties vers [c true]. Cela facilite la vérification qu'une référence n'est pas nulle avant de s'en servir~ :
[code=d <<<
int[] a;
@ -274,39 +275,40 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
// ... non null; 'a' peut être utilisé...
} else {
// ... null; 'a' peut ne peut pas être utilisé...
// ... null; 'a' ne peut pas être utilisé...
}
>>>]
]
[ = Conversions de types explicites
Comme nous l'avons vu ci-avant, il y a des cas où les conversions automatiques ne sont pas possibles :
Comme nous venons de le voir, il y a des cas où les conversions automatiques ne sont pas possibles~ :
- les conversions depuis des types plus larges vers des types plus restreints
- les conversions depuis [c const] vers mutable
- les conversions [c immutable]
- les conversions depuis les entiers vers les valeurs [c enum]
- les conversions depuis des types plus larges vers des types plus restreints~ ;
- les conversions depuis [c const] vers mutable~ ;
- les conversions [c immutable]~ ;
- les conversions depuis les entiers vers les valeurs [c enum].
S'il sait qu'une telle conversion est sûre, le programmeur peut explicitement demander une conversion de types avec l'une des trois méthodes suivantes~ :
- en appelant la fonction [c std.conv.to]~ ;
- en appelant la fonction [c std.exception.assumeUnique]~ ;
- en utilisant l'opérateur [c cast].
Si on sait qu'une telle conversion est sûre, le programmeur peut explicitement demander pour une conversion de types avec l'une des trois méthodes suivantes :
- en appelant la fonction [c std.conv.to]
- en appelant la fonction [c std.exception.assumeUnique]
- en utilisant l'opérateur [c cast]
[ = La fonction [c to] pour la plupart des conversions
La fonction [c to], que nous avons déjà utilisé surtout pour convertir des valeurs vers [c string], peut en fait être utilisée pour beaucoup d'autres types. Sa syntaxe complète est la suivante :
La fonction [c to], que nous avons déjà utilisée surtout pour convertir des valeurs vers [c string], peut en fait être utilisée pour beaucoup d'autres types. Sa syntaxe complète est la suivante~ :
[code=d <<<
to!($(I TypeDestination))(valeur)
to!(TypeDestination)(valeur)
>>>]
Étant un modèle, on peut utiliser la fonction [c to] avec la notation raccourcie des paramètres de modèles : quand le type de destination ne consiste qu'en un seul [i=FIXME token] (en général, qu'un seul mot), elle peut être appelée sans la première paire de parenthèses :
Puisque c'est un modèle, on peut utiliser la fonction [c to] avec la notation raccourcie des paramètres de modèles~ : quand le type de destination ne consiste qu'en un seul [i=FIXME token] (en général, qu'un seul mot), elle peut être appelée sans la première paire de parenthèses :
[code=d <<<
to!$(I TypeDestination)(valeur)
to!TypeDestination(valeur)
>>>]
Le programme suivant essaie de convertir une valeur [c double] vers [c short] et une chaîne vers [c int] :
Le programme suivant essaie de convertir une valeur [c double] vers [c short] et une chaîne vers [c int]~ :
[code=d <<<
void main()
@ -318,7 +320,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
}
>>>]
Comme toutes les valeurs [c double] ne peuvent pas être représentées par [c short] et comme toute chaîne ne peut pas être représentée comme un entier, ces conversions ne sont pas automatiques. Quand le programmeur sait que les conversions sont en fait sûres ou que les conséquences d'une mauvaise conversion sont acceptables, les types peuvent être converties par [c to()] :
Comme toutes les valeurs [c double] ne peuvent pas être représentées par [c short] et comme toute chaîne ne peut pas être représentée comme un entier, ces conversions ne sont pas automatiques. Quand le programmeur sait que les conversions sont en fait sûres ou que les conséquences d'une mauvaise conversion sont acceptables, les types peuvent être convertis par [c to()]~ :
[code=d <<<
import std.conv;
@ -337,27 +339,27 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
Notez que puisque [c short] ne peut pas représenter les valeurs fractionnaires, la valeur convertie est -1.
La fonction [c to] est sûre : Elle lève une exception quand une conversion n'est pas possible.
La fonction [c to] est sûre~ : elle lève une exception quand une conversion n'est pas possible.
]
[ = [c assumeUnique()] pour des conversions [c immutable] rapides
La fonction [c to] peut également effectuer des conversions [c immutable] :
La fonction [c to] peut également effectuer des conversions [c immutable]~ :
[code=d <<<
int[] tranche = [ 10, 20, 30 ];
auto trancheImmuable = to!(immutable int[])(tranche);
>>>]
Pour garantir que les éléments de [c trancheImmuable] ne changeront jamais, elle ne peut pas partager ses éléments avec [c tranche]. Pour cette raison, la fonction [c to] crée une tranche additionnelle avec les éléments [c immutable] ci-avant. Sinon, les modifications des éléments de [c tranche] entraînerait également la modification des éléments de [c trancheImmuable]. Ce comportement est le même avec la propriété [c .idup] des tableaux.
Pour garantir que les éléments de [c trancheImmuable] ne changeront jamais, elle ne peut pas partager ses éléments avec [c tranche]. Pour cette raison, la fonction [c to] crée une tranche nouvelle avec des éléments [c immutable]. Sinon, les modifications des éléments de [c tranche] entraîneraient également la modification des éléments de [c trancheImmuable]. Ce comportement est le même avec la propriété [c .idup] des tableaux.
Nous pouvons voir que les éléments de [c trancheImmuable] sont en fait des copies des éléments de [c tranche] en regardant l'adresse de leurs premiers éléments :
Nous pouvons voir que les éléments de [c trancheImmuable] sont en fait des copies des éléments de [c tranche] en regardant l'adresse de leurs premiers éléments~ :
[code=d <<<
assert(&(tranche[0]) != &(trancheImmuable[0]));
>>>]
Parfois, cette copie n'est pas nécessaire et peut rallentir le programme de façon négligeable dans certain cas. Par exemple, considérons la fonction suivante qui prend une tranche [c immutable] :
Parfois, cette copie n'est pas nécessaire et peut ralentir le programme de façon négligeable dans certain cas. Par exemple, considérons la fonction suivante qui prend une tranche [c immutable]~ :
[code=d <<<
void calculer(immutable int[] coordonnees)
@ -376,7 +378,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
}
>>>]
Le programme ci-avant ne peut pas être compilé parce que l'appelant ne passe pas un argument [c immutable] à la fonction [c calculer]. Comme nous l'avons vu ci-avant, une tranche [c immutable] peut être créée par la fonction [c to] :
Ce programme ne peut pas être compilé parce que l'appelant ne passe pas un argument [c immutable] à la fonction [c calculer]. Comme nous l'avons vu auparavant, une tranche [c immutable] peut être créée par la fonction [c to]~ :
[code=d <<<
import std.conv;
@ -385,7 +387,7 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
calculer(nombresImmuables); // ← maintenant, compile
>>>]
Cependant, si on n'a besoin de [c nombres] que pour produire cet argument et qu'elle ne sera jamais utilisée après l'appel de la fonction, copier ses éléments vers [c nombresImmuables] n'est pas nécessaire. La fonction [c assumeUnique] rend les éléments d'une tranche immuables sans les copier :
Cependant, si on n'a besoin de [c nombres] que pour produire cet argument et qu'elle ne sera jamais utilisée après l'appel de la fonction, copier ses éléments vers [c nombresImmuables] n'est pas nécessaire. La fonction [c assumeUnique] rend les éléments d'une tranche immuables sans les copier~ :
[code=d <<<
import std.exception;
@ -401,39 +403,47 @@ Même si [c somme] et [c increment] sont de types différents, le code ci-avant
[ = L'opérateur [c cast]
Les fonctions [c to] et [c assumeUnique] utilisent toutes deux l'opérateur de conversion [c cast], qui est également disponible pour le programmeur.
Les fonctions [c to()] et [c assumeUnique()] utilisent toutes deux l'opérateur de conversion [c cast], qui est également disponible pour le programmeur.
L'opérateur [c cast] prend le type de destination entre parenthèses :
L'opérateur [c cast] prend le type de destination entre parenthèses~ :
[code=d <<<
cast(TypeDestination)valeur
>>>]
[c cast] est utile pour les conversions que la fonction [c to] ne peut pas effectuer de façon sûre. [c to] ne réalise pas les conversions suivantes :
[c cast] est utile pour les conversions que la fonction [c to()] ne peut pas effectuer de façon sûre. [c to()] ne réalise pas les conversions suivantes~ :
[code=d <<<
Couleur couleur = to!Couleur(2); // ERREUR de compilation
bool b = to!bool(2); //ERREUR de compilation
>>>]
Parfois, seul le programmeur peut savoir si une valeur entière correspond à une valeur [c enum] valide ou qu'il fait sens de traiter une valeur entière comme un booléen. L'opérateur [c cast] peut être utilisé quand on sait que la conversion est correct selon la logique du programme :
Parfois, seul le programmeur peut savoir si une valeur entière correspond à une valeur [c enum] valide ou s'il fait sens de traiter une valeur entière comme un booléen. L'opérateur [c cast] peut être utilisé quand on sait que la conversion est correcte selon la logique du programme~ :
[code=d <<<
Couleur couleur = cast(Couleur)2;
assert(couleur == Couleur.carreaux);
assert(couleur == Couleur.carreau);
bool b = cast(bool)2;
assert(b);
>>>]
Bien que cela arrive rarement, certaines interfaces de bibliothèques C demandent de stocker une valeur de pointeur dans un type non-pointeur. S'il est garanti que la conversion préserve la bonne valeur, [c cast] peut aussi effectuer la conversion entre des types pointeur et non-pointeur~ :
[code=d <<<
size_t valeurPointeurSauvée = cast(size_t) p;
// ...
int * p2 = cast(int*)valeurPointeurSauvée;
>>>]
]
]
[ = Résumé
- Les conversions de types automatiques sont principalement celles qui se font dans le sens sûre : du type le plus restreint au type le plus large et de mutable vers [c const].
- Les conversions de types automatiques sont principalement celles qui se font dans le sens sûr~ : du type le plus restreint au type le plus large et de mutable vers [c const].
- Cependant, les conversions vers les types non signés peuvent avoir des effets surprenant parce que les types non signés ne peuvent pas avoir de valeur négatives.
- les types [c enum] peuvent automatiquement être convertis vers des valeurs entières mais la conversion inverse n'est pas automatique.
- [c false] et [c true] sont automatiquement convertis vers [c 0] et [c 1], respectivement. De manière similaire, les valeurs nulles (dans le sens « zéro ») sont automatiquement converties vers [c false] et les valeurs non nulles sont automatiquement converties vers [c true].
- Les types [c enum] peuvent automatiquement être convertis vers des valeurs entières mais la conversion inverse n'est pas automatique.
- [c false] et [c true] sont automatiquement convertis vers [c 0] et [c 1], respectivement. De manière similaire, les valeurs nulles (dans le sens «~ zéro~ ») sont automatiquement converties vers [c false] et les valeurs non nulles sont automatiquement converties vers [c true].
- Les références nulles sont automatiquement converties vers [c false] et les références non nulles sont automatiquement converties vers [c true].
- La fonction [c to] couvre la plupart des conversions explicites.
- La fonction [c assumeUnique] convertit vers [c immutable] sans copier.

View File

@ -2,6 +2,7 @@
title = "Nombre variable de paramètres"
partAs = correction
translator = "Olivier Pisano"
proofreader = "Stéphane Goujet"
]
Pour que la fonction ``calculer`` puisse accepter un nombre variable de paramètres, elle doit inclure une tranche de ``Calcul`` suivie de ``...``~ :
@ -11,10 +12,8 @@ Pour que la fonction ``calculer`` puisse accepter un nombre variable de paramèt
{
double[] resultats;
foreach (calcul; calculs)
{
final switch (calcul.op)
{
foreach (calcul; calculs) {
final switch (calcul.op) {
case Operation.addition:
resultats ~= calcul.premier + calcul.second;
break;
@ -44,8 +43,7 @@ Voici le programme complet~ :
enum Operation { addition, soustraction, multiplication, division }
struct Calcul
{
struct Calcul {
Operation op;
double premier;
double second;
@ -55,10 +53,8 @@ Voici le programme complet~ :
{
double[] resultats;
foreach (calcul; calculs)
{
final switch (calcul.op)
{
foreach (calcul; calculs) {
final switch (calcul.op) {
case Operation.addition:
resultats ~= calcul.premier + calcul.second;
break;
@ -79,11 +75,8 @@ Voici le programme complet~ :
return resultats;
}
>>>]
[code=d <<<
void main()
{
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),

View File

@ -2,9 +2,10 @@
title = "Structures"
partAs = correction
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
# Une des conceptions les plus simples est d'utiliser deux membres [c dchar] :
# Un des choix de conception les plus aisés est d'utiliser deux membres [c dchar]~ :
[code=d <<<
struct Carte
@ -14,7 +15,7 @@
}
>>>]
# Ce serait aussi simple que d'afficher les deux membres côtes à côtes :
# Il s'agit simplement d'afficher les deux membres côte à côte~ :
[code=d <<<
void afficherCarte(in Carte carte)
@ -23,7 +24,7 @@
}
>>>]
# En supposant qu'il y a déjà une fonction appelée [c nouvelleCouleur()], [c nouveauJeu()] peut être implémentée en appelant cette fonction pour chaque couleur :
# En supposant qu'il y ait déjà une fonction appelée [c nouvelleCouleur()], [c nouveauJeu()] peut être implémentée en appelant cette fonction pour chaque couleur~ :
[code=d <<<
Carte[] nouveauJeu()
@ -44,7 +45,7 @@
}
>>>]
Le reste du travail peut être accomplis par la fonction [c nouvelleCouleur()] suivante, qui construit la couleur en combinant le caractère de la couleur avec chaque valeur d'une chaîne :
Le reste du travail peut être accompli par la fonction [c nouvelleCouleur()] suivante, qui construit la couleur en combinant le caractère de la couleur avec chaque valeur d'une chaîne~ :
[code=d <<<
Carte[] nouvelleCouleur(in dchar couleur)
@ -70,165 +71,145 @@
return cartesCouleur;
}
>>>]
---
$(P
Note that the functions above take advantage of contract programming to reduce risk of program errors.
)
$(LI
Swapping two elements at random would make the jeu become more and more shuffled at each repetition. Although it is possible to pick the same element by chance, swapping an element with itself does not have any effect other than missing an opportunity towards a more shuffled jeu.
)
---
void shuffle(Carte[] jeu, in int repetition)
{
/* Note: A better algorithm is to walk the jeu from the
* beginning to the end and to swap each element
* with a random one that is picked among the
* elements from that point to the end.
*
* It would be even better to call randomShuffle() from
* the std.algorithm module, which already applies the
* same algorithm. Please read the comment in main() to
* see how randomShuffle() can be used.
*/
foreach (i; 0 .. repetition) {
// Pick two elements at random
immutable first = uniform(0, jeu.length);
immutable second = uniform(0, jeu.length);
swap(jeu[first], jeu[second]);
}
}
---
$(P
The function above calls $(C std.algorithm.swap), which simply swaps the values of its two $(C ref) parameters. It is effectively the equivalent of the following function:
)
---
void mySwap(ref Carte left,
ref Carte right)
{
immutable temporary = left;
left = right;
right = temporary;
}
---
)
$(HR)
$(P
Here is the entire program:
)
---
import std.stdio;
import std.random;
import std.algorithm;
struct Carte
{
dchar couleur;
dchar valeur;
}
void printCard(in Carte card)
{
write(card.couleur, card.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[] newDeck()
out (resultat)
{
assert(resultat.length == 52);
}
body
{
Carte[] jeu;
jeu ~= nouvelleCouleur('♠');
jeu ~= nouvelleCouleur('♡');
jeu ~= nouvelleCouleur('♢');
jeu ~= nouvelleCouleur('♣');
return jeu;
}
void shuffle(Carte[] jeu, in int repetition)
{
/* Note: A better algorithm is to walk the jeu from the
* beginning to the end and to swap each element
* with a random one that is picked among the
* elements from that point to the end.
*
* It would be even better to call randomShuffle() from
* the std.algorithm module, which already applies the
* same algorithm. Please read the comment in main() to
* see how randomShuffle() can be used.
*/
foreach (i; 0 .. repetition) {
// Pick two elements at random
immutable first = uniform(0, jeu.length);
immutable second = uniform(0, jeu.length);
swap(jeu[first], jeu[second]);
}
}
void main()
{
Carte[] jeu = newDeck();
shuffle(jeu, 100);
/* Note: Instead of the shuffle() call above, it would be
* better to call randomShuffle() as in the
* following line:
*
* randomShuffle(jeu);
*/
foreach (card; jeu) {
printCard(card);
write(' ');
}
writeln();
}
---
Macros:
SUBTITLE=Structs
On peut noter que ces fonctions mettent en œuvre la programmation par contrat afin de réduire les risques d'erreurs de programmation.
DESCRIPTION=Programming in D exercise solutions: Structs
# Si l'on échange 2 é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.
KEYWORDS=d programming book tutorial struct exercise solutions
[code=d <<<
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]);
}
}
>>>]
La fonction ci-dessus appelle [c std.algorithm.swap] qui échange tout simplement les valeurs de ses deux parmètres [c ref]. En pratique, c'est l'équivalent de la fonction suivante~ :
[code=d <<<
void mySwap(ref Carte gauche,
ref Carte droite)
{
immutable temporaire = gauche;
gauche = droite;
droite = temporaire;
}
>>>]
Voici le programme au complet~ :
[code=d <<<
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();
}
>>>]

View File

@ -2,9 +2,10 @@
title = "Surcharge de fonctions"
partAs = correction
translator = "Olivier Pisano"
proofreader = "Stéphane Goujet"
]
Les deux surcharges suivantes utilisent les surcharges d'``info()`` existantes :
Les deux surcharges suivantes utilisent les surcharges d'``info()`` existantes~ :
[code=d <<<
void info(in Repas repas)
@ -12,10 +13,10 @@ Les deux surcharges suivantes utilisent les surcharges d'``info()`` existantes 
info(repas.moment);
write('-');
info(ajouterDuree(repas.moment, MomentDeLaJournee(1, 30)));
write(" Repas, Adresse~ : ", repas.adresse);
write(" Repas, adresse : ", repas.adresse);
}
void info(PlanningJournalier planning)
void info(planningDeLaJournee planning)
{
info(planning.reunionMatin);
writeln();
@ -25,7 +26,7 @@ Les deux surcharges suivantes utilisent les surcharges d'``info()`` existantes 
}
>>>]
Voici le programme complet, qui utilise tous ces types :
Voici le programme complet, qui utilise tous ces types~ :
[code=d <<<
import std.stdio;
@ -42,7 +43,7 @@ Voici le programme complet, qui utilise tous ces types :
}
MomentDeLaJournee ajouterDuree(in MomentDeLaJournee debut,
in MomentDeLaJournee duree)
in MomentDeLaJournee duree)
{
MomentDeLaJournee resultat;
@ -70,14 +71,14 @@ Voici le programme complet, qui utilise tous ces types :
write('-');
info(reunion.fin);
writef(" réunion \"%s\" avec %s personnes", reunion.sujet,
writef(" Réunion \"%s\" avec %s personnes", reunion.sujet,
reunion.nombreDeParticipants);
}
struct Repas
{
MomentDeLaJournee moment;
string adresse;
string adresse;
}
void info(in Repas repas)
@ -86,17 +87,17 @@ Voici le programme complet, qui utilise tous ces types :
write('-');
info(ajouterDuree(repas.moment, MomentDeLaJournee(1, 30)));
write(" Repas, Adresse~ : ", repas.adresse);
write(" Repas, adresse : ", repas.adresse);
}
struct PlanningJournalier
struct planningDeLaJournee
{
Reunion reunionMatin;
Repas repas;
Reunion reunionApresMidi;
Reunion reunionApresMidi;
}
void info(PlanningJournalier planning)
void info(planningDeLaJournee planning)
{
info(planning.reunionMatin);
writeln();
@ -111,34 +112,34 @@ Voici le programme complet, qui utilise tous ces types :
MomentDeLaJournee(10, 30),
MomentDeLaJournee(11, 45));
immutable dejeuner = Repas(MomentDeLaJournee(12, 30), "Istanbul");
immutable dejeuner = Repas(MomentDeLaJournee(12, 30), "Istamboul");
immutable reunionBudget = Reunion("Budget", 8,
MomentDeLaJournee(15, 30),
MomentDeLaJournee(17, 30));
immutable planningAujourdhui = PlanningJournalier(reunionCyclisme,
dejeuner,
reunionBudget);
immutable planningAujourdhui = planningDeLaJournee(reunionCyclisme,
dejeuner,
reunionBudget);
info(planningAujourdhui);
writeln();
}
>>>]
La fonction main peut aussi n'être écrite qu'avec des objets littéraux :
La fonction [c main()] peut aussi n'être écrite qu'avec des objets littéraux~ :
[code=d <<<
void main()
{
info(PlanningJournalier(Reunion("Cyclisme", 4,
MomentDeLaJournee(10, 30),
MomentDeLaJournee(11, 45)),
info(planningDeLaJournee(Reunion("Cyclisme", 4,
MomentDeLaJournee(10, 30),
MomentDeLaJournee(11, 45)),
Repas(MomentDeLaJournee(12, 30), "Istanbul"),
Repas(MomentDeLaJournee(12, 30), "Istamboul"),
Reunion("Budget", 8,
MomentDeLaJournee(15, 30),
MomentDeLaJournee(17, 30))));
Reunion("Budget", 8,
MomentDeLaJournee(15, 30),
MomentDeLaJournee(17, 30))));
writeln();
}

View File

@ -2,11 +2,12 @@
title = "Nombre variable de paramètres"
partAs = chapitre
translator = "Olivier Pisano"
proofreader = "Stéphane Goujet"
]
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
- les valeurs par défaut de paramètres~ ;
- les fonctions variadiques.
[ = Les valeurs par défaut de paramètres
@ -24,11 +25,9 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
auto cles = ta.keys.sort;
foreach (i, cle; cles)
{
foreach (i, cle; cles) {
// Pas de séparateur avant le premier élément
if (i != 0)
{
if (i != 0) {
write(separateurElement);
}
@ -40,7 +39,7 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
}
>>>]
Cette fonction est appelée ci-après avec "``:``" comme séparateur de clé et "``,``" comme séparateur d'éléments~ :
Cette fonction est appelée ci-dessous avec "``:``" comme séparateur de clé et "``,``" comme séparateur d'éléments~ :
[code=d <<<
string[string] dictionnaire = [ "bleu":"blue", "rouge":"red", "gris":"gray" ];
@ -55,7 +54,7 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
>>>]
Si les séparateurs seront presque toujours ces deux-mêmes, ils peuvent être définis avec des valeurs par défaut~ :
Si les séparateurs sont presque toujours les deux mêmes, ils peuvent être définis avec des valeurs par défaut~ :
[code=d <<<
void afficheTA(in char[] titre,
@ -71,7 +70,7 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
Les paramètres par défaut n'ont pas besoin d'être spécifiés lors de l'appel d'une fonction~ :
[code=d <<<
afficheTA("Dictionnaire des couleurs", dictionnaire); /* ← pas de séparateur spécifié. Les deux paramètres prendront leurs valeurs par défaut */
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é~ :
@ -88,13 +87,13 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
bleu=blue, gris=gray, rouge=red
>>>]
L'appel ci-après spécifie les deux paramètres~ :
L'appel ci-dessous spécifie les deux paramètres~ :
[code=d <<<
afficheTA("Dictionnaire des couleurs", dictionnaire, "=", "\n");
>>>]
Le résultat:
Le résultat~ :
[output <<<
-- Dictionnaire des couleurs --
@ -103,24 +102,86 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
rouge=red
>>>]
Les valeurs par défaut peuvent seulement être définies pour les paramètres en fin de liste.
Les valeurs par défaut ne peuvent être définies que pour les paramètres en fin de liste.
]
[ = Mots-clés speciaux 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~ :
- [c __MODULE__]~ : Nom du module,
- [c __FILE__]~ : Nom du fichier source,
- [c __LINE__]~ : Numéro de la ligne,
- [c __FUNCTION__]~ : Nom de la fonction,
- [c __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~ :
[code=d <<<
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~ :
[output <<<
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, leurs valeurs font référence à l'endroit où la fonction est appelée~ :
[code=d <<<
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 à [c main()], l'appelant de la fonction~ :
[output <<<
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 leurs valeurs dépendant du compilateur et du moment de la compilation~ :
- [c __DATE__]~ : date de la compilation,
- [c __TIME__]~ : moment de la compilation (heures, minutes, secondes),
- [c __TIMESTAMP__]~ : date et moment de la compilation,
- [c __VENDOR__]~ : auteurs du compilateur (par ex. ''Digital Mars D'')
- [c __VERSION__]~ : version du compilateur, en tant qu'entier (par ex. la valeur 2069 pour la version 2.069)
]
[ = 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 leurs valeurs par défaut, ``afficheTA()`` prend toujours quatre paramètres et les utilise selon son implémentation.
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é avec un nombre quelconque de paramètres~ :
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~ :
[code=d <<<
writeln("hello", 7, "world", 9.8); /* et autant d'autres arguments que nécessaires */
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 régulières, 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.
- 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 [c <<<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 dont nous allons parler dans cette section.
@ -152,17 +213,17 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
En fait, les arguments peuvent aussi être passés sous la forme d'une seule tranche~ :
[code=d <<<
writeln(somme([1.1, 2.2, 3.3])); // identique au code ci-avant
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~ :
[code=d <<<
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
{
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) {
@ -178,7 +239,7 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
Les deux premiers paramètres sont obligatoires~ :
[code=d <<<
parentheser("{"); // ← Erreur de compilation
parentheser("{"); // ← ERREUR de compilation
>>>]
À partir du moment où les deux premiers paramètres sont spécifiés, les autres sont optionnels~ :
@ -187,13 +248,125 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
writeln(parentheser("{", "}","pomme", "poire", "banane"));
>>>]
En sortie:
En sortie~ :
[output <<<
{pomme}{poire}{banane}
>>>]
]
[ = 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, se serait un bogue~ :
[code=d <<<
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 [c foo()] aussi bien que la fonction membre [c S.foo()] sont boguées parce qu'elle 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~ :
[code=d <<<
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~ :
[code=d <<<
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();
}
>>>]
]
[ = Exercice
Considérez que l'énumération suivante est définie~ :
@ -202,11 +375,10 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
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.
Considérez également qu'il existe une structure qui représente le résultat d'un calcul et ses deux opérandes~ :
[code=d <<<
struct Calcul
{
struct Calcul {
Operation op;
double premier;
double second;
@ -215,22 +387,21 @@ Ce chapitre évoque deux fonctionnalités de D qui apportent de la flexibilité
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 structures, calcule le résultat de chacun et retourne ceux-ci comme une tranche de type double[].
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 [c <<<double[]>>>].
Par exemple, il serait possible d'appeler cette fonction comme ceci~ :
Par exemple, il doit être possible d'appeler cette fonction comme ceci~ :
[code=d <<<
void main()
{
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)));
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 serait le suivant~ :
Le résultat de ce code doit être le suivant~ :
[output <<<
[3.3, -1.1, 36.3, 0.875]

View File

@ -44,7 +44,7 @@
[part=fonctions]
[part=const_et_immutable]
[part=parametres_de_fonctions]
[part=lvalue_rvalue]
[part=lvalue_rvalue]
[part=operateurs_paresseux]
[part=main]
[part=exceptions]
@ -56,7 +56,7 @@
[part=valeur_vs_reference]
[part=null_is]
[part=cast]
[part=struct]
[part=struct]
[part=flexibilite_parametres]
[part=surcharge_fonctions]
[part=fonctions_membres]

View File

@ -2,13 +2,24 @@
title = "Structures"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Comme nous l'avons vu plusieurs fois dans les précédents chapitres de ce livre, les types fondamentaux ne sont pas suffisant pour représenter des concepts de plus haut niveau. Par exemple, même si une valeur de type [c int] est adaptée pour représenter une heure de la journée, deux variables [c int] seraient ensemble plus adaptées pour représenter un point dans le temps : une pour l'heure, une pour la minute.
Comme nous l'avons vu plusieurs fois dans les précédents chapitres de ce livre, les types fondamentaux ne sont pas suffisants pour représenter des concepts de plus haut niveau. Par exemple, même si une valeur de type [c int] peut être utilisée pour représenter une heure de la journée, deux variables [c int] seraient ensemble plus appropriées pour représenter un point dans le temps~ : une pour l'heure, une pour la minute.
Les structures sont la fonctionnalité qui permet de définir des nouveaux types en combinant d'autres types déjà existant. Le nouveau type est défini par le mot-clé [c struct]. Le gros de ce chapitre est directement applicable aux classes ; en particulier, l'idée de [i combiner les types existant pour définir un nouveau type] est exactement la même pour celles-ci.
Les structures sont la fonctionnalité qui permet de définir de nouveaux types en combinant d'autres types déjà existant. Le nouveau type est défini par le mot-clé [c struct]. Le gros de ce chapitre est également directement applicable aux classes~ ; en particulier, l'idée de [i combiner les types existant pour définir un nouveau type] est exactement la même pour celles-ci.
Pour comprendre l'utilité des structures, considérons à la fonction [c ajouterDuree] que nous avons définie plus tôt dans le [[doc:assert | chapitre sur les assertions et [c enforce]]]. La définition suivante est tirée des solutions de l'exercice de ce chapitre :
Ce chapitre ne couvre que les fonctionnalités basiques des structures. Nous en verrons plus sur les structures dans les chapitres suivant:
- [[part:fonctions_membres]]
- [[part:fonctions_membres_const]]
- [[part:fonctions_speciales]]
- [[part:surcharge_operateurs]]
- [[part:encapsulation]]
- [[part:propriete]]
- [[part:invariant]]
- [[part:foreach_opapply]]
Pour comprendre l'utilité des structures, considérons la fonction [c ajouterDuree] que nous avons définie plus tôt dans le [[doc:assert | chapitre sur les assertions et [c enforce]]]. La définition suivante est tirée des solutions de l'exercice de ce chapitre~ :
[code=d <<<
void ajouterDuree(in int heuresDepart, in int minutesDepart,
@ -24,13 +35,13 @@ Pour comprendre l'utilité des structures, considérons à la fonction [c ajoute
}
>>>]
[p Note : | Je vais ignorer les blocs [c in], [c out] et [c unittest] dans ce chapitre pour garder les codes exemples courts.]
[p Note~ : | je vais ignorer les blocs [c in], [c out] et [c unittest] dans ce chapitre pour garder les codes exemples courts.]
Même si la fonction ci-avant prend clairement six paramètres, quand on regarde les trois paires de paramètres de plus près, elle ne prend que trois informations : le moment de départ, la durée et le résultat.
Même s'il est clair que cette fonction prend six paramètres, quand on regarde les trois paires de paramètres de plus près, elle ne prend que trois informations~ : le moment de départ, la durée et le résultat.
[ = Définition
Le mot-clé [c struct] définit un nouveau type en combinant des variables qui ont un certain rapport :
Le mot-clé [c struct] définit un nouveau type en combinant des variables qui ont un certain rapport~ :
[code=d <<<
struct MomentDeLaJournee
@ -40,17 +51,17 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Le code ci-avant défnit un nouveau type nommé [c MomentDeLaJournee], qui consiste en deux variables nommées [c heure] et [c minute]. Cette définition permet d'utiliser le nouveau type [c MomentDeLaJournee] dans le programme comme n'importe quel autre type. Le code suivant démontre comment son utilisation est similaire à celle d'un [c int] :
Ce code définit un nouveau type nommé [c MomentDeLaJournee], qui consiste en deux variables nommées [c heure] et [c minute]. Cette définition permet d'utiliser le nouveau type [c MomentDeLaJournee] dans le programme comme n'importe quel autre type. Le code suivant démontre comment son utilisation est similaire à celle d'un [c int]~ :
[code=d <<<
int nombre; // une variable
nombre = autreNombre; // prenant la valeur de autreNombre
MomentDeLaJournee moment; // un objet
moment = autreMoment; // prenant la valeur de
moment = autreMoment; // prenant la valeur de autreMoment
>>>]
La syntaxe de la définition [c struct] est la suivante :
La syntaxe de la définition [c struct] est la suivante~ :
[code=d <<<
struct NomType
@ -61,14 +72,16 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
Nous verrons les fonctions membres dans des chapitres ultérieurs.
Les variables qu'une structure combine sont appelés ses [* membres]. Selon cette définition, [c MomentDeLaJournee] a deux membres : [c heure] et [c minute].
Les variables qu'une structure combine sont appelés ses [* membres]. Selon cette définition, [c MomentDeLaJournee] a deux membres~ : [c heure] et [c minute].
[ = [c struct] définit un type, pas une variable
Il y a une distinction importante ici : Surtout après les chapitres sur les [[doc:espace_de_nom | Espaces de Noms] et sur [[doc:duree_vie | les Durées de Vie et Opérations Fondamentales]], les accolades des définitions de structures peuvent donner la fausse impression que les membres des structures commencent et finissent leurs vies à l'intérieur de ce bloc. Ce n'est pas le cas.
Il y a une distinction importante ici : surtout après les chapitres sur les [[doc:espace_de_nom | Espaces de Noms] et sur [[doc:durees_vie | les Durées de Vie et Opérations Fondamentales]], les accolades des définitions de structures peuvent donner la fausse impression que les membres des structures commencent et finissent leurs vies à l'intérieur de ce bloc. Ce n'est pas le cas.
Les définitions de membres ne sont pas des definitions de variables :
[comment <<< La structure des paragraphes et exemples qui suivent légèrement changé en VO. >>>]
[code=d <<<
struct MomentDeLaJournee
{
@ -77,7 +90,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
La définition d'une structure détermine les types et les noms des membres que les objets de cette structure auront :
La définition d'une structure détermine les types et les noms des membres que les objets de cette structure auront~ :
[code=d <<<
struct MomentDeLaJournee
@ -87,7 +100,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Ces variables membres seront construit comme parties des objets [c MomentDeLaJournee] qui seront utilisés dans le programme :
Ces variables membres seront construites comme parties des objets [c MomentDeLaJournee] qui seront utilisés dans le programme~ :
[code=d <<<
MomentDeLaJournee sieste; // Cet objet contient ses propres
@ -103,22 +116,22 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
[ = Confort de programmation
La possibilité de combiner les idées d'heure et de minute ensemble dans un nouveau type est très pratique. Par exemple, la fonction ci-avant peut être réécrite de façon plus parlante en prenant trois paramètres de type [c MomentDeLaJournee] au lieu des six paramètres existant :
La possibilité de combiner les idées d'heure et de minute ensemble dans un nouveau type est très pratique. Par exemple, la fonction précédente peut être réécrite de façon plus parlante en prenant trois paramètres de type [c MomentDeLaJournee] au lieu des six paramètres existant~ :
[code=d <<<
void ajouterDuree(in MomentDeLaJournee depart,
in MomentDeLaJournee duree,
out MomentDeLaJournee resultatat)
out MomentDeLaJournee resultat)
{
// ...
}
>>>]
[p Note : | Il n'est pas normal d'ajouter deux variables qui représentent deux points du temps. Par exemple, il n'est pas sensé d'ajouter le moment du déjeuner (12h00) au moment du petit-déjeuner (7h30). Il serait plus sensé de définir un autre type, convenablement appelé [c Duree], et d'ajouter les objets de ce type aux objets [c MomentDeLaJournee]. Malgré cette erreur de conception, on continuera d'utiliser seulement [c MomentDeLaJournee] dans ce chapitre et on introduira [c Duree] dans un chapitre ultérieur.
[p Note~ : | il n'est pas normal d'ajouter deux variables qui représentent deux points du temps. Par exemple, il n'y a pas de sens à ajouter le moment du déjeuner (12h00) au moment du petit-déjeuner (7h30). Il serait plus sensé de définir un autre type, convenablement appelé [c Duree], et d'ajouter les objets de ce type aux objets [c MomentDeLaJournee]. Malgré cette erreur de conception, on continuera d'utiliser seulement [c MomentDeLaJournee] dans ce chapitre et on introduira [c Duree] dans un chapitre ultérieur.]
Comme nous l'avons vu, les fonction retournent au plus une valeur. Ceci avait été précisément la raison pour laquelle la fonction [c ajouterDuree] prenait deux paramètres résultats ([c out]) : elle ne pouvait pas retourner à la fois l'heure et la minute.
Comme nous l'avons vu, les fonction retournent au plus une valeur. Ceci était précisément la raison pour laquelle la fonction [c ajouterDuree] prenait deux paramètres résultats ([c out])~ : elle ne pouvait pas retourner à la fois l'heure et la minute.
Les structures enlèvent cette limitation : comme de multiples valeurs peuvent être combinées en un seul type [c struct], les fonction peuvent retourner un objet d'une telle structure, en retournant effectivement plusieurs valeurs à la fois. La fonction [c ajouterDuree] peut maintenant retourner son résultat :
Les structures enlèvent cette limitation : comme de multiples valeurs peuvent être combinées en un seul type [c struct], les fonction peuvent retourner un objet d'une telle structure, en retournant effectivement plusieurs valeurs à la fois. La fonction [c ajouterDuree] peut maintenant retourner son résultat~ :
[code=d <<<
MomentDeLaJournee ajouterDuree(in MomentDeLaJournee depart,
@ -128,9 +141,9 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Par conséquent, [c ajouterDuree] devient une fonction qui produit une valeur, au lieu d'être une fonction qui a des effets de bord. Comme vu dans le [[doc:fonctions | chapitre sur les Fonctions], il est préférable de produire des valeurs plutôt qu'avoir des effets de bord.
Par conséquent, [c ajouterDuree] devient une fonction qui produit une valeur, au lieu d'être une fonction qui a des effets de bord. Comme vu dans le [[doc:fonctions | chapitre sur les fonctions], il est préférable de produire des valeurs plutôt qu'avoir des effets de bord.
Les structures peuvent être des membres d'autres structures. Par exemple, la structure suivante a deux membres de type [c MomentDeLaJournee] :
Les structures peuvent être des membres d'autres structures. Par exemple, la structure suivante a deux membres de type [c MomentDeLaJournee]~ :
[©ode=d <<<
struct Reunion
@ -142,29 +155,29 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
La structure réunion peut à son tour former un membre d'une autre structure. En supposant qu'on ait aussi une structure [c Repas] :
La structure ``Reunion`` peut à son tour servir de membre d'une autre structure. En supposant qu'on ait aussi une structure [c Repas]~ :
[code=d <<<
struct DailyPlan
struct planningDeLaJournee
{
Reunion reunionDeProjet;
Meal dejeuner;
Reunion ReunionBudget;
Repas dejeuner;
Reunion reunionBudget;
}
>>>]
]
]
[ = Accéder aux membres
Les membres de structures peuvent être utilisés comme n'importe quelle autre variable. La seule différence est que le nom de la structure et un [* point] doivent être indiqués avant le nom du membre :
Les membres de structures peuvent être utilisés comme n'importe quelle autre variable. La seule différence est que le nom de la structure et un [* point] doivent être indiqués avant le nom du membre~ :
[code=d <<<
depart.heure = 10;
>>>]
La ligne ci-avant affecte la valeur 10 au membre [c heure] de l'objet [c depart].
La ligne ci-dessus affecte la valeur 10 au membre [c heure] de l'objet [c depart].
Réécrivons la fonction [c ajouterDuree] que nous avons vue jusqu'à maintenant :
Réécrivons la fonction [c ajouterDuree] avec ce que nous avons vu jusqu'ici~ :
[code=d <<<
MomentDeLaJournee ajouterDuree(in MomentDeLaJournee depart,
@ -183,74 +196,81 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
On remarque que les noms des variables sont maintenant beaucoup plus court dans cette version de la fonction : [c depart], [c duree] et [c resultat]. De plus, au lieu d'utiliser des noms complexes comme [c heuresDepart], il est possible d'accéder aux membres des structures à travers leurs structures respectives comme dans [c depart.heure].
On remarque que les noms des variables sont maintenant beaucoup plus courts dans cette version de la fonction~ : [c depart], [c duree] et [c resultat]. De plus, au lieu d'utiliser des noms complexes comme [c heuresDepart], il est possible d'accéder aux membres des structures à travers leurs structures respectives comme dans [c depart.heure].
Voici un code qui utilise la nouvelle fonction [c ajouterDuree]. Étant donné le moment de départ et la durée, le code suivant calcule quand une période de classe dans une école finit :
Voici un code qui utilise la nouvelle fonction [c ajouterDuree]. Étant donné le moment de début et la durée, le code suivant calcule l'horaire de fin d'un cours dans une école~ :
[code=d <<<
void main()
{
MomentDeLaJournee debutPeriode;
debutPeriode.heure = 8;
debutPeriode.minute = 30;
MomentDeLaJournee debutCours;
debutCours.heure = 8;
debutCours.minute = 30;
MomentDeLaJournee dureePeriode;
dureePeriode.heure = 1;
dureePeriode.minute = 15;
MomentDeLaJournee dureeCours;
dureeCours.heure = 1;
dureeCours.minute = 15;
immutable periodeFin = ajouterDuree(debutPeriode, dureePeriode);
immutable finCours = ajouterDuree(debutCours, dureeCours);
writefln("Fin de la période : %sh%s", periodeFin.heure, periodeFin.minute);
writefln("Fin du cours : %sh%s", finCours.heure, finCours.minute);
}
>>>]
La sortie :
La sortie~ :
[output <<<
Fin de la période : 9h45
Fin du cours : 9h45
>>>]
La fonction [c main] ci-avant a été écrite seulement avec ce que nous avons vu jusqu'à maintenant. Nous rendrons bientôt ce code encore plus clair et succinct.
La fonction [c main] a été écrite en utilisant uniquement ce que nous avions vu jusqu'à maintenant. Nous rendrons bientôt ce code encore plus clair et succinct.
]
[ = Construction
Les trois premières lignes de [c main] servent à construire l'objet [c debutPeriode] et les trois lignes suivantes servent à construire l'objet [c dureePeriode]. Dans chacun de blocs de 3 lignes, un objet est d'abord défini, et ensuite ses membres heure et minutes sont affectés.
Les trois premières lignes de [c main] servent à construire l'objet [c debutCours] et les trois lignes suivantes servent à construire l'objet [c dureeCours]. Dans chacun de ces blocs de 3 lignes, un objet est d'abord défini, et ensuite ses membres ``heure`` et ``minute`` sont affectés.
Pour qu'une variable soit utilisée de manière sûre, cette variable doit d'abord être construite dans un état consistant. La construction étant très commune, il existe une syntaxe spéciale pour construire les structures :
Pour qu'une variable soit utilisée de manière sûre, cette variable doit d'abord être construite dans un état cohérent. La construction étant una action très commune, il existe une syntaxe spéciale pour construire les structures~ :
[code=d <<<
MomentDeLaJournee debutPeriode = MomentDeLaJournee(8, 30);
MomentDeLaJournee dureePeriode = MomentDeLaJournee(1, 15);
MomentDeLaJournee debutCours = MomentDeLaJournee(8, 30);
MomentDeLaJournee dureeCours = MomentDeLaJournee(1, 15);
>>>]
Les valeurs sont automatiquement affectées aux membres dans l'ordre dans lequel ils sont définis : comme [c heure] est défini en premier dans la structure, la valeur 8 est affectée à [c debutPeriode.heure] et 30 est affectée à [c debutPeriode.minute].
Les valeurs sont automatiquement affectées aux membres dans l'ordre dans lequel ils sont définis : comme [c heure] est défini en premier dans la structure, la valeur 8 est affectée à [c debutCours.heure] et 30 est affecté à [c debutCours.minute].
Comme nous le verrons dans un [[part:cast | chapitre ultérieur]], la syntaxe de construction peut aussi être utilisée pour d'autres types~ :
[code=d <<<
auto u = ubyte(42); // u est un ubyte
auto i = int(u); // i est un int
>>>]
[ = Construire des objets [c immutable]
Pouvoir construire l'objet en spécifiant les valeurs de ses membres au même moment permet de définir des objets [c immutable] :
Pouvoir construire l'objet en spécifiant les valeurs de ses membres au même moment permet de définir des objets [c immutable]~ :
[code=d <<<
immutable debutPeriode = MomentDeLaJournee(8, 30);
immutable dureePeriode = MomentDeLaJournee(1, 15);
immutable debutCours = MomentDeLaJournee(8, 30);
immutable dureeCours = MomentDeLaJournee(1, 15);
>>>]
Autrement, il ne serait pas possible de marquer un objet comme [c immutable] et ensuite de modifier ses membres :
Autrement, il ne serait pas possible de marquer un objet comme [c immutable] et ensuite de modifier ses membres~ :
[code=d <<<
immutable MomentDeLaJournee debutPeriode;
debutPeriode.heure = 8; // ERREUR de compilation
debutPeriode.minute = 30; // ERREUR de compilation
immutable MomentDeLaJournee debutCours;
debutCours.heure = 8; // ERREUR de compilation
debutCours.minute = 30; // ERREUR de compilation
>>>]
]
[ = Les membres restant n'ont pas besoin d'être spécifiés
Il peut y avoir moins de valeurs spécifiés que de nombre de membres. Dans ce cas, les membres restant sont initialisés par les valeurs [c .init] de leurs types respectifs.
Il peut y avoir moins de valeurs spécifiées qu'il n'y a de membres. Dans ce cas, les membres restant sont initialisés par les valeurs [c .init] de leur type respectif.
Le programme suivant construit des objets [c Test] avec un paramètre de moins à chaque construction. Les assertions indiquent que les membres non spécifiés sont automatiquement initialisés par leur valeur [c .init] (L'utilisation de la fonction [c isNaN] est expliquée après le programme) :
Le programme suivant construit des objets [c Test] avec un paramètre de moins à chaque construction. Les assertions indiquent que les membres non spécifiés sont automatiquement initialisés par leur valeur [c .init] (l'utilisation de la fonction [c isNaN] est expliquée après le programme)~ :
[code=d <<<
import std.math;
@ -296,16 +316,16 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Comme nous l'avons vu dans le [[doc:virgule_flottante | chapitre sur les Virgules Flottantes]], la valeur initiale de [c double] est [c double.nan]. Comme la valeur [c .nan] n'est pas ordonnée, l'utiliser dans des comparaisons d'égalité n'est pas sensé. C'est pourquoi appeler [c std.math.isNaN] est la manière correcte de déterminer si une valeur est égale à [c .nan] ou non.
Comme nous l'avons vu dans le [[doc:virgule_flottante | chapitre sur les virgules flottantes]], la valeur initiale de [c double] est [c double.nan]. Comme la valeur [c .nan] n'est pas ordonnée, l'utiliser dans des comparaisons d'égalité n'a pas de sens. C'est pourquoi appeler [c std.math.isNaN] est la manière correcte de déterminer si une valeur est égale à [c .nan] ou non.
]
[ = Spécifier les valeurs par défaut des membres
Il est important que les variables membres soient automatiquement initialisé avec des valeurs initiales connues. Ceci évite au programme de continuer avec des valeurs indéterminées. Cependant, la valeur [c .init] de leurs types respectifs peut ne pas convenir pour tous les types. Par exemple, [c char.init] n'est même pas une valeur valide.
Il est important que les variables membres soient automatiquement initialisées avec des valeurs initiales connues. Ceci évite au programme de continuer avec des valeurs indéterminées. Cependant, la valeur [c .init] de leurs types respectifs peut ne pas convenir pour tous les types. Par exemple, [c char.init] n'est même pas une valeur valide.
Les valeurs initiales des membres d'une structure peuvent être spécifiées lors de la définition de la structure. Ceci est utile pour, par exemple, initialiser les membres à virgule flottante à [c 0.0], plutôt qu'à [c .nan] qui est souvent inexploitable.
Les valeurs initiales des membres d'une structure peuvent être spécifiées lors de la définition de la structure. Ceci peut servir, par exemple, à initialiser les membres à virgule flottante à [c 0.0], plutôt qu'à [c .nan] qui est souvent inexploitable.
Les valeurs par défaut sont spécifiées avec la syntaxe de l'affectation lors de la définition des membres :
Les valeurs par défaut sont spécifiées avec la syntaxe de l'affectation lors de la définition des membres~ :
[code=d <<<
struct Test
@ -317,16 +337,16 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
>>>]
Veuillez noter que la syntaxe ci-avant n'est pas vraiment une affectation. Le code ci-avant détermine les valeurs par défaut qui seront utilisées quand les objets de cette structures seront construits plus tard dans le programme.
Veuillez noter que cette syntaxe n'est pas vraiment une affectation. Le code se contente de déterminer les valeurs par défaut qui seront utilisées quand les objets de cette structure seront construits plus tard dans le programme.
Par exemple, l'objet [c Test] suivant est construit sans valeur spécifique :
Par exemple, l'objet [c Test] suivant est construit sans valeur spécifique~ :
[code=d <<<
Test t; // aucune valeur n'est spécifiée pour les membres de t
writefln("%s,%s,%s", t.c, t.i, t.d);
>>>]
Tous les membres sont initialisés avec leurs valeurs par défaut :
Tous les membres sont initialisés avec leur valeur par défaut~ :
[code=bash <<<
A,11,0.25
@ -336,48 +356,53 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
[ = Construction avec la syntaxe [c { }]
Les structures peuvent aussi être construites avec la syntaxe suivante :
Les structures peuvent aussi être construites avec la syntaxe suivante~ :
[code=d <<<
MomentDeLaJournee debutPeriode = { 8, 30 };
MomentDeLaJournee debutCours = { 8, 30 };
>>>]
Comme avec la syntaxe précédente, les valeurs indiquées sont affectées aux membres dans l'ordre dans lequel ceux-ci sont définis. Les membres restant seront initialisés à leurs valeurs par défaut.
Cette syntaxe est héritée du langage C :
Cette syntaxe est héritée du langage C~ :
[code=d <<<
auto debutPeriode = MomentDeLaJournee(8, 30); // ← recommended
MomentDeLaJournee periodeFin = { 9, 30 }; // ← as in C
auto debutCours = MomentDeLaJournee(8, 30); // ← recommandé
MomentDeLaJournee finCours = { 9, 30 }; // ← façon C
>>>]
Cette syntaxe autorise les initialisateurs désignés. Les initialisateurs désignés sont utilisés pour spécifier le membre associé à une valeur d'initialisation. Il est même possible d'initialiser les membres dans un ordre différent de celui dans lequel ils sont définis dans la structure~ :
[code=d <<<
MomentDeLaJournee m = { minute: 42, heure: 7 };
>>>]
]
]
[ = Copie et affectation
Les structures sont des types valeur. Comme décrit dans le [[doc:valeur_vs_reference | chapitre sur les types valeur et types référence]], ceci veut dire que chaque objet [c struct] a sa propre valeur. Les objets reçoivent leurs propres valeurs quand ils sont construits et leurs valeurs changent quand on leur affecte des nouvelles valeurs.
Les structures sont des types valeur. Comme décrit dans le [[doc:valeur_vs_reference | chapitre sur les types valeur et types référence]], ceci veut dire que chaque objet [c struct] a sa propre valeur. Les objets reçoivent leurs propres valeurs quand ils sont construits et leurs valeurs changent quand on leur affecte de nouvelles valeurs.
[code=d <<<
auto votreHeureDuDejeuner = MomentDeLaJournee(12, 0);
auto monHeureDuDejeuner = votreHeureDuDejeuner;
// Seul monHeureDuDejeuner est changé :
// Seul monHeureDuDejeuner passe à 12h05 :
monHeureDuDejeuner.minute += 5;
// ... votreHeureDuDejeuner reste inchangé :
assert(yourLunchTime.minute == 0);
>>>]
Pendant une copie, tous les membres de l'objet source sont automatiquement copiés vers leurs membres correspondant dans l'objet de destination. De manière similaire, l'affectation implique l'affectation de chaque membre de la source vers le membre de destination correspondant.
Pendant une copie, tous les membres de l'objet source sont automatiquement copiés vers les membres correspondant dans l'objet de destination. De manière similaire, l'affectation implique l'affectation de chaque membre de la source au membre de destination correspondant.
Les membres de structures qui sont de types référence demandent une attention particulière.
Les membres de structures qui sont d'un type référence demandent une attention particulière.
[ = Attention avec les membres de types référence !
[ = Attention avec les membres qui sont de types référence !
Comme vous le savez, affecter les variables de types référence ne changent aucune valeur, cela change ce que l'objet référence. Ainsi, copier ou affecter une structure qui a des membres de type référence crée des références dans l'objet de destination. Ceci implique que des membres de deux structures distinctes peuvent donner accès à la même valeur.
Comme vous le savez, le fait de copier ou d'affecter les variables de types référence ne modifie aucune valeur, cela ne fait que changer [* quel] objet est référencé. Ainsi, copier ou affecter une structure qui a des membres de type référence va créer dans l'objet de destination des références supplémentaires aux objets référencés dans l'objet source. Ceci implique que des membres de deux structures distinctes peuvent donner accès à la même valeur.
Pour voir un exemple de ceci, regardons une structure dont l'un des membres est de type référence. Cette structure est utilisée pour stocker le numéro et les notes d'un étudiant :
Pour voir un exemple de ceci, regardons une structure dont l'un des membres est de type référence. Cette structure est utilisée pour stocker le numéro et les notes d'un étudiant~ :
[code=d <<<
struct Etudiant
@ -387,7 +412,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Le code suivant construit un second objet en copiant un objet existant :
Le code suivant construit un second objet [c Etudiant] en copiant un objet existant~ :
[code=d <<<
// On construit le premier objet :
@ -398,29 +423,29 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
auto etudiant2 = etudiant1;
etudiant2.numero = 2;
// AVERTISSEMENT: Les notes sont maintenant partagés par les deux objets !
// AVERTISSEMENT: Les notes sont maintenant partagées par les deux objets !
// Changer les notes du premier étudiant
// Changer les notes du premier étudiant...
etudiant1.notes[0] += 5;
// ... affecte les notes du second étudiant :
writeln(etudiant2.notes[0]);
>>>]
Quand [c etudiant2] est construit, ses membres prennent les valeurs des membres de [c studient1]. Comme [c int] est un type valeur, le second objet obtient sa propre valeur [c numero].
Quand [c etudiant2] est construit, ses membres prennent les valeurs des membres de [c etudiant1]. Comme [c int] est un type valeur, le second objet obtient sa propre valeur [c numero].
Les deux objets [c Etudiant] ont aussi des membres [c notes] individuels. Cependant, comme les tranches sont des types référence, les éléments que les tranches partagent sont les mêmes. Ainsi, une modification faite depuis une des tranches sera reflétée sur l'autre tranche.
Les deux objets [c Etudiant] ont aussi un membre [c notes] chacun. Cependant, comme les tranches sont des types référence, les éléments que les tranches partagent sont les mêmes. Ainsi, une modification faite depuis une des tranches sera reflétée sur l'autre tranche.
La sortie du code indique que la note du second étudiant a été augmentée également :
La sortie du code indique que la note du second étudiant a été augmentée également~ :
[output <<<
75
>>>]
Pour cette raison, une meilleure approche serait de construire le deuxième objet en copiant les notes du premier :
Pour cette raison, une meilleure approche serait de construire le deuxième objet en copiant les notes du premier~ :
[code=d <<<
// Le deuxième Etudiant construit en copiant les notes du premier:
// Le deuxième Etudiant est construit en copiant les notes du premier :
auto etudiant2 = Etudiant(2, etudiant1.notes.dup);
// Changer les notes du premier étudiant...
@ -430,53 +455,53 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
writeln(etudiant2.notes[0]);
>>>]
Comme les notes ont été copiées avec [c .dup], les notes du deuxième étudiant ne sont cette fois pas affectée :
Comme les notes ont été copiées avec [c .dup], les notes du deuxième étudiant ne sont cette fois pas affectées~ :
[output <<<
70
>>>]
[p (Note : | Il est possible d'avoir même les membres références copiés automatiquement. nous verrons comment plus tard, quand on verra les membres fonctions.)]
[p (Note : | il existe néanmoins une possibilité de faire en sorte que les membres références soit également copiés. Nous verrons comment plus tard, quand on abordera les membres fonctions.)]
]
[ = Littéraux structure
Comme pour l'utilisation des valeurs littérales comme 10 dans les expressions sans avoir besoin de définir une variable, des littéraux de structures peuvent également être utilisés :
De la même manière que l'on peut utiliser des valeurs littérales entières (comme 10) dans les expressions sans avoir besoin de définir de variable, des littéraux de structures peuvent également être utilisés~ :
Les littéraux de structures sont construits par la syntaxe de construction d'objets.
Les littéraux de structures sont construits avec la syntaxe de construction d'objets.
[code=d <<<
MomentDeLaJournee(8, 30) // ← littéral de structure
>>>]
Réécrivons la fonction [c main()] ci-avant avec ce qu'on a appris depuis sa dernière version. Les variables sont construites par la syntaxe de construction et sont cette fois immuables :
Réécrivons la dernière fonction [c main()] que nous avons défini, avec ce qu'on a appris depuis sa dernière version. Les variables sont construites avec la syntaxe de construction et sont cette fois immuables~ :
[code=d <<<
void main()
{
immutable debutPeriode = MomentDeLaJournee(8, 30);
immutable dureePeriode = MomentDeLaJournee(1, 15);
immutable debutCours = MomentDeLaJournee(8, 30);
immutable dureeCours = MomentDeLaJournee(1, 15);
immutable periodeFin = ajouterDuree(debutPeriode,
dureePeriode);
immutable finCours = ajouterDuree(debutCours,
dureeCours);
writefln("Fin de la période : %s:%s",
periodeFin.heure, periodeFin.minute);
writefln("Fin du cours : %s:%s",
finCours.heure, finCours.minute);
}
>>>]
Notez que [c debutPeriode] et [c dureePeriode] ne nécessitent pas d'être définies comme des variables nommées dans le code ci-avant. Elles sont en fait des variables temporaires dans ce programme simple, qui sont utilisées seulement pour calculer la variable [c periodeFin]. Elles pourraient être passées à [c ajouterDuree()] comme des valeurs littérale :
Notez qu'il n'est pas nécessaire de définir [c debutCours] et [c dureeCours] comme des variables nommées dans ce cas. Elles sont en fait des variables temporaires dans ce programme simple, qui sont utilisées seulement pour calculer la variable [c finCours]. Elles pourraient être passées à [c ajouterDuree()] comme des valeurs littérales~ :
[code=d <<<
void main()
{
immutable periodeFin = ajouterDuree(MomentDeLaJournee(8, 30),
immutable finCours = ajouterDuree(MomentDeLaJournee(8, 30),
MomentDeLaJournee(1, 15));
writefln("Fin de la période : %s:%s",
periodeFin.heure, periodeFin.minute);
writefln("Fin du cours : %s:%s",
finCours.heure, finCours.minute);
}
>>>]
@ -484,9 +509,9 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
[ = Membres [c static]
Même si les objets ont surtout besoin de copies individuelles des membres, il peut être utile pour une type de structures particulier de partager certaines variables. Ce peut être nécessaire quand par exemple une information générale sur ce type de structure a besoin d'être maintenue.
Même si les objets ont surtout besoin de copies individuelles de leurs membres, il peut être utile pour un type de structure particulier de partager certaines variables. Ce peut être nécessaire quand par exemple une information générale sur ce type de structure a besoin d'être maintenue.
Par exemple, imaginons un type qui affecte un identificateur différent pour chaque objet de ce type :
Par exemple, imaginons un type qui affecte un identificateur différent pour chaque objet de ce type~ :
[code=d <<<
struct Point
@ -499,7 +524,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Afin de pouvoir affecter différents identificateurs à chaque objet, il faut une variable distincte pour garder la prochaine valeur que l'on peut utiliser, qui sera incrémentée à chaque foit qu'un nouvel objet est créé. Supposons que [c idSuivant] est à définir autre part et accessible à la fonction suivante :
Afin de pouvoir affecter un identificateur différent à chaque objet, il faut une variable distincte pour garder la prochaine valeur que l'on peut utiliser, variable qui sera incrémentée à chaque foit qu'un nouvel objet est créé. Supposons que [c idSuivant] est définie autre part et accessible à la fonction suivante~ :
[code=d <<<
Point creerPoint(int ligne, int colonne)
@ -511,7 +536,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Une décision doit être prise sur l'endroit où l'on définit la variable commune [c idSuivant]. les membres statiques sont utiles dans de tels cas. Une information de cette sorte est définie comme un membre statique de la structure. Contrairement aux membres classiques, il y a une seule variable pour chaque membre statique dans tout le programme. Cette variable unique est partagée par tous les objets de cette structure :
Une décision doit être prise quant à l'endroit où l'on définit la variable commune [c idSuivant]. Les membres statiques sont utiles dans de tels cas. Une information de cette sorte est définie comme un membre statique ([c static]) de la structure. Contrairement aux membres classiques, il y a une seule variable pour chaque membre statique dans tout le fil d'exécution. Cette variable unique est partagée par tous les objets de cette structure~ :
[code=d <<<
import std.stdio;
@ -548,7 +573,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Comme [c idSuivant] est incrémenté lors de la construction de chaque objet, les objets on chacun un identificateur unique :
Comme [c idSuivant] est incrémenté lors de la construction de chaque objet, les objets ont chacun un identificateur unique~ :
[output <<<
0
@ -556,7 +581,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
2
>>>]
Comme les membres statiques appartiennent au type entier, il n'y a pas besoin d'objet pour y accéder. Comme nous l'avons vu ci-avant, on peut aussi bien accéder à de tels objets par le nom du type qu'à travers le nom de tout objet de ce type :
Comme les membres statiques appartiennent au type lui-même, il n'y a pas besoin d'objet pour y accéder. Comme nous l'avons vu ci-dessus, on peut accéder à de tels objets par le nom du type, mais on peut aussi bien le faire à travers le nom de tout objet de ce type~ :
[code=d <<<
++Point.idSuivant;
@ -564,25 +589,84 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
>>>]
]
[ = [c static this()] pour l'initialisation et [c static ~~this()] pour la finalisation
Au lieu d'affecter explicitement une valeur initiale à [c idSuivant] ci-dessus, nous avons utilisé sa valeur initiale par défaut, zéro. Nous aurions pu utiliser n'importe quelle autre valeur~ :
[code=d <<<
static size_t idSuivant = 1000;
>>>]
Cependant, une telle initialisation est possible seulement quand la valeur initiale est connue lors de la compilation. De plus, certains codes spéciaux peuvent nécessiter d'être exécutés avant qu'une structure soit utilisée dans un fil d'exécution. De tels codes peuvent être écrits dans des portées [c static this()].
Par exemple, le code suivant lit la valeur initiale dans un fichier s'il existe~ :
[code=d <<<
import std.file;
struct Point {
// ...
enum fichierIdSuivant = "Point_fichier_id_suivant"
static this() {
if (exists(fichierIdSuivant)) {
auto fichier = File(fichierIdSuivant, "r");
fichier.readf(" %s", &idSuivant);
}
}
}
>>>]
Le contenu des blocs [c static this()] est exécuté une fois par fil d'exécution avant que le type de struct soit utilisé dans ce fil d'exécution. Le code qui devrait être exécuté seulement une fois durant toute l'exécution du programme (par exemple, pour initialiser des variables [c shared] ou [c immutable]) doivent être définis dans des blocs [c shared static this()] et [c shared static ~this()], qui seront vus dans le [[part:concurrence_shared | chapitre sur les données partagées et la concurrence]].
De manière similaire, [c static ~this()] est utilisé pour les opérations finales d'un fil d'exécution et [c shared static ~this()] est utilisé pour les opérations finales du programme entier.
L'exemple suivant complète le bloc [c static this()] en écrivant la valeur de [c idSuivant] dans le même fichier, ce qui permet de rendre les identifiants des objets persistants entre les exécutions du programme~ :
[code=d <<<
struct Point {
// ...
static ~this() {
auto fichier = File(fichierIdSuivant, "w");
file.writeln(idSuivant);
}
}
>>>]
Le programme initialise maintenant [c idSuivant] à partir de l'identifiant de l'exécution précédente. Par exemple, ce qui suit est la sortie de la deuxième exécution du programme~ :
[output <<<
3
4
5
>>>]
]
[ = Exercices
# Concevez une structure [c Carte] pour représenter une carte à jouer.
Cette structure peut avoir deux membres : un pour la couleur et un la valeur. Il peut être judicieux d'utiliser un [c enum] pour représenter la couleur, ou vous pouvez simplement utiliser les caractères ♠, ♡, ♢ et ♣.
Cette structure peut avoir deux membres~ : un pour la couleur et un pour la valeur. Il peut être judicieux d'utiliser un [c enum] pour représenter la couleur, ou vous pouvez simplement utiliser les caractères ♠, ♡, ♢ et ♣.
Une valeur [c int] ou [c dchar] peut être utilisée pour la valeur de la carte. Si vous décidez d'utiliser un [c int], les valeurs 1, 11, 12 et 13 peuvent représenter les cartes qui ne sont pas des nombres (as, vallet, dame et roi).
Une valeur [c int] ou [c dchar] peut être utilisée pour la valeur de la carte. Si vous décidez d'utiliser un [c int], les valeurs 1, 11, 12 et 13 peuvent représenter les cartes qui ne sont pas des nombres (as, valet, dame et roi).
Il y a d'autres choix de conception à faire. Par exemple, les valeurs des cartes peuvent aussi être représentées par un type [c enum].
La manière dont les objets de cette structure peuvent être construits dépendra des choix de types de ses membres. Par exemple, si les deux membres sont des [c dchar], alors les objects de type [c Carte] pourront être construits de cette manière :
La manière dont les objets de cette structure peuvent être construits dépendra des choix de types de ses membres. Par exemple, si les deux membres sont des [c dchar], alors les objects de type [c Carte] pourront être construits ainsi~ :
[code=d <<<
auto carte = Carte('♣', '2');
>>>]
# Définissez une fonction nommée [c afficherCarte()], qui prend un objet carte en paramètre elle l'affiche :
# Définissez une fonction nommée [c afficherCarte()], qui prend un objet carte en paramètre et l'affiche~ :
[code=d <<<
struct Carte
{
// ... définir la structure ...
}
void afficherCarte(in Carte carte)
{
// ... écrire le corps de la fonction ...
@ -595,7 +679,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Par exemple, la fonction peut afficher le deux de trèfle de cette manière :
Par exemple, la fonction peut afficher le deux de trèfle de cette manière~ :
[output <<<
♣2
@ -603,9 +687,9 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
L'implémentation de cette fonction peut dépendre des choix de types des membres.
# Définissez une fonction nommée [c nouveauJeu()] qui retourne les 52 cartes d'un jeu en tant que tranche de d'objets Carte :
# Définissez une fonction nommée [c nouveauJeu()] qui retourne les 52 cartes d'un jeu en tant que tranche d'objets Carte~ :
[code=d <<<<
[code=d <<<
Carte[] nouveauJeu()
out (resultat)
{
@ -617,7 +701,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Il devrait être possible d'appeler [c nouveauJeu()] comme dans le code suivant :
Il doit être possible d'appeler [c nouveauJeu()] comme dans le code suivant~ :
[code=d <<<
void main()
@ -633,7 +717,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
La sortie devrait ressembler à ce qui suit, avec 52 cartes distinctes :
La sortie devrait ressembler à ce qui suit, avec 52 cartes distinctes~ :
[output <<<
♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠0 ♠V ♠D ♠R ♠A ♡2 ♡3 ♡4
@ -642,7 +726,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
♣V ♣D ♣R ♣A
>>>]
# Écrivez une fonction qui mélange le jeu. Une manière de le faire est de prendre deux cartes au hasard aveec [c std.random.uniform], les échanger et répéter ce processus un nombre de fois suffisant. La fonction devrait prendre le nombre de répétition en paramètre :
# Écrivez une fonction qui mélange le jeu. Une manière de le faire est de prendre deux cartes au hasard avec [c std.random.uniform], les échanger et répéter ce processus un nombre de fois suffisant. La fonction devrait prendre le nombre de répétitions en paramètre~ :
[code=d <<<
void melanger(Carte[] jeu, in int repetitions)
@ -651,7 +735,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
Voici comment elle devrait être utilisée :
Voici comment elle doit pouvoir être utilisée~ :
[code=d <<<
void main()
@ -668,22 +752,22 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
}
>>>]
La fonction devrait échanger les cartes [c répétitions] fois. Par exemple, un appel avec 1 devrait donner une sortie similaire à ce qui suit :
La fonction doit échanger les cartes [c répétitions] fois. Par exemple, un appel avec 1 doit donner une sortie similaire à ceci~ :
[code=d <<<
[output <<<
♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠0 ♠V ♠D ♠R ♠A ♡2 ♡3 ♡4
♡5 ♡6 ♡7 ♡8 ♣4 ♡0 ♡V ♡D ♡R ♡A ♢2 ♢3 ♢4 ♢5 ♢6 ♢7
♢8 ♢9 ♢0 ♢V ♢D ♢R ♢A ♣2 ♣3 ♡9 ♣5 ♣6 ♣7 ♣8 ♣9 ♣0
♣V ♣D ♣R ♣A
>>>]
Une valeur plus grande de [c repetitions] devrait résulter en un jeu plus mélangé : should resultat in a more shuffled jeu :
Une valeur plus grande de [c repetitions] devrait résulter en un jeu plus mélangé~ :
[code=d <<<
melanger(jeu, 100);
>>>]
La sortie :
La sortie~ :
[output <<<
♠4 ♣7 ♢9 ♢6 ♡2 ♠6 ♣6 ♢A ♣5 ♢8 ♢3 ♡D ♢V ♣R ♣8 ♣4
@ -692,7 +776,7 @@ Même si la fonction ci-avant prend clairement six paramètres, quand on regarde
♠R ♣9 ♠0 ♣3
>>>]
[p Note : | Un meilleure façon de mélanger le jeu est expliquée dans les solutions.]
[p Note : | une meilleure façon de mélanger le jeu est expliquée dans les solutions.]
[[part:corrections/struct | … Les solutions]]
]

View File

@ -2,11 +2,12 @@
title = "Surcharge de fonctions"
partAs = "chapitre"
translator = "Olivier Pisano"
proofreader = "Stéphane Goujet"
]
On appelle surcharger une fonction le fait de définir plusieurs fonctions ayant toutes le même nom. Pour pouvoir les différentier, leurs paramètres doivent être différents.
On appelle surcharger une fonction le fait de définir plusieurs fonctions ayant toutes le même nom. Pour pouvoir les différencier, leurs paramètres doivent être différents.
Le code suivant montre plusieurs surcharges de la fonction ``info()``, chacune prenant un type de paramètre différent :
Le code suivant montre plusieurs surcharges de la fonction ``info()``, chacune prenant un type de paramètre différent~ :
[code=d <<<
import std.stdio;
@ -36,7 +37,7 @@ Le code suivant montre plusieurs surcharges de la fonction ``info()``, chacune p
Bien que toutes les fonctions soient nommées ``info()``, le compilateur choisit celle qui correspond à l'argument qui est utilisé lors de l'appel. Ici, la fonction ``info()`` prenant un double est appelée parce que la constante littérale ``1.2`` est de type ``double``.
Le choix de la fonction à appeler est fait à la compilation, ce qui n'est pas toujours facile ou évident. Par exemple, puisque le type int peut être implicitement converti en type ``double`` et en type ``real``, le compilateur ne peut choisir quelle fonction employer dans le programme suivant :
Le choix de la fonction à appeler est fait à la compilation, ce qui n'est pas toujours facile ou évident. Par exemple, puisque le type ``int`` peut être implicitement converti en type ``double`` et en type ``real``, le compilateur ne peut choisir quelle fonction employer dans le programme suivant~ :
[code=d <<<
real septFois(in real valeur)
@ -52,13 +53,13 @@ Le choix de la fonction à appeler est fait à la compilation, ce qui n'est pas
void main()
{
int valeur = 5;
auto result = septFois(valeur); // ← erreur de compilation
auto resultat = septFois(valeur); // ← ERREUR de compilation
}
>>>]
[p Note: | Il n'est généralement pas nécessaire d'écrire des fonctions séparées quand les corps des fonctions sont identiques. Nous verrons plus tard dans le chapitre des templates comment écrire une définition de fonction unique qui peut être utilisée avec des types différents.]
[p Note~ : | il n'est généralement pas nécessaire d'écrire des fonctions séparées quand les corps des fonctions sont identiques. Nous verrons plus tard dans le chapitre des templates comment écrire une définition de fonction unique qui peut être utilisée avec des types différents.]
Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``long``, alors l'ambigüité est résolue, car long correspond mieux à int que double ou real~ :
Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``long``, alors l'ambigüité est résolue, car ``long`` correspond mieux à ``int`` que ``double`` ou ``real``~ :
[code=d <<<
long septFois(in long valeur)
@ -68,7 +69,7 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
// ...
auto result = septFois(valeur); // compile maintenant
auto resultat = septFois(valeur); // compile maintenant
>>>>]
@ -76,22 +77,22 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
Le compilateur choisit la surcharge qui correspond le mieux aux arguments. C'est ce que l'on appelle la résolution de surcharge.
Bien que la résolution soit simple et intuitive dans la plupart des cas, c'est parfois compliqué. Voici les règles de résolution de surcharge. Elles sont présentées d'une manière simplifiée dans ce livre.
Bien que la résolution soit simple et intuitive la plupart du temps, ce n'est pas toujours le cas. Voici les règles de résolution de surcharge. Elles sont présentées de manière simplifiée dans ce livre.
Il y a 4 états de correspondance, du pire au meilleur~ :
- Pas de correspondance
- Correspondance via conversion automatique de type
- Correspondance via qualification const
- Correspondance exacte
Il y a 4 niveaux de correspondance, du pire au meilleur~ :
- pas de correspondance~ ;
- correspondance via conversion automatique de type~ ;
- correspondance via qualification ``const``~ ;
- correspondance exacte.
Le compilateur considère toutes les surcharges d'une fonction pendant la résolution de surcharge. Si la fonction a plusieurs paramètres, l'état de correspondance global d'une fonction est le plus mauvais parmi l'état de correspondance de chacun des paramètres.
Le compilateur considère toutes les surcharges d'une fonction pendant la résolution de surcharge. Si la fonction a plusieurs paramètres, le niveau de correspondance global d'une fonction est le plus mauvais des niveaux de correspondance de tous les paramètres.
Après que tous les états de correspondance ont été déterminés, la surcharge avec la meilleure correspondance est choisie. S'il y a plusieurs surcharges qui ont la même correspondance, alors des règles plus complexes sont appliquées. Je ne rentrerai pas dans les détails de ces règles dans ce livre. Si votre programme en est au point où il dépend de règles complexes de résolution de surcharge de fonctions, c'est peut-être un signe qu'il est temps d'en revoir la conception. Une autre option est de profiter d'autres fonctionnalités de D comme des templates. Une approche encore plus simple serait de nommer chaque fonction différemment pour chaque type comme ``septFois_real()`` et ``septFois_double()``.
Après que tous les niveaux de correspondance aient été déterminés, la surcharge avec la meilleure correspondance est choisie. S'il y a plusieurs surcharges qui ont la même correspondance, alors des règles plus complexes sont appliquées. Je ne rentrerai pas dans les détails de ces règles dans ce livre. Si votre programme en est au point où il dépend de règles complexes de résolution de surcharge de fonctions, c'est peut-être un signe qu'il est temps d'en revoir la conception. Une autre option est de profiter d'autres fonctionnalités de D comme des [* templates]. Une approche encore plus simple serait de nommer chaque fonction différemment pour chaque type comme ``septFois_real()`` et ``septFois_double()``.
]
[ = Surcharge de fonctions pour les structures
[ = Surcharge de fonctions pour les types définis par l'utilisateur
La surcharge de fonctions est aussi utile pour les structures et les classes. Qui plus est, les ambigüités liées à la surcharge de fonctions sont bien moins fréquentes avec les types définis par l'utilisateur. Surchargeons la fonction ``info()`` ci-avant pour quelques-uns des types que nous avons définis dans le chapitre sur les structures~ :
La surcharge de fonctions s'utilise également avec les structures et les classes. Qui plus est, les ambigüités liées à la surcharge de fonctions sont bien moins fréquentes avec les types définis par l'utilisateur. Surchargeons la fonction ``info()`` précédente avec quelques-uns des types que nous avons définis dans le chapitre sur les structures~ :
[code=d <<<
struct MomentDeLaJournee
@ -114,7 +115,7 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
info(momentPetitDejeuner);
>>>]
Les objets ``MomentDeLaJournee`` correspondent à cette surcharge de ``info()``~ :
Les objets ``MomentDeLaJournee`` correspondent à cette nouvelle surcharge de ``info()``~ :
[output <<<
07:00
@ -149,7 +150,7 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
MomentDeLaJournee(9, 10));
>>>]
Resultat~ :
Résultat~ :
[output <<<
09:00-09:10 réunion "Cyclisme" avec 3 personnes
@ -158,9 +159,9 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
[ = Limitations
Bien que les surcharges ci-avant soient très pratiques, cette méthode a quelques limitations~ :
Bien que nos surcharges de la fonction [c info()] soient très pratiques, cette méthode présente quelques limitations~ :
* ``info()`` écrit systématiquement sur la sortie standard. Elle serait nettement plus utile si elle pouvait écrire sur n'importe quel fichier. Une solution pour cela serait de passer également le flux de sortie en paramètre. Par exemple, pour le type ``MomentDeLaJournee``~ :
* ``info()`` écrit systématiquement sur la sortie standard. Elle serait nettement plus utile si elle pouvait écrire dans n'importe quel fichier. Une solution pour cela serait de passer également le flux de sortie en paramètre. Par exemple, pour le type ``MomentDeLaJournee``~ :
[code=d <<<
void info(File fichier, in MomentDeLaJournee temps)
@ -169,30 +170,30 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
}
>>>]
Cela permettrait d'écrire des objets ``MomentDeLaJournee`` dans n'importe quel fichier, y compris la sortie standard:
Cela permettrait d'écrire des objets ``MomentDeLaJournee`` dans n'importe quel fichier, y compris la sortie standard~ :
[code=d <<<
info(stdout, tempsPetitDejeuner);
info(stdout, momentPetitDejeuner);
auto fichier = File("un_fichier", "w");
info(fichier, tempsPetitDejeuner);
info(fichier, momentPetitDejeuner);
>>>]
[p Note~ : | les objets spéciaux stdin, stdout et stderr sont de type File.]
[p Note~ : | les objets spéciaux ``stdin``, ``stdout`` et ``stderr`` sont de type ``File``.]
* Plus important~ : ``info()`` ne permet pas de produire une représentation textuelle de variables. Par exemple, elle ne permet pas de passer des types utilisateur à ``writeln()``~ :
[code=d <<<
writeln(tempsPetitDejeuner); // Écrit avec le format générique, inutile.
writeln(momentPetitDejeuner); // Écrit avec le format générique, inutile.
>>>]
Le code ci-avant affiche l'objet dans un format générique qui contient son type et les valeurs de ses membres, d'une manière qui n'est pas pertinent pour le programme~ :
Le code qui précède affiche l'objet dans un format générique qui contient son type et les valeurs de ses membres, d'une manière qui n'est pas pertinent pour le programme~ :
[code=d <<<
MomentDeLaJournee(7, 0)
>>>]
Il serait beaucoup plus utile d'avoir une fonction qui convertisse des objets MomentDeLaJournee en string dans leur format spécial "``12:34``". Nous verrons comment définir des représentations textuelles des objets structures dans le prochain chapitre.
Il serait beaucoup plus utile d'avoir une fonction qui convertisse des objets MomentDeLaJournee en [c string] dans leur format spécial "``12:34``". Nous verrons comment définir des représentations textuelles d'objets structures dans le prochain chapitre.
]
[ = Exercice
@ -203,14 +204,14 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
struct Repas
{
MomentDeLaJournee moment;
string adresse;
string adresse;
}
struct PlanningJournalier
struct planningDeLaJournee
{
Reunion reunionMatin;
Repas repas;
Reunion reunionApresMidi;
Reunion reunionApresMidi;
}
>>>]
@ -218,7 +219,7 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
[code=d <<<
MomentDeLaJournee ajouterDuree(in MomentDeLaJournee debut,
in MomentDeLaJournee duree)
in MomentDeLaJournee duree)
{
MomentDeLaJournee resultat;
@ -233,12 +234,12 @@ Néanmoins, s'il y a une autre surcharge de fonction prenant un paramètre ``lon
}
>>>]
Une fois que les moments de fin des objets repas seront calculés au moyen de ``ajouterDuree()``, les objets ``PlanningJournalier`` devront être affichés ainsi~ :
Une fois que les moments de fin des objets repas seront calculés au moyen de ``ajouterDuree()``, les objets ``planningDeLaJournee`` devront être affichés ainsi~ :
[output <<<
10:30-11:45 réunion "Cyclisme" avec 4 personnes
12:30-14:00 Repas, Adresse: İstanbul
15:30-17:30 réunion "Budget" avec 8 personnes
10:30-11:45 Réunion "Cyclisme" avec 4 personnes
12:30-14:00 Repas, adresse : Istamboul
15:30-17:30 Réunion "Budget" avec 8 personnes
>>>]
[[part:corrections/surcharge_fonctions | … La solution]]