Cours complet pour apprendre à programmer en D


précédentsommairesuivant

19. Caractères

Les caractères sont des briques élémentaires des chaînes. Chaque symbole d'un système d'écriture est appelé caractère : les lettres des alphabets, les chiffres, les signes de ponctuation, l'espace, etc. Les briques élémentaires des caractères elles-mêmes sont appelées caractères également, ce qui entraîne certaines ambiguïtés.

Les tableaux de caractères constituent les chaînes. Nous avons vu les tableaux dans le chapitre précédent ; les chaînes de caractères seront vues dans deux chapitres.

Comme n'importe quelle autre donnée, les caractères sont aussi représentés par des valeurs entières composées de bits. Par exemple, la valeur entière d'un 'a' minuscule est 97 et la valeur entière du chiffre '1' est 49. Ces valeurs ont été choisies principalement par convention quand la table ASCII a été écrite.

Dans la plupart des langages, les caractères sont représentés par le type char, qui ne peut stocker que 256 valeurs distinctes. Si vous êtes déjà familier-ère avec le type char dans d'autres langages, vous savez déjà probablement que ce n'est pas assez pour supporter les glyphes de beaucoup de systèmes d'écriture. Avant de parler des trois types de caractères du D, faisons un peu d'histoire sur les caractères dans les systèmes d'information.

19-1. Histoire

19-1-1. Table ASCII

La table ASCII a été écrite à une époque où le matériel informatique était très limité comparé aux systèmes modernes. Basée sur 7 bits, la table ASCII peut représenter 128 codes différents. C'est suffisant pour représenter des caractères comme les versions minuscules et majuscules des 26 lettres de l'alphabet latin, les chiffres, les signes de ponctuation couramment utilisés, et quelques caractères de contrôle pour le terminal.

Par exemple, les codes ASCII des caractères de la chaîne "hello" sont les suivants : 104, 101, 108, 108, 111.

Chaque code ci-dessus représente une lettre de "hello". Par exemple, il y a deux codes 108 pour les deux lettres 'l'. (Note : l'ordre réel de ces caractères dépend de la plateforme et même du document duquel ces valeurs font partie. Les codes ci-dessus sont dans l'ordre dans lequel ils apparaissent dans la chaîne.)

Les codes de la table ASCII ont ensuite été écrits sur 8 bits pour donner la table ASCII Étendue. La table ASCII Étendue contient 256 valeurs distinctes.

19-1-2. Pages de code IBM

IBM Corporation a défini un ensemble de tables, chacune d'elles affectant les codes de 128 à 256 de la table ASCII étendue à un ou plusieurs systèmes d'écriture. Ces tables de code ont permis de prendre en charge les lettres de beaucoup plus d'alphabets. Par exemple, les lettres spéciales de l'alphabet turc font partie de la page de code IBM 857.

Bien qu'elles soient plus utiles qu'ASCII, les pages de code ont des problèmes et des limitations : pour afficher le texte correctement, la page de code utilisée pour l'écrire doit être connue. En effet, le même code correspond à un autre caractère dans la plupart des autres tables. Par exemple, le code qui représente 'Ğ' dans la table 857 correspond à 'ª' dans la table 437.

Un autre problème est la limitation du nombre d'alphabets qui peuvent être utilisés au sein d'un même document. De plus, les alphabets qui ont plus de 128 caractères non ASCII ne peuvent pas être pris en charge par une table IBM.

19-1-3. Pages de code ISO/IEC 8859

Ces pages de code sont le résultat d'efforts de standardisation internationaux. Elles sont similaires aux pages de code IBM dans leur manière d'associer des caractères aux codes. Par exemple, les lettres spécifiques à l'alphabet turc apparaissent dans la page de code 8859-9. Ces tables ont les mêmes problèmes et limitations que les tables d'IBM. Par exemple, le digramme néerlandais ij n'apparaît dans aucune de ces tables.

19-1-4. Unicode

Unicode résout tous les problèmes et les limitations des solutions précédentes. Unicode s'étend à plus de 100 milliers de caractères et symboles de systèmes d'écriture de beaucoup de langages humains, présents et passés (des nouveaux caractères sont constamment passés en revue pour être ajoutés à la table). Chacun de ces caractères a un code unique. Les documents qui sont codés en Unicode peuvent utiliser tous les caractères des différents systèmes d'écriture en même temps sans aucune ambiguïté ni limitation.

19-2. Codages Unicode

