Programmation D/Éléments de base du langage et programmation impérative

Les commentaires

modifier

J'ai remarqué dans l'exemple précédent // j'imprime sur la sortie standard. Que cela veut-il dire ? Ceci est un commentaire. En effet, vous pouvez laisser dans votre code des explications (c'est d'ailleurs très fortement recommandé). Il existe plusieurs formes de commentaires :

Commentaire sur une ligne

// Mon commentaire
/// Ceci est une documentation

Commentaires multi-lignes

/*
  Ceci est un commentaire
*/
/*
 * Ceci est un commentaire
 */
/+
  Ceci est un commentaire
+/
/+
 + Ceci est un commentaire
 +/
/**
  Ceci est une documentation
*/
/**
 * Ceci est une documentation
 */
/++
 + Ceci est une documentation
 +/

Vous remarquerez qu'il est parfois écrit « documentation » au lieu de « commentaire ». En effet, on peut documenter son code. Ainsi, un programme externe va lire votre code source et générer automatiquement la documentation. Pour les connaisseurs, c'est le même principe que la javadoc ou doxygen. Nous y reviendrons plus loin dans ce chapitre.

Les types primitifs

modifier

Note 1 : les valeurs suivantes ont été obtenues sur un système 64 bits.

Note 2 : vous pouvez générer ces résultats avec le programme D pour connaitre la taille des types.

Type Description Min Max
byte Valeur entière sur 1 octet (8 bits) -128 127
ubyte Valeur entière positive sur 1 octet (8 bits) 0 255
short Valeur entière sur 2 octets (16 bits) -32768 32767
ushort Valeur entière positive sur 2 octets (16 bits) 0 65535
int Valeur entière sur 4 octets (32 bits) -2147483648 2147483647
uint Valeur entière positive sur 4 octets (32 bits) 0 4294967296
long Valeur entière sur 8 octets (64 bits) -9223372036854775808 9223372036854775807
ulong Valeur entière positive sur 8 octets (64 bits) 0 18446744073709551615
float Valeur numérique sur 4 octets (32 bits) 1.18e-38 3.40e+38
ifloat Valeur numérique imaginaire pure sur 4 octets (32 bits) 1.18e-38i 3.40e+38i
double Valeur numérique sur 8 octets (64 bits) 2.23e-308 1.80e+308
idouble Valeur numérique imaginaire pure sur 8 octets (64 bits) 2.23e-308i 1.80e+308i
real Le plus grand numérique supporté par le processeur soit 16 octets (128 bits) 3.36e-4932 1.19e+4932
ireal Le plus grand numérique supporté par le processeur soit 16 octets (128 bits) 3.36e-4932i 1.19e+4932
char Un caractère imprimable encodé en UTF-8 sur 1 octet (8bits) 0 255
wchar Un caractère imprimable encodé en UTF-16 sur 2 octets (16bits) 0 65535
dchar Un caractère imprimable encodé en UTF-32 sur 4 octets (32bits) 0 4294967293
cfloat Nombre complexe de 2 valeurs flottantes (float) 1.18e-38 +1.18e-38i -nan
cdouble Nombre complexe de 2 doubles 2.23e-308 +2.23e-308i 1.19e+4932
creal Nombre complexe réel 3.36e-4932 +3.36e-4932i 1.19e+4932

Les opérateurs arithmétiques

modifier

+ permet d'additionner deux variables numériques. - permet de soustraire deux variables numériques. * permet de multiplier deux variables numériques. / permet de diviser deux variables numériques. % permet de renvoyer le reste de la division euclidienne de deux variables de type numérique, cet opérateur s'appelle le modulo.

Par exemple :

import tango.io.Stdout;
void main(){
   uint a = 2;
   uint b = 2;
   Stdout.formatln("une addition: {} + {} = {}", a, b, a+b);
   Stdout.formatln("une soustraction: {} - {} = {}", a, b, a-b);
   Stdout.formatln("une multiplication: {} x {} = {}", a, b, a*b);
   Stdout.formatln("une division: {} / {} = {}", a, b, a/b);
   Stdout.formatln("le modulo: {} % {} = {}", a, b, a%b);
}

