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

Cours complet pour apprendre à programmer en D


précédentsommairesuivant

21. Chaînes de caractères

Nous avons utilisé des chaînes de caractères dans beaucoup de programmes que nous avons vus jusqu'à maintenant. Les chaînes sont une combinaison de deux fonctionnalités que nous avons couvertes dans les trois derniers chapitres : les caractères et les tableaux. Dans la définition la plus simple, les chaînes ne sont rien d'autre que des tableaux de caractères. Par exemple,

char[]

est un type de chaîne.

Cette simple définition peut être trompeuse. Comme nous l'avons vu dans le chapitre sur les caractèresCaractères, D a trois types de caractères distincts, dont certains peuvent avoir des résultats surprenants dans certaines opérations sur les chaînes.

21-1. readln et chomp, à la place de readf

Il y a des surprises même quand on lit des chaînes depuis la console.

Étant des tableaux de caractères, les chaînes peuvent contenir des caractères de contrôle comme '\n'. Lorsqu'on lit des chaînes de caractères depuis l'entrée, le caractère de contrôle qui correspond à la touche Entrée sur laquelle on appuie à la fin d'une saisie dans la console fait partie de la chaîne également. De plus, à cause de l'impossibilité de dire à readf() combien de caractères il faut lire, readf() continue à lire jusqu'à la fin de l'entrée. De ce fait, read() ne marche pas comme attendu quand on lit des chaînes :

 
Sélectionnez
import std.stdio;
 
void main()
{
   char[] nom;
 
   write("Quel est votre nom ? ");
   readf(" %s", &nom);
 
   writeln("Salut ", nom, "!");
}

La touche entrée sur laquelle l'utilisateur appuie après le nom ne termine pas l'entrée. readf() continue à attendre plus de caractères à ajouter à la chaîne :

 
Sélectionnez
Quel est votre nom ? Mert
   ← L'entrée n'est pas terminée alors même que la touche Entrée a été enfoncée.
   ← (Supposons que Entrée est enfoncée une nouvelle fois ici)

Une manière de terminer le flux d'entrée standard est d'appuyer sur Ctrl+D sous les systèmes de type Unix et Ctrl+Z sous les systèmes Windows. Si l'utilisateur finit l'entrée de cette manière, on voit que les caractères de nouvelle ligne ont été lus également :

 
Sélectionnez
Salut Mert
    ← nouvelle ligne après le nom
!(encore un avant le point d'exclamation)

Le point d'exclamation apparaît après ces caractères au lieu d'être affiché juste après le nom.

readln() convient mieux à la lecture de chaînes. Abréviation de read line (lire ligne), readln() lit jusqu'à la fin de la ligne. Il est utilisé différemment parce que la chaîne de formatage " %s" et l'opérateur & ne sont pas nécessaires :

 
Sélectionnez
import std.stdio;
 
void main()
{
   char[] nom;
 
   write("Quel est votre nom ? ");
   readln(nom);
 
   writeln("Salut ", nom, " !");
}

readln() enregistre le caractère de nouvelle ligne également ; ceci, pour que le programme ait un moyen de déterminer si l'entrée contient une ligne complète ou si la fin de l'entrée a été atteinte :

 
Sélectionnez
Quel est votre nom ? Mert
Salut Mert
 ! ← Caractère de nouvelle ligne avant le point d'exclamation

De tels caractères de contrôle, qui sont situés à la fin des chaînes, peuvent être supprimés par std.string.chomp.

 
Sélectionnez
import std.stdio;
import std.string;
 
void main()
{
   char[] nom;
 
   write("Quel est votre nom ? ");
   readln(nom);
   nom = chomp(nom);
 
   writeln("Salut ", nom, " !");
}

L'expression chomp() ci-devant retourne une nouvelle chaîne qui ne contient pas les caractères de contrôle de la fin de la chaîne. Réaffecter la valeur de retour à nom donne le résultat attendu :

 
Sélectionnez
Quel est votre nom ? Mert
Salut Mert !                   # ← pas de nouvelle ligne

readln() peut être utilisé sans paramètre. Dans ce cas, readln() retourne la ligne qui vient d'être lue. Chaîner le résultat de readln() et chomp() permet une syntaxe plus lisible et plus courte :

 
Sélectionnez
string nom = chomp(readln());

Après avoir introduit le type string, j'utiliserai cette syntaxe.

21-2. Guillemets doubles, et non simples

Nous avons vu que les guillemets simples sont utilisés pour définir des caractères littéraux. Les chaînes littérales sont définies avec des guillemets doubles. 'a' est un caractère ; "a" est une chaîne qui ne contient qu'un caractère.

21-3. string, wstring, et dstring sont immuables (immutable)

Il y a trois types de chaînes qui correspondent aux trois types de caractères : char[], wchar[] et dchar[].

Il y a trois alias des versions immuables de ces types : string, wstring et dstring. Les caractères de ces variables qui sont définies avec ces alias ne peuvent pas être modifiés. Par exemple, les caractères d'un wchar[] peuvent être modifiés, mais les caractères d'une wstring ne peuvent pas l'être (nous verrons l'immuabilité en D ultérieurement).

