programmez-en-d/chaines.whata

352 lines
14 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "Chaînes de caractères"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
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 3 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, [c <<<char[]>>>] est un type de chaîne.
Cette simple définition peut être trompeuse. Comme nous l'avons vu dans le [[part:caracteres | chapitre sur les caractères]], D a trois types de caractères distincts, dont certain peuvent avoir des résultats surprenants dans certaines opérations sur les chaînes.
[ = [c readln] et [c chomp], à la place de [c 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 [c '\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 à [c readf()] combien de caractères il faut lire, [c readf()] continue à lire jusqu'à la fin de l'entrée. De ce fait, [c read()] ne marche pas comme attendu quand on lit des chaînes~ :
[code=d <<<
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. [c readf()] continue à attendre plus de caractères à ajouter à la chaîne~ :
[output <<<
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 sur les systèmes Windows. Si l'utilisateur finit l'entrée de cette manière, on voit que les caractères de nouvelle ligne on été lus également~ :
[output <<<
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.
[c readln()] convient mieux à la lecture de chaînes. Abréviation de [i read line] (lire ligne), [c readln()] lit jusqu'à la fin de la ligne. Il est utilisé différemment parce que la chaîne de formatage [c " %s"] et l'opérateur [c &] ne sont pas nécessaires~ :
[code=d <<<
import std.stdio;
void main()
{
char[] nom;
write("Quel est votre nom ? ");
readln(nom);
writeln("Salut ", nom, " !");
}
>>>]
[c 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~ :
[code <<<
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 [c std.string.chomp].
[code=d <<<
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 [c chomp()] ci-avant 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 à [c nom] donne le résultat attendu~ :
[code=bash <<<
Quelle est votre nom ? Mert
Salut Mert ! # ← pas de nouvelle ligne
>>>]
[c readln()] peut être utilisé sans paramètre. Dans ce cas, [c readln()] retourne la ligne qui vient d'être lue. Chaîner le résultat de [c readln()] et [c chomp()] permet une syntaxe plus lisible et plus courte~ :
[code=d <<<
string nom = chomp(readln());
>>>]
Après avoir introduit le type [c string], j'utiliserai cette syntaxe.
]
[ = 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. [c 'a'] est un caractère~ ; [c "a"] est une chaîne qui ne contient qu'un caractère.
]
[ = [c string], [c wstring], et [c dstring] sont immuables (''immutable'')
Il y a trois types de chaînes qui correspondent aux trois types de caractères~ : [c char~[~]], [c wchar~[~]] et [c dchar~[~]].
Il y a trois [* alias] des versions immuables de ces types : [c string], [c wstring] et [c 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 [c wchar~[~]] peuvent être modifiés mais les caractères d'une [c 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 [c string] ne compile pas~ :
[code=d <<<
string nePeutEtreMutée = "salut";
nePeutEtreMutée[0] = 'S'; // ERREUR DE COMPILATION
>>>]
On pourrait penser à définir la variable en tant que [c char~[~]] au lieu de l'alias [c string] mais ceci ne compilerait pas non plus~ :
[code=d <<<
char[] a_tranche = "hello"; // ERREUR DE COMPILATION
>>>]
Cette fois, l'erreur de compilation est due à la combinaison de deux facteurs~ :
# Le type des chaînes littérales comme [c "hello"] est [c string], et non [c char~[~]], elles sont donc immuables.
# Le [c 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 [c char~[~]] est mutable et que [c string] ne l'est pas, il y a conflit. Le compilateur ne permet pas d'accéder aux caractères d'un tableaux immuable par une tranche «~ mutable~ ».
La solution ici est de faire une copie de la chaîne immuable avec la propriété [c .dup]~ :
[code=d <<<
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~ :
[output Salut]
De façon similaire, [c char~[~]] ne peut pas être utilisé là ou une [c string] est nécessaire. Dans ce tels cas, la propriété [c .idup] peut être utilisée pour produire une variable [c string] immuable à partir d'une variable mutable [c char[]]. Par exemple, si [c s] est une variable du type [c char~[~]], la ligne suivante ne peut être compilée~ :
[code=d <<<
string resultat = s ~ '.'; // ERREUR DE COMPILATION
>>>]
Quand le type de [c s] est [c char~[~]], le type de l'expression à droite de l'affectation ci-dessus est [c char~[~]] également. [c .idup] est utilisé pour obtenir des chaînes immuables à partir de chaînes existantes~ :
[code=d <<<
string resultat = (s ~ '.').idup; // ← maintenant, compile
>>>]
]
[ = 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é [c .length] des chaînes~ :
[code=d <<<
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~ :
[output 8]
Le type des éléments de chaînes littérales comme [c "hello"] est [c char] et [c 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~ :
[code=d <<<
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 lettre 'é'~ ; ils remplacent des unités de stockage uniques, ce qui conduit à un codage UTF-8 incorrect~ :
[output <<<
Avant : résumé
Après : re<72>sueé ← INCORRECT
>>>]
Quand on s'occupe des lettres, des symboles ou autre caractères unicodes directement comme dans le code ci-dessus, le type à utiliser est [c dchar]~ :
[code=d <<<
dchar[] s = "résumé"d.dup;
writeln("Before: ", s);
s[1] = 'e';
s[5] = 'e';
writeln("After : ", s);
>>>]
[output <<<
Avant : résumé
Après : resume
>>>]
Notez les deux différences dans le nouveau code~ :
# Le type de chaîne est [c dchar~[~]].
# Il y a un ``d`` à la fin du littéral [c "résumé"d], indiquant que c'est un tableau de [c dchar]s.
]
[ = 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~ :
[code=d <<<
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);
}
>>>]
[output <<<
8
6
6
>>>]
Parce que toutes les lettres de [c "résumé"] peuvent être représentés par un seul [c wchar] ou [c dchar], les deux dernières longueurs sont égales au nombre de lettres.
]
[ = Concaténations de chaînes
Comme les chaînes sont en fait des tableaux, toute les opérations sur les tableaux peuvent être appliquées sur les chaînes égalament. [c ~~] concatène deux chaînes et [c ~~=] ajoute à une nouvelle chaîne~ :
[code=d <<<
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);
}
>>>]
[output <<<
Quel est votre nom ? Can
Salut Can ! Bienvenue...
>>>]
]
[ = Comparaison de chaînes
[p Notes~ : | 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 [[http://code.google.com/p/trileri/ | trileri]] qui prend en charge l'idée d'alphabet.]
Jusqu'à maintenant, nous n'avons utilisé les opérateurs de comparaison [c <], [c > >=], 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~ :
[code=d <<<
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.
]
[ = 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 function [c icmp()] du module [c 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 [[http://dlang.org/phobos/std_string.html | sa documentation]].
Du fait que les chaînes sont des tableaux (et donc des ''intervalles''), les fonctions des modules [c std.array], [c std.algorithm], et [c std.range] sont très utiles avec les chaînes également.
]
[ = Exercices
# Parcourez la documentation des modules [c std.string], [c std.array], [c std.algorithm] et [c std.range].
# Écrivez un programme qui utilise l'opérateur [c ~~]~ : l'utilisateur entre le prénom et le nom en minuscule 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 «~ ette ligne~ ».
Les fonctions [c indexOf()] et [c lastIndexOf()] sont utiles pour obtenir les deux indices pour créer une tranche.
Comme indiqué dans leur documentation, les types de retour de [c indexOf()] et [c lastIndexOf()] ne sont pas [c int] ni [c size_t], mais [c sizediff_t]. Vous pouvez avoir besoin de définir des variables de ce type~ :
[code=d <<<
sizediff_t premier_e = indexOf(ligne, 'e');
>>>]
Il est possible de définir des variables de façon plus concise avec le mot-clé [c auto], que nous verrons dans un chapitre ultérieur~ :
[code=d <<<
auto premier_e = indexOf(ligne, 'e');
>>>]
]
[[part:corrections/chaines | … Les solutions]]
]