Une histoire d'incrémentation et décrémentation

modifier

Dans la même catégorie, il existe l'incrémentation et la décrémentation :

  • l'incrémentation consiste à augmenter de 1 la valeur de la variable.
  • la décrémentation consiste à diminuer de 1 la valeur de la variable.

Pour cela, il y a plusieurs façons de le faire. Prenons le cas de l'incrémentation : int a = 2; a = a + 1; a += 1; a++; ++a Vous remarquez que les 2 dernières formes sont plus rapides à écrire (oui, le programmeur est un fainéant).

Y a t-il une différence entre ++a et a++ ?

Oui, il y en a une et elle est subtile :

  • a++ : utilise la valeur de a, puis l'incrémente
  • ++a: incrémente la valeur de a, puis l'utilise

Par exemple :

import tango.io.Stdout;
void main(){
   uint a = 2; 
   uint b = 1;
   Stdout.formatln("une incrémentation: {} + {} = {}", a, b, a++);
   a = 2;
   Stdout.formatln("une incrémentation: {} + {} = {}", a, b, ++a);
   a = 2;
   Stdout.formatln("une décrémentation: {} - {} = {}", a, b, a--);
   a = 2;
   Stdout.formatln("une décrémentation: {} - {} = {}", a, b, --a);
}

Autres raccourcis

modifier

On peut également utiliser les raccourcis pour la multiplication, la division et le reste de la division euclidienne. un court exemple :

uint a = 5;
a *= 2;
a /= 2;
a %= 2;
Attention
modifier

Je vous mets en garde sur le type utilisé. Ce dernier peut avoir des conséquences graves ! En effet, jusqu'ici on a utilisé le type uint et nos résultats étaient des entiers. Mais ceci n'est pas systématiquement vrai. Par exemple, diviser 1 par 3 donne un résultat décimal ne pouvant pas correspondre à un entier positif (uint). Par conséquent, vous devez utiliser un autre type plus approprié, comme le type float ou double :

double a = 1;
a /=3; // a vaut 0.33333333
int b = 1;
int c = 3;
double d = b / c; // d vaut 0.33333333

Les opérateurs de comparaison

modifier

Il est fréquent dans un programme que l'on ai de besoin de comparer. Pour cela, en D il est possible de tester différents cas :

  • tester une égalité
3 == 3; // cette expression retournera vrai
3 == 4; // cette expression retournera faux
  • tester une différence
int a = 4;
3 != a; // 3 est différent de 4 donc cette expression retournera vrai
  • comparer a ou b
a || b // si l'une des expressions est vraie, l'expression globale sera vraie
  • comparer a et b
a && b // si l'une des expressions est fausse, l'expression globale sera fausse
a < b // savoir si a est strictement plus petit que b

a <= b // savoir si a est plus petit ou égal à que b

a > b // savoir si a est strictement plus grand que b

a >= b // savoir si a est plus grand ou égal à b

a is b // savoir si a est identique à b

a !== b // savoir si a n'est pas identique à b

Test d'identité

modifier

Les comparateurs d'identités sont particuliers et méritent plus d'explications. Si l'on compare :

  • des types entre eux (par exemple int)
int[1] a = 2;
int[1] b = a;
a is b; // retournera vrai

Lorsque l'on compare des types avec ce comparateur, il agit comme si c'était le comparateur ==

  • des objets
Personne jean = new Personne("jean");
Personne paul = new Personne("paul");
jean is paul; // retournera faux

Lorsque l'on compare des objets entre eux, ce comparateur va dire si oui ou non c'est le même objet qui est référencé. On reviendra plus tard sur les références, gardez les à l'esprit et n'hésitez pas à revenir dessus.

Le comparateur !== est la négation du comparateur === donc

Personne jean = new Personne("jean");
Personne paul = new Personne("paul");
jean !is paul; // retournera vrai