Par exemple, le code suivant, qui essaie de capitaliser la première lettre d'une string ne compile pas :

 
Sélectionnez
string nePeutEtreMutée = "salut";
nePeutEtreMutée[0] = 'S'; // ERREUR DE COMPILATION

On pourrait penser à définir la variable en tant que char[] au lieu de l'alias string, mais ceci ne compilerait pas non plus :

 
Sélectionnez
char[] a_tranche = "hello";   // ERREUR DE COMPILATION

Cette fois, l'erreur de compilation est due à la combinaison de deux facteurs :

  1. Le type des chaînes littérales comme "hello" est string, et non char[], elles sont donc immuables.
  2. Le char[] sur le côté gauche est une tranche qui donnerait accès, si le code était compilé, à tous les caractères du côté droit.

Comme char[] est mutable et que string ne l'est pas, il y a conflit. Le compilateur ne permet pas d'accéder aux caractères d'un tableau immuable par une tranche « mutable ».

La solution ici est de faire une copie de la chaîne immuable avec la propriété .dup :

 
Sélectionnez
import std.stdio;
 
void main()
{
   char[] s = "salut".dup;
   s[0] = 'S';
   writeln(s);
}

Le programme peut maintenant être compilé et afficher la chaîne modifiée :

 
Sélectionnez
Salut

De façon similaire, char[] ne peut pas être utilisé là où une string est nécessaire. Dans de tels cas, la propriété .idup peut être utilisée pour produire une variable string immuable à partir d'une variable mutable char. Par exemple, si s est une variable du type char[], la ligne suivante ne peut pas être compilée :

 
Sélectionnez
string resultat = s ~ '.'; // ERREUR DE COMPILATION

Quand le type de s est char[], le type de l'expression à droite de l'affectation ci-dessus est char[] également. .idup est utilisé pour obtenir des chaînes immuables à partir de chaînes existantes :

 
Sélectionnez
string resultat = (s ~ '.').idup;   // ← maintenant, compile

21-4. Confusion potentielle sur la taille des chaînes

Nous avons vu que certains caractères Unicode sont représentés par plus d'un octet. Par exemple, la lettre 'é' est représentée par deux octets. On remarque cela avec la propriété .length des chaînes :

 
Sélectionnez
writeln("résumé".length);

Même si « résumé » contient 6 lettres, la taille de la chaîne est le nombre de caractères qu'elle contient : 8

Le type des éléments de chaînes littérales comme "hello" est char et char représente une unité de stockage UTF-8. Cela peut engendrer un problème quand on essaie de remplacer une lettre à deux unités de stockage par une lettre avec une seule unité de stockage :

 
Sélectionnez
char[] s = "résumé".dup;
writeln("Avant : ", s);
s[1] = 'e';
s[5] = 'e';
writeln("Après : ", s);

Les deux caractères 'e' ne remplacent pas les deux lettres 'é' ; ils remplacent des unités de stockage uniques, ce qui conduit à un codage UTF-8 incorrect :

Sortie :

 
Sélectionnez
Avant : résumé
Après : re�sueé   ← INCORRECT

Quand on s'occupe des lettres, des symboles ou autres caractères Unicode directement comme dans le code ci-dessus, le type à utiliser est dchar :

 
Sélectionnez
dchar[] s = "résumé"d.dup;
writeln("Before: ", s);
s[1] = 'e';
s[5] = 'e';
writeln("After : ", s);

Sortie :

 
Sélectionnez
Avant : résumé
Après : resume

Notez les deux différences dans le nouveau code :

  1. Le type de chaîne est dchar[].
  2. Il y a un d à la fin du littéral "résumé"d, indiquant que c'est un tableau de dchars.

21-5. Chaînes littérales

Le caractère optionnel qui est indiqué après les chaînes littérales détermine le type d'élément de la chaîne :

 
Sélectionnez
import std.stdio;
 