Unicode associe un unique code à chaque caractère. Comme il y a plus de caractères Unicode que ce que peut contenir une valeur 8 bits, certains caractères doivent être représentés par au moins deux valeurs 8-bits. Par exemple, le code Unicode de 'Ğ' (286) est plus grand que la valeur maximale d'un type 8 bits (255).

Je vais utiliser 1 et 30 comme les valeurs des deux octets qui représentent Ğ de façon arbitraire. Ils ne sont valides dans aucun codage Unicode, mais utiles pour introduire ces codages Unicode. Les valeurs correctes de ces valeurs sont hors de la portée de ce chapitre.

La manière dont les caractères sont représentés électroniquement est appelée codage. Nous avons vu ci-dessus comment la chaîne "hello" est représentée en ASCII. Nous allons maintenant voir les trois codages Unicode qui correspondent aux types de caractères D.

  • UTF-32 : ce codage utilise 32 bits (4 octets) pour chaque caractère Unicode. Le codage UTF-32 de la chaîne "hello" est similaire à son codage ASCII, mais chaque caractère est représenté par 4 octets : 0, 0, 0, 104 ; 0, 0, 0, 101 ; 0, 0, 0, 108 ; 0, 0, 0, 108 ; 0, 0, 0, 111.
    Autre exemple : le codage UTF-32 de « aĞ » est 0, 0, 0,97 ; 0,0, 1, 30.
    Note : les valeurs réelles de ces octets sont différentes et l'ordre réel de ces octets peut être différent.
    'a' et 'Ğ' sont représentés par 1 et 2 octets significatifs respectivement, et les valeurs des 5 autres octets sont toutes zéro. Ces zéros peuvent être vus comme des octets de remplissage qui font que chaque caractère Unicode occupe 4 octets.
    Pour les documents écrits avec l'alphabet latin de base, ce codage utilise toujours quatre fois plus d'octets que le codage ASCII. Quand, dans un document donné, la plupart des caractères ont des équivalents ASCII, les trois octets de remplissage pour chacun de ces caractères rendent ce codage moins efficace que les autres codages.
    D'un autre côté, il y a des avantages à ce que chaque caractère soit toujours représenté par le même nombre d'octets.
  • UTF-16 : ce codage utilise 16 bits (2 octets) pour représenter la plupart des caractères Unicode. Comme 16 bits peuvent représenter environ 65 milliers de valeurs uniques, les autres 35 milliers caractères Unicode doivent être représentés par des octets supplémentaires.
    Par exemple, "aĞ" est codé avec 4 octets en UTF-16 : 0, 97 ; 1, 30.
    Note : les valeurs réelles de certains de ces octets sont différentes et l'ordre réel des octets peut être différent.
    Comparé à UTF-32, ce codage prend moins d'espace pour la plupart des documents, mais parce qu'il y a des caractères qui sont représentés par plus de deux octets. UTF-16 est plus compliqué à traiter.
  • UTF-8 : ce codage utilise un ou plusieurs octets pour chaque caractère. Si un caractère a un équivalent dans la table ASCII, il est représenté par un octet et par le même code que dans la table ASCII. Les autres caractères Unicode sont représentés par 2, 3 ou 4 octets. La plupart des caractères spéciaux des systèmes d'écriture européens font partie du groupe de caractères qui sont représentés par 2 octets.
    Pour la plupart des documents, UTF-8 est le codage qui prend le moins de place. Un autre bénéfice d'UTF-8 est que les documents qui ont déjà été codés en ASCII correspondent directement à leur codage UTF-8 respectif. UTF-8 ne gaspille pas d'espace : chaque caractère est représenté par des octets significatifs uniquement. Par exemple, le codage UTF-8 de "aĞ" est : 97, 1, 30.
    Note : les valeurs réelles de ces octets sont différentes et leur ordre peut être différent également.

19-3. Les types de caractères du D

Il y a trois types de caractères en D. Ces caractères correspondent aux trois codages Unicode mentionnés ci-avant. D'après ce que vous vous souvenez du chapitre sur les types fondamentauxTypes fondamentaux.

Type

Définition

Valeur initiale

char

Unité de stockage UTF-8

0xFF

wchar

Unité de stockage UTF-16

0xFFFF

dchar

Unité de stockage UTF-32 et point de code Unicode

0x0000FFFF

Comparé à d'autres langages de programmation, les caractères en D peuvent ne pas avoir le même nombre d'octets. Par exemple, parce que Ğ ne peut être représenté que par 2 octets au minimum dans Unicode, il ne rentre pas dans une variable de type char. En revanche, le type dchar, faisant 4 octets, peut stocker n'importe quel caractère Unicode.