Les conditions

modifier

Nous avons vu les opérateurs de comparaison : maintenant, nous allons les utiliser avec les conditions. Rien de plus simple ! Commençons de suite.

Avec les mots clés if et else

modifier
int a = 2;
if ( a == 2 ){ // test une égalité avec l'opérateur == , si a vaut 2
    Stdout("la variable a vaut bien 2").nl;
}
else if ( a == 3){ // sinon si la variable a vaut 3
    Stdout("la variable a vaut bien 3").nl;
}
else{ // sinon (pour tous les autres cas)
    Stdout.formatln("la variable a vaut {}",a);
}

C'était un exemple pour vous dégourdir les méninges. Complexifions légèrement avec divers exemples d'utilisation :

int a = 2;
int b = 3;

if ( a == 2 && b == 3 ){ // si a vaut 2 et b vaut 3. Les 2 conditions doivent être vraies pour rentrer dans le bloc
    Stdout.formatln("la variable a vaut {} et la variable b vaut {}", a, b);
}
if ( a == 2 || b ==3 ){ // si a vaut 2 ou bien si b vaut 3. Au moins une des 2 conditions doit être vraie pour rentrer dans le bloc
    Stdout.formatln("la variable a vaut {} et la variable b vaut {}", a, b);
}
if ( a > 1 && b >= 3 ){ // si a est strictement plus grande que 1 et b plus grande ou égale à 3. Les 2 conditions doivent être vraies pour rentrer dans le bloc
    Stdout.formatln("la variable a vaut {} et la variable b vaut {}", a, b);
}
if ( a <= 2 || b < 3){ // si a est plus petite ou égale à 2 ou bien si b est strictment plus petite que 3. Au moins une des 2 conditions doit être vraie pour rentrer dans le bloc
    Stdout.formatln("la variable a vaut {} et la variable b vaut {}", a, b);
}

Avec les mots clés switch et case

modifier

Vous avez vu précédemment la possibilité d'imbriquer des si - sinon de la manière suivante :

int a = 4;
if ( a == 0 ){

}
else if ( a == 1 ){

}
else if ( a == 2 ){

}
else if ( a == 3 ){

}
else if ( a == 4 ){

}
else {

}

Sachez qu'il existe une manière plus élégante d'écrire cela à l'aide des mots clés switch et case :

int a = 4;
switch (a){ // on met la variable à tester
    case 1: // cas où la valeur est 1
        
        break
    case 2: // cas où la valeur est 2
        
        break
    case 3: // cas où la valeur est 3
        
        break
    case 4: // cas où la valeur est 4
        
        break
    default: // cas où la valeur est différente
        
}

Pourquoi retrouve-t-on break à chaque fois ? Admettons, pour imager, 2 choses :

  1. que la variable a vaut 3
  2. que le mot clé break soit absent

D'après l'exemple précédent, on entrerait dans le bloc à partir du cas 3 et on effectuerait tous les cas suivants, c'est à dire ici le cas 4 et le cas par défaut. Dans la majeure partie des cas, on n'a pas besoin d'exécuter 2 règles, c'est pour cela que l'on met l'instruction break afin qu'il quitte après avoir effectué son traitement. Un dernier exemple du même type pour la route :

int a = 1;
switch (a) { // on met la variable à tester
    case 1: // cas où la valeur est 1
        
    case 2: // cas où la valeur est 2
        
    case 3: // cas où la valeur est 3
        
        break
    case 4: // cas où la valeur est 4
        
        break
    default: // cas où la valeur est différente
        
}

Ici, on va rentrer dans le cas 1, 2 et 3 puis sortir.

Ce comportement est similaire en C et en C++. Mais en D, on peut également utiliser des chaînes de caractères :

char[] prenom = "jonathan";
switch (prenom){ // on met la variable à tester
    case "jonathan": // cas où la valeur est "jonathan"
        
        break
    case "jean": // cas où la valeur est "jean"
        
        break
    case "paul": // cas où la valeur est "paul"
        
        break
    case "remi": // cas où la valeur est "remi"
        
        break
    default: // cas où la valeur est différente
        
}