void main()
{
   string s = "résumé"c;   // équivalent à "résumé"
   wstring w = "résumé"w;
   dstring d = "résumé"d;
 
   writeln(s.length);
   writeln(w.length);
   writeln(d.length);
}

Sortie :

 
Sélectionnez
8
6
6

Parce que toutes les lettres de "résumé" peuvent être représentées par un seul wchar ou dchar, les deux dernières longueurs sont égales au nombre de lettres.

21-6. Concaténations de chaînes

Comme les chaînes sont en fait des tableaux, toutes les opérations sur les tableaux peuvent être appliquées sur les chaînes également. ~ concatène deux chaînes et ~= ajoute à une nouvelle chaîne :

 
Sélectionnez
import std.stdio;
import std.string;
 
void main()
{
   write("Quel est votre nom ? ");
   string nom = chomp(readln());
 
   // Concaténer :
   string salutation = "Salut " ~ nom;
 
   // Ajouter :
   salutation ~= " ! Bienvenue...";
 
   writeln(salutation);
}

Sortie :

 
Sélectionnez
Quel est votre nom ? Can
Salut Can ! Bienvenue...

21-7. Comparaison de chaînes

Unicode ne définit pas comment les caractères sont ordonnés autrement que par leurs codes Unicode. Pour cette raison, vous pouvez obtenir des résultats qui ne correspondent pas à vos attentes. Quand l'ordre alphabétique est important, vous pouvez utiliser une bibliothèque du type trileri qui prend en charge l'idée d'alphabet.

Jusqu'à maintenant, nous n'avons utilisé les opérateurs de comparaison <, >=, etc. qu'avec les entiers et les flottants. Les mêmes opérateurs peuvent être utilisés avec les chaînes également, mais avec une signification différente : les chaînes sont ordonnées de façon lexicographique. Cet ordre considère le code Unicode de chaque caractère comme étant la place de ce caractère dans un alphabet fictif géant :

 
Sélectionnez
import std.stdio;
import std.string;
 
void main()
{
   write("      Entrez une chaîne : ");
   string s1 = chomp(readln());
 
   write("Entrez une autre chaîne : ");
   string s2 = chomp(readln());
 
   if (s1 == s2) {
      writeln("Ce sont les mêmes !");
 
   } else {
      string avant;
      string apres;
 
      if (s1 < s2) {
            avant = s1;
            apres = s2;
 
      } else {
            avant = s2;
            apres = s1;
      }
 
      writeln("'", avant, "' vient avant '", apres, "'.");
   }
}

Du fait qu'Unicode reprend la table ASCII pour les lettres de base de l'alphabet latin, les chaînes qui ne contiennent que des caractères ASCII sont ordonnées correctement.

21-8. Majuscule et minuscule sont différentes

Du fait que chaque lettre a un code unique, chaque lettre est différente des autres. Par exemple, 'A' et 'a' sont des lettres différentes.

De plus, du fait de leur code ASCII, les lettres majuscules sont ordonnées avant les lettres minuscules. Par exemple, 'B' est avant 'a'. La fonction icmp() du module std.string peut être utilisée quand les chaînes doivent être comparées sans tenir compte des majuscules et des minuscules. Vous pouvez voir les fonctions de ce module dans sa documentation.

Du fait que les chaînes sont des tableaux (et donc des intervalles), les fonctions des modules std.array, std.algorithm, et std.range sont très utiles avec les chaînes également.

21-9. Exercices

  • Parcourez la documentation des modules std.string, std.array, std.algorithm et std.range.
  • Écrivez un programme qui utilise l'opérateur ~ : l'utilisateur entre le prénom et le nom en minuscules et le programme donne le nom complet qui contient la bonne capitalisation des noms. Par exemple, quand les chaînes sont « ebru » et « domates », le programme devrait afficher « Ebru Domates ».
  • Lisez une ligne depuis l'entrée et affichez la partie entre le premier et le dernier 'e' de la ligne. Par exemple, quand la ligne est « cette ligne a 5 mots », le programme devrait afficher « cette ligne ».

     
    Sélectionnez
    sizediff_t premier_e = indexOf(ligne, 'e');
  • Il est possible de définir des variables de façon plus concise avec le mot-clé auto, que nous verrons dans un chapitre ultérieur :
 
Sélectionnez
auto premier_e = indexOf(ligne, 'e');

Les solutions Chaînes de caractères - Correction .


précédentsommairesuivant