Même si le D propose ces types utiles, le D ne supporte pas certaines fonctionnalités ésotériques d'Unicode. J'y reviens après.

19-4. Caractères littéraux

Les littéraux sont des valeurs constantes qui sont écrits dans le code source du programme. En D, les caractères littéraux sont écrits entres apostrophes :

 
Sélectionnez
char  letter_a = 'a';
wchar letter_e_acute = 'é';

Les guillemets ne sont pas valides pour les caractères parce qu'ils sont utilisés quand on écrit des chaînes, que nous verrons dans deux chapitres. 'a' est un caractère littéral et "a" est une chaîne littérale constituée d'un caractère.

Les variables de type char ne peuvent stocker que des lettres qui sont dans la table ASCII.

Il y a beaucoup de manières d'insérer des caractères dans le code :

  • le plus naturellement, en les tapant sur le clavier ;
  • en les copiant-collant depuis un autre programme ou un autre texte. Par exemple, vous pouvez copier-coller depuis un site Web, ou depuis un programme conçu pour afficher des caractères (on trouve de tels programmes dans la plupart des environnements Linux sous le nom de « Table de Caractères ») ;
  • en utilisant les noms raccourcis des caractères. La syntaxe, pour le faire, est : \&nom_du_caractere;. Par exemple, le nom du caractère Euro est euro et peut être écrit dans le programme comme ceci :

     
    Sélectionnez
    wchar currencySymbol = '\€';
  • voir la liste de tous les caractères nommés qui peuvent être écrits de cette manière ;

  • en indiquant les caractères par leur numéro Unicode :

     
    Sélectionnez
        char a = 97;
        wchar Ğ = 286;
  • en spécifiant les codes des caractères de la table ASCII soit par \valeur_en_octal soit par \xvaleur_en_hexadécimal :
 
Sélectionnez
    char questionMarkOctal = '\77';
    char questionMarkHexadécimal = '\x3f';

Ces méthodes peuvent également être utilisées pour écrire des caractères dans les chaînes. Par exemple, les deux lignes qui suivent contiennent la même chaîne :

 
Sélectionnez
writeln("Résumé préparation: 10,25€");
writeln("\x52\ésum\u00e9 pr\u00e9paration: 10,25\€");

19-5. Caractères de contrôle

Certains caractères ne font qu'affecter le format du texte, ils n'ont pas de représentation visuelle propre. Par exemple, le caractère de nouvelle ligne, qui indique que la sortie devrait continuer sur une nouvelle ligne, n'a pas de représentation visuelle. De tels caractères sont appelés caractères de contrôle. Les caractères de contrôle sont écrits avec la syntaxe \caractère_de_contrôle.

Syntaxe

Nom

Effet

\n

nouvelle ligne

Déplace l'affichage sur une nouvelle ligne

\r

etour chariot

Déplace l'affichage au début de la ligne actuelle

\t

tab

Déplace l'affichage à la prochaine tabulation

Par exemple, la fonction write, qui ne commence pas de nouvelle ligne automatiquement, le ferait pour chaque caractère \n. Chaque occurrence du caractère de contrôle \n à l'intérieur du littéral suivant représente le début d'une nouvelle ligne :

 
Sélectionnez
write("première ligne\ndeuxième ligne\ntroisième ligne\n");

Sortie :

 
Sélectionnez
première ligne
deuxième ligne
troisième ligne

19-6. Guillemet simple et antislash