Les tableaux

modifier

Déclaration

modifier

Il y a 5 façons de déclarer un tableau :

Code Description
int* p;
Pointeur vers les données
int[3] s;
Tableau statique
int[] a;
Tableau dynamique
int[char[]] x;
Tableau associatif
int[][] m;
Matrice

Les pointeurs

modifier
int* p;

Ceci est un simple pointeur vers des données[1]., il représente la même chose qu'en C.


Attention cependant, contrairement au C, le code suivant

int* p1, p2;

déclare deux pointeurs vers des entiers, alors qu'en C, il déclare un pointeur et un entier ! Pour cette raison, il est d'usage de toujours rattacher l'astérisque au type de données plutôt qu'au nom de la variable.

Les tableaux statiques

modifier
int[3] s;

Un tableau statique est un tableau dont on spécifie la taille lors de sa déclaration, taille qui ne changera plus. Ici, on crée un tableau dans lequel on peut stocker 3 entiers. La taille d'un tableau statique est fixée au moment de la compilation.

Par exemple :

int[3] s;
s[0] = 50;
s[1] = 24;
s[2] = 98

Les tableaux dynamiques

modifier
int[] a;

Un tableau dynamique est un tableau dont la taille peut varier. Lors de sa déclaration, on ne spécifie pas sa taille. En fait, c'est le ramasse-miette (garbage collector) qui gère ce tableau au moyen d'un pointeur.

Exemple :

int[] a;
a.length = 2; // on donne une taille de 2
a[0] = 5;
a[1] = 8;
a.length = a.length + 5 // ici 2+5 = 7 soit la nouvelle taille du tableau
a[2] = 5;
a[3] = 7;
a[4] = 9;
a[5] = 3
a[6] = 1;

Il est conseillé de faire le minimum de redimensionnements de tableau possibles afin de garder de bonnes performances. Ainsi, on évitera :

int[] a;
for ( i = 0 , i < 50, i++) {
    a.length = a.length + 1;
    a[i] = 0;
}

On utilisera de préférence :

int[] a;
for ( i = 0 , i < 50, i++){
    if (a.length == i) {
        a.length = a.length * 2; // si la taille du tableau est égale à l'indice i, on double sa taille;
    }
    a = 0;
}
a.length = i; // Afin d'économiser la mémoire, on ajuste exactement la taille du tableau au besoin soit la valeur de l'indice i

Les tableaux associatifs

modifier
int[char[]] x;

Un tableau associatif est un tableau où l'on associe 2 valeurs ensemble. Ici, on associe une chaîne de caractères à un entier.

Astuce: pour comprendre ce que que représente un tableau associatif, je vous conseille de le lire de la droite vers la gauche. Soit :

Le tableau associatif x contient des chaînes de caractères qui sont associées à un entier

Exemple :

int[char[]] x;
x["pomme"] = 2;
x["poire"] = 5;
x["orange"] = 7;

Dans l'exemple ci-dessus, on associe un fruit à un nombre (pratique pour connaitre la quantité restante de chaque fruit).

Les propriétés

modifier
Propriétés des tableaux statiques
Propriétés Descriptions
.sizeof Retourne la taille du tableau multipliée par le nombres d'octets pour chaque élément du tableau
.length Retourne le nombre d'éléments dans le tableau. Cette valeur ne peut pas être modifiée pour les tableaux statiques
.ptr Retourne un pointeur sur le premier élément du tableau
.reverse Inverse l'ordre des élements (le premier devient le dernier) et renvoie le tableau inversé
.sort Trie les éléments du tableau et renvoie le tableau trié
.dup Crée un tableau dynamique de la même taille et copie tous les éléments dans ce tableau puis renvoie ce tableau
Propriétés des tableaux dynamiques
Propriétés Descriptions
.sizeof Retourne la taille de la réference du tableau dynamique, qui est de 8 sur un ordinateur 32 bits
.length Permet d'obtenir ou de changer la taille du tableau
.ptr Retourne un pointeur sur le premier élément du tableau
.reverse Inverse l'ordre des élements (le premier devient le dernier) et retourne le tableau inversé
.sort Trie les éléments du tableau et renvoie le tableau trié
.dup Crée un tableau dynamique de la même taille, et copie tous les éléments dans ce tableau puis retourne ce tableau

Le slicing

modifier

Le slicing est une technique particulière qui permet de copier une série d'éléments d'un tableau dans un autre. Voici un exemple de cette technique :

int[10] a = [1,2,3,4,5,6,7,8,9,10]; // déclare un tableau de 10 entiers
int[] b; // déclare un tableau dynamique
int[] c; // déclare un tableau dynamique
int[3] d; // déclare un tableau statique de 3 éléments
b = a[1..3]; // le fameaux slicing, on copie ici les éléments des indices 1 à 3 dans le tableau b. Rapel: le 1er indice est le 0 a[0]
c = a[4..$]; // ici on copie tous les éléments à partir de l'indice 4 jusqu'à la fin
d[0..2] = 5; // ici on assigne la valeur 5 à tous les éléments du tableau. Cela équivaut à d[0] = 5 d[1] = 5 d[2] = 5

Concaténation de tableaux

modifier
int[10] a = [1,2,3,4,5,6,7,8,9,10]; // déclare un tableau de 10 entiers
int[10] b = [11,12,13,14,15,16,17,18,19,20]; // déclare un tableau de 10 entiers
int[] c;
c = a ~ b; // soit c contient 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20

On peut combiner le slicing et la concaténation :

int[10] a = [1,2,3,4,5,6,7,8,9,10]; // déclare un tableau de 10 entiers
int[10] b = [11,12,13,14,15,16,17,18,19,20]; // déclare un tableau de 10 entiers
int[] c;
c = a ~ b[0..5]; // soit c contient 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

Les matrices

modifier

Peu de langages supportent nativement les matrices, mais le langage D en fait partie.

Qu'est-ce qu'une matrice ?

Mieux qu'un long discours voici un exemple :

[1, 1, 1]
[1, 1, 1]
[1, 1, 1]
[1, 1, 1]

Ceci est une matrice de 3 colonnes par 4 lignes :

uint[3][4] matrix; // déclaration de la matrice tous les élément de la matrice sont à 0
foreach (ligne; matrix)
{
   ligne[0..$] = 1;// on remplit la matrice de 0 en utilisant le slicing, c'est pratique !
}

Les chaînes de caractères

modifier

Les langages de programmation doivent bien gérer les chaînes de caractères. Le C et le C++ ne sont pas très bons pour cela. La première difficulté est de gérer la mémoire, les traitements temporaires, de constamment scanner les chaînes de caractères pour rechercher la terminaison par "\0" et de fixer la taille.

Les tableaux dynamiques en D suggèrent la solution la plus évidente. Une chaîne est simplement un tableau dynamique de caractères. Les chaînes littérales sont juste un moyen facile d'écrire des tableaux de caractères.

char[] str = "Le D c'est génial"; // déclaration et initialisation
char[] str1 = str.dup; // copie de la chaîne de caractères
str += " et je l'ai adopté"; // on ajoute à la suite de la chaîne str
char str2[] = "Vive le D";
char str3 = str ~ str2; // on concatène str et str2, grâce au ~ et on stocke le tableau résultant dans str3

Par défaut les chaînes de caractère sont encodées en utf8. Il existe également les types:

  • wchar
  • wchar[]
  • dchar
  • dchar[]

Si une chaîne est ambigüe et peut correspondre à plusieurs types, il faut préciser.

Si on veut de l'UTF-16, on peut faire un cast (voir « Le transtypage (cast) ») :

(wchar [])"J'aime le D";

Ou si on veut de l'UTF-32 :