Le guillemet simple lui-même ne peut pas être écrit à l'intérieur de guillemets simples parce que le compilateur prendrait le deuxième guillemet comme le caractère qui fermerait le premier : '''. Les deux premiers seraient les guillemets ouvrant et fermant, et le troisième serait tout seul, entraînant une erreur de compilation (NDT De plus, n'écrire aucun caractère entre les deux guillemets simples est illégal).

De manière similaire, on ne peut pas écrire '\' pour désigner le caractère antislash. Comme l'antislash a une signification spéciale, le compilateur prendrait \' comme un caractère spécial. Le compilateur chercherait ensuite un guillemet fermant et ne le trouverait pas.

Pour éviter ces confusions, le guillemet simple et l'antislash sont échappés par un antislash :

caractère

Syntaxe

Nom

'

\'

guillemet simple

\

\\

antislash (contre-oblique)

19-7. Le module std.uni

Le module std.uni inclut des fonctions pour manipuler les caractères Unicode. Vous pouvez voir ces fonctions dans la documentation de ce module.

Les fonctions qui commencent par is répondent à certaines questions sur les caractères : le résultat est false ou true selon que la réponse est non ou oui (respectivement). Ces fonctions sont utiles dans des expressions logiques :

  • isLower : le caractère est-il en minuscule ?
  • isUpper : le caractère est-il en majuscule ?
  • isAlpha : le caractère est-il alphabétique au sens d'Unicode ? (une lettre) ;
  • isWhite : le caractère est-il blanc ? (espace, nouvelle ligne, tabulation…).

Les fonctions qui commencent par to renvoient de nouveaux caractères à partir de caractères existants :

  • toLower : renvoie le caractère minuscule correspondant au caractère donné ;
  • toUpper : renvoie le caractère majuscule correspondant au caractère donné ;

Voici un programme qui utilise toutes ces fonctions :

 
Sélectionnez
import std.stdio;
import std.uni;
 
void main()
{
   writeln("Est-ce que ğ est minuscule ? ", isLower('ğ'));
   writeln("Est-ce que Ş est minuscule ? ", isLower('Ş'));
 
   writeln("Est-ce que Ş est minuscule ? ", isUpper('İ'));
   writeln("Est-ce que ç est majuscule ? ", isUpper('ç'));
 
   writeln("Est-ce que z est alphanumérique ? ", isAlpha('z'));
   writeln("Est-ce que  est alphanumérique ? ", isAlpha('\€'));
 
   writeln("Est-ce que la nouvelle ligne est un caractère blanc ? ", isWhite('\n'));
   writeln("Est-ce que le tiret du bas est un caractère blanc ? ", isWhite('_'));
 
   writeln("La minuscule de Ğ : ", toLower('Ğ'));
   writeln("La minuscule de İ : ", toLower('İ'));
 
   writeln("La majuscule de ş : ", toUpper('ş'));
   writeln("La majuscule de ı : ", toUpper('ı'));

Sortie :

 
Sélectionnez
Est-ce que ğ est minuscule ? true
Est-ce que Ş est minuscule ? false
Est-ce que İ est majuscule ? true
Est-ce que ç est majuscule ? false
Est-ce que z est alphanumérique ? true
Est-ce que € est alphanumérique ? false
Est-ce que la nouvelle ligne est un caractère blanc ? true
Est-ce que le tiret du bas est un caractère blanc ? false
La minuscule de Ğ : ğ
La minuscule de İ : i
La majuscule de ş : Ş
La majuscule de ı : I

19-7-1. Prise en charge limitée pour ı et i de l'alphabet turc

Les versions minuscules et majuscules des lettres ı et i sont « pointées » ou non de façon cohérente dans l'alphabet turc. La majorité des alphabets sont incohérents de ce point de vue : la majuscule du i « pointé » n'est pas « pointée ».

Parce que les systèmes informatiques ont commencé avec la table ASCII, la majuscule de i est I. Pour cette raison, ces deux lettres ont besoin d'une attention particulière. Le programme suivant montre ce problème :

 
Sélectionnez
import std.stdio;
import std.uni;
 
void main()
{
   writeln("La majuscule de i : ", toUpper('i'));
   writeln("La minuscule de I : ", toLower('I'));
}

Sortie :

 
Sélectionnez
La majuscule de i : I
La minuscule de I : i

19-7-2. Tous les alphabets ont une prise en charge limitée

Les caractères sont transformés en majuscules/minuscules selon leur code Unicode. Cette méthode est problématique pour beaucoup d'alphabets. Par exemple, les alphabets azéri et celte sont aussi affectés par le problème de la minuscule 'I' étant 'i'.

Il y a des problèmes similaires avec le tri. Les lettres de beaucoup d'alphabets, comme le ğ en turc, sont placées après le z. Même les caractères accentués comme á sont placés après le z, et ce, même pour l'alphabet latin basique.

19-8. Problème de lecture des caractères

La souplesse et la puissance des caractères Unicode en D peuvent être la source de résultats inattendus lors de la lecture d'un flux d'entrée. Cette contradiction est due aux multiples sens du terme « caractère ». Avant de développer plus ce point, considérons un programme qui a ce problème :

 
Sélectionnez
import std.stdio;
 
void main()
{
   char lettre;
   write("Veuillez entrer une lettre : ");
   readf(" %s", &lettre);
   writeln("La lettre suivante a été lue : ", lettre);
}

Si vous essayez ce programme dans un environnement qui n'utilise pas Unicode, vous pouvez voir que même les caractères non Unicode sont lus et affichés correctement.

Cependant, si vous démarrez ce programme dans un environnement Unicode, par exemple une console sous Linux, vous pouvez voir que le caractère affiché à la sortie n'est pas le même caractère que celui qui a été entré. Pour voir ceci, entrons un caractère non ASCII dans une console qui utilise le codage UTF-8 (comme la plupart des consoles sous Linux) :

Sortie :

 
Sélectionnez
Veuillez entrer une lettre : ğ
La lettre suivante a été lue :     ← Pas de lettre à la sortie

La raison de ce problème est que les caractères non ASCII tels que le ğ sont représentés par deux codes, et lire un char depuis l'entrée ne lit que le premier des deux octets. Comme ce seul caractère n'est pas suffisant pour représenter le caractère Unicode entier, la console n'affiche pas le caractère incomplet.

Pour voir que les codes UTF-8 qui constituent le caractère sont lus char par char, lisons deux char et affichons-les l'un après l'autre :

 
Sélectionnez
import std.stdio;
 
void main()
{
   char premierCode;
   char secondCode;
 
   write("Veuillez entrer une lettre : ");
   readf(" %s", &premierCode);
   readf(" %s", &secondCode);
 
   writeln("La lettre suivante a été lue : ",
         premierCode, secondCode);
}

Le programme lit deux variables char depuis l'entrée et les affiche dans le même ordre. Quand ces codes sont envoyés à la console dans le même ordre, ils correspondent à un caractère UTF-8 entier sur la console et cette fois, le caractère Unicode est affiché correctement.

Sortie :

 
Sélectionnez
Veuillez entrer une lettre : ğ
La lettre suivante a été lue : ğ ← Le caractère Unicode qui
                                   consiste en deux codes char

Ces résultats sont dépendants aussi du fait que les entrées-sorties standard des programmes sont des flux de char.

Nous verrons plus tard dans le chapitre sur les chaînesChaînes de caractères qu'il est plus facile de lire des caractères comme des chaînes, plutôt que s'occuper individuellement des codes UTF-8.

19-9. Prise en charge de l'Unicode par le langage D

Unicode est un standard vaste et compliqué. D prend en charge un sous-ensemble utile de celui-ci.

Dans un document codé en Unicode, on distingue plusieurs idées.

  • Unité de stockage (code unit) : les valeurs qui constituent les codages UTF sont appelées unités de stockage. Selon le codage et les caractères eux-mêmes, les caractères Unicode sont faits d'une ou plusieurs unités de stockage. Par exemple, dans le codage UTF-8, la lettre 'a' est faite d'une seule unité de stockage et la lettre 'ğ' est faite de deux unités de stockage.
    Les types char, wchar, et dchar du D correspondent respectivement à une unité de stockage UTF-8, UTF-16, et UTF-32.
  • Point de code (code point) : chaque lettre, chiffre, symbole, etc. qu'Unicode définit est appelé un point de code. Par exemple, les codes Unicode de 'a' est de 'ğ' sont deux points de code distincts.
    Selon le codage, les points de code sont représentés par une ou plusieurs unités de stockage. Comme mentionné ci-avant, dans le codage UTF-8 'a' est représenté par une unité de stockage et 'ğ' est représenté par deux unités de stockage. En revanche, 'a' et 'ğ' sont représentés par une seule unité de stockage dans les codages UTF-16 et UTF-32.
    Le type D qui prend en charge les points de code est dchar. char et wchar ne peuvent être utilisés que pour les unités de stockage.
  • Caractère : n'importe quel symbole que le standard Unicode définit et que nous appelons « caractère » dans la vie de tous les jours est un caractère.
    La définition Unicode de caractère est flexible et ceci complique les choses. Certains caractères peuvent être formés d'un ou plusieurs points de code. Par exemple, la lettre ğ peut être désignée de deux manières :

    • comme simple point de code 'ğ' ;
    • comme les deux points de code 'g' et '̆ '. (combining breve).

D ne prend pas nativement en charge le concept de point de code combiné. En D, le point de code ğ est différent des deux points de code consécutifs 'g' et '̆ '.

19-10. Résumé

  • Unicode supporte tous les caractères de tous les systèmes d'écriture.
  • Char est pour le codage UTF-8 ; même s'il ne convient pas pour représenter les caractères en général, il prend en charge la table ASCII.
  • wchar est pour l'encodage UTF-16 ; même s'il ne convient pas pour représenter les caractères en général, il peut prendre en charge une multitude d'alphabets.
  • dchar est pour le codage UTF-32 ; il peut également être utilisé pour les points de code parce qu'il est 32 bits.

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+