(dchar[])"J'aime le D";
char c;
wchar u;
dchar a;
c = "b"; // le caractère b (UTF-8) est assigné à c
u = "b" // le caractère b (UTF-16) est assigné à u
a = "b" // le caractère b (UTF-32) est assigné à a
u = \n; // un retour à la ligne est assigné à u

Les tableaux associatifs

modifier
À faire... 

à déplacer ?

Le langage D intègre nativement les tableaux associatifs. Précédemment, vous avez eu un court exemple de leur utilisation. Voici quelques autres exemples :

int[char[]] b; // association d'une chaîne de caractère à un entier
b["pomme"] = 3 // on met 3 pommes
uint nombre =  b["pomme"]; // renvoie le nombre de pommes
delete  b["pomme"]; // on supprime la clé "pomme"
Propriété
Propriétés Descriptions
.sizeof Renvoie la taille de la référence vers le tableau associatif
.length Renvoie le nombre de valeurs dans le tableau associatif. À la différence des tableaux dynamiques, on ne peut pas changer sa valeur.
.keys Renvoie un tableau dynamique contenant les clés du tableau associatif
.values Renvoie un tableau dynamique contenant les valeurs du tableau associatif
.rehash Réorganise le tableau associatif pour gagner en performance. Retourne une référence du tableau réorganisé.

Dépassement de la taille du tableau

modifier

Le dépassement de la taille limite du tableau constitue une erreur fréquemment rencontrée. Le compilateur vous annoncera « Array index out of bounds ». Vous apprendrez plus loin comment attraper et gérer les exceptions.

Important

modifier

En D 1.x comme dans d'autres langages tels que le Python, les tableaux et les objets sont passés par référence et non par copie.

Qu'est-ce que cela veut dire ?

Pour faire simple, une référence est une entité qui pointe vers son original. Si l'on modifie la référence, cela modifie l'original. C'est un comportement qui permet d'éviter de recopier un tableau (ce qui peut être long) mais qui, si l'on ne fait pas attention, peut générer des erreurs. Gardez bien cela à l'esprit, c'est capital !

Si le besoin d'une copie et non d'une référence se fait sentir, ils vous suffit de la demander avec la propriété dup :

int [5] a = 3; // les 5 colonnes valent 3
int [] b = a; // b est une référence de a, si je modifie b le tableau a sera modifié
int [] c = a.dup; // c est une copie de a, je peux modifier c sans modifier a
Exemple
modifier
import tango.io.Stdout;

void ajoute(uint nombre, uint[] tableau)
{
    foreach(indice,element;tableau){
        tableau[indice] = element + nombre;
    }
}
void main()
{
    uint[] montableau = [1,2,3,4,5,6];
    ajoute(2, montableau);
    Stdout(montableau).nl;
}

Vous voyez votre tableau a été modifié à votre insu, vous avez sur le terminal :

[3, 4, 5, 6, 7, 8]

Vous vous retrouvez avec le tableau modifié par la fonction ajoute, si vous ne voulez pas ce comportement vous devez envoyer une copie du tableau, voici en exemple:

import tango.io.Stdout;

void ajoute(uint nombre, uint[] tableau)
{
    foreach(indice,element;tableau){
        tableau[indice] = element + nombre;
    }
}
void main()
{
    uint[] montableau = [1,2,3,4,5,6];
    ajoute(2, montableau.dup);
    Stdout(montableau).nl;
}

Ce qui donne :

[1, 2, 3, 4, 5, 6]

Si le besoin d'une copie et non d'une référence se fait sentir, ils vous suffit de la demander avec la propriété dup :

int [5] a = 3; // les 5 colonnes valent 3
int [] b = a; // b est une référence de a, si je modifie b le tableau a sera modifié
int [] c = a.dup; // c est une copie de a, je peux modifier c sans modifier a

Attention

modifier

À partir de la version 2 du langage, les tableaux statiques sont passés par valeur ! Ce changement permet une meilleure vectorisation du code (notamment pour l'utilisation par le compilateur d'instructions comme les SSE) et facilite la programmation concurrente. Pour porter du code D1 vers D2 et revenir à l'ancien comportement, il suffira de passer un slice complet du tableau en paramètre, les slices, comme les tableaux dynamiques, étant toujours passés par référence :

// CODE D 2.0 !
int[4] tableauStatique;
int[] tableauDynamique = new int[4];

fonction(tableauStatique);   // passage par valeur !
fonction(tableauStatique[]); // avec un slice, on passe par référence, comme en D1
fonction(tableauDynamique);  // passage par référence, ça ne change pas entre D1 et D2

Les boucles

modifier

Tant que (while)

modifier
import tango.io.Stdout

void main()
{
    uint = 0;
    while (i < 10)
    {
        Stdout(i).nl;
        i++;
    }
}

Tant que i est plus petit que 10, on boucle et on imprime sur la sortie standard la valeur courante de i.

Faire tant que (do … while)

modifier

Cette boucle est semblable à la précédente, seulement ici on garantit au moins une fois le passage dans la boucle.

import tango.io.Stdout

void main()
{
    uint i = 10;
    do{
        Stdout(i).nl;
        i++;
    }
    while(i < 10)
}

Pour (for)

modifier

On peut effectuer la déclaration d'une variable, définir les conditions de la boucle et définir une action exécutée à chaque début de boucle (généralement l'incrémentation de cette variable) dans la déclaration de la boucle for :

for (uint i = 0; i < 10; ++i)
{
    Stdout(i).nl;
}

Pour chaque (foreach)

modifier
uint[5] a = [1,5,4,6,8];
foreach(element;a)
{
     Stdout(element).nl;
}

Pour chaque élément de a, on imprime sa valeur sur la sortie standard.

On peut également connaître le nombre d'itérations dans la boucle foreach. Par exemple, connaître le numéro de ligne ou l'indice du tableau en cours de traitement.

uint[5] a = [1,5,4,6,8];
foreach(compteur,element;a) // Notons que element est de même type que a, ici de type uint[] 
{
     Stdout.formatln("numéro {} valeur {}",compteur, element);
}

Aller à (goto)

modifier

Le goto n'est à utiliser que dans des cas précis. Il ne faut surtout pas en abuser et la plupart du temps les autres types de boucles suffisent. Depuis le temps que je programme je n'ai eu à l'utiliser qu'une seule fois. Le goto permet d'aller directement à un endroit du code défini par une étiquette :

import tango.io.Stdout;

void main()
{
    uint i = 0;
    Stdout("Bonjour").nl;
    monEtiquette:
    i++;
    Stdout.formatln("Valeur de i {}", i);
    if ( i < 2 )
    {
        goto monEtiquette;
    }
    Stdout("Fin").nl;
}

Le transtypage (cast)

modifier

En langage D, il existe 2 manières d'effectuer une conversion de type :

  • avec le mot clé cast
  • avec le module tango.util.Convert

La première façon de faire est générique et permet de caster (convertir) tout et n'importe quoi. C'est l'équivalent du dynamic_cast pour les connaisseurs du C++. La seconde manière est restreinte au type cité dans un chapitre précédent mais est plus sûre. Exemples :

import module tango.util.Convert;

short a = 1;
float b = 0.5;
int c = to!(int)(a); // on utilise le template nommé "to" provenant du module Convert et on spécifie le type int
int d = cast(int)a; 
double e = to!(double)(b); // on utilise le template nommé "to" provenant du module Convert et on spécifie le type double
double f = cast(double)(b);

On utilise le module Convert uniquement pour utiliser le template "to" (le mot clé cast n'a besoin d'aucun module).

Lire les entrées clavier

modifier
import tango.io.Console;

void main()
{
    Cout("Entrer votre nom: ")();
    char[] nom = Cin.get;
    Cout("Salut ")(nom).newline;
}

Demander le nom et l'âge :

import tango.io.Console;
import tango.util.Convert;
import tango.io.Stdout;

void main()
{
    Cout("Entrer votre nom: ")();
    char[] nom = Cin.get;
    Cout("Entrer votre age: ")();
    uint age = to!(uint)(Cin.get);
    Stdout.formatln("Salut {} tu as donc {} ans", nom , age);    
}

On doit convertir ce qui a été saisie par l'utilisateur (char[]) en uint.

Parser les arguments de la ligne de commande

modifier

La bibliothèque tango propose un module très pratique pour parser les arguments de la ligne de commande. Il existe un tutoriel en anglais ici. Je vais vous présenter la manière dont je l'utilise :

import text.Arguments  : Arguments;
import tango.io.Stdout : Stdout, Stderr;
import tango.text.Regex: Regex;
import monApplication.Parser;

bool verbose = false;
const float numVersion = 0.1;
void verboseMode(char[] message)
{
    if (verbose)
        Stdout.formatln("\033[0;31m'{}'\033[0;0m",message);
}
void about()
{
    Stdout.formatln("monApplication version \033[0;31m'{}'\033[0;0m", numVersion);
    Stdout.formatln("monApplication est un super programme écrit en D.");
}
void usage(Arguments getopt)
{
    Stdout.formatln("\033[0;31mUsage:\033[0;0m ./monApplication [options] --input files");
    Stdout.formatln("Options:");
    getopt.help((char[] param, char[] text){Stdout.format("\t--{}    {}\n",param, text);});
}
int main (char[][] args)
{
    char[] inputFile;
    char[] outputFile;
    
    /**
     * Definit les options du programme
     */
    auto getopt = new Arguments;
    getopt("help").params(0).aliased('h').help("- Display this message");
    getopt("verbose").params(0).aliased('v').help("- Enable verbose mode");
    getopt("input").params(1).aliased('i').help("- Path to input file");
    getopt("output").params(1).aliased('o').help("- Path to output file");
    getopt("version").params(0).help("- Display metatool version");
    /**
     * Parse la ligne de commande
     */
     getopt.parse(args);
     /**
      * Active ou non le mode verbeux
      */
    if (getopt("verbose").set)
    {
        verbose = true;
    }
    /**
     * Affiche l'aide si demandé
     */
    if (getopt("help").set)
    {
        usage(getopt);
    }
    /**
     * Affiche la version si demandée
     */
    else if (getopt("version").set)
    {
        about();
    }
    /**
     * Affiche un message d'erreur et quitte le programme si l'option --input n'est pas presente
     */
    else if (!getopt("input").set)
    {
        Stderr.formatln("\033[0;31mWarning:\033[0;0m option --input absent!");
        usage(getopt);
        scope(failure) Stderr.formatln("Programme quit subitement");
    }
    else{
        /**
         * Stocke le nom du fichier soumis par l'option --input
         */
        inputFile = getopt("input").assigned[0];
        verboseMode("Input file: "~inputFile);

        /**
         * Stocke le nom du fichier soumis par l'option  --output
         */
        if (getopt("output").set){
            outputFile = getopt("output").assigned[0];
        }
        else{ // Si l'option --output est absente, il prend le nom du fichier "input" et remplace l'extention par .out
            auto filePattern= new Regex(r"\.[a-zA-Z0-9_]+");
            if (filePattern.test(inputFile)){
                outputFile = filePattern.replaceLast(inputFile,".out");
            }
            else{
                outputFile = inputFile ~ ".out";
            }
        }
        verboseMode("Output file: "~outputFile);
        /**
         * Parse le fichier input
         */
        auto parser = new Parser(inputFile);
        parser.parse();
        parser.check();
        
    }
    scope(exit) Stdout.formatln("quit");
    scope(success) Stdout.formatln("Programme se termine avec succès");
    return 1;
}

  1. Si vous n'êtes pas familier avec les pointeurs, vous pourrez en apprendre plus dans La mémoire, une question d'adresse sur le site du zéro