3. Chaînes de caractères

3.1. Chaînes en C et C++

3.1.1. Cas du langage C

En langage C, une chaîne de caractères est représentée par un tableau de caractères, sachant qu'un caractère est représenté par un octet. La fin de la chaîne est marquée par le caractère de fin de chaîne '\0', soit la valeur 0 codée sur 8 bits.

Les caractères sont entourés de guillemets simples (en anglais simple quotes) alors que les chaînes sont entourées de guillemets doubles (double quotes).

Certains caractères sont dits spéciaux comme la fin de ligne ou la tabulation et sont représentés par un caractère anti-slash suivi d'une lettre ou d'un chiffre :

La chaine "bonjour" est donc modélisée en mémoire par $8$ octets :

Le premier caractère 'b' se trouve à l'indice $0$ du tableau, et le dernier, '\0', se trouve à l'indice $7$.

Voici un petit programme qui montre comment manipuler les chaînes en C :

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4.  
  5. /* ------------------------------------------------------------------
  6.     QUOI
  7.        Fonction principale
  8.        
  9.    ------------------------------------------------------------------ */
  10. int main() {
  11.  
  12.     const char *chaine = "bon\tjour\n";
  13.    
  14.     // Affiche la chaine avec printf
  15.     printf("%s", chaine );
  16.     // Calcul et affichage de la longueur de la chaine grâce à strlen
  17.     unsigned int longueur = static_cast<unsigned int>(strlen( chaine ));
  18.     printf("longueur de la chaine: %u\n", longueur );
  19.    
  20.     // Affichage du premier caractère de la chaîne : 'b'
  21.     printf("premier caractère: %d %c\n", chaine[0],
  22.         chaine[0] );
  23.     // Affichage du dernier caractère de la chaîne, en l'occurrence '\n'
  24.     printf("dernier caractère: %d %c\n", chaine[longueur-1],
  25.         chaine[longueur-1] );
  26.    
  27.     return EXIT_SUCCESS;
  28. }
  29.  
  30.  

Le résultat au niveau du terminal donne :

bon    jour
longueur de la chaine: 9
premier caractère: 98 b
dernier caractère: 10 

3.1.2. Cas du langage C++

Afin de faciliter l'utilisation des chaînes en C++, la classe string a été introduite.

Elle dispose de méthodes comme size() qui retourne la longueur de la chaîne. On a également introduit la concaténation de chaînes grâce à la redéfinition de l'opérateur +.

Voici le même programme que précédemment mais en C++ avec la concaténation de chaînes :

  1. #include <iostream>
  2. #include <string>
  3.  
  4. /* ------------------------------------------------------------------
  5.     QUOI
  6.        Fonction principale
  7.        
  8.    ------------------------------------------------------------------ */
  9.  
  10. int main() {
  11.  
  12.   std::string chaine = "bon\tjour\n";
  13.  
  14.   // Affichage de la chaîne
  15.   std::cout << chaine << std::endl;
  16.   // Calcul de affichage de la longueur
  17.   size_t longueur = chaine.size();
  18.   std::cout << "longueur de la chaine: " << longueur << std::endl;
  19.  
  20.   // Affichage du premier caractère de la chaîne : 'b'
  21.   std::cout << "premier caractère: " << static_cast<int>(chaine[0])
  22.     << " " << chaine[0] << std::endl;
  23.   // Affichage du dernier caractère de la chaîne, en l'occurrence '\n' 
  24.   std::cout << "dernier caractère: " << static_cast<int>(chaine[longueur-1])
  25.     << " " << chaine[longueur-1] << std::endl;
  26.  
  27.   // Concaténation de chaînes
  28.   std::string chaine_1 = "bon";
  29.   std::string chaine_2 = "jour";
  30.  
  31.   std::string chaine_finale = chaine_1 + " " + chaine_2;
  32.   std::cout << chaine_finale << std::endl;
  33.  
  34.   return EXIT_SUCCESS;
  35. }
  36.  
  37.  

3.2. Représentation ASCII

L'ASCII pour American Standard Code for Information Interchange est une norme de codage des caractères apparue dans les années 1960. Elle est utilisée pour représenter les chaînes de caractères.

La première norme est l'ASCII 7 bits qui permet de représenter 128 caractères date du début des années 1960.

L'ASCII a ensuite été étendu à 8 bits, soit 256 caractères, pour deux raisons :

  • les systèmes informatiques des années 1970 sont passés à la manipulation d'octets (8 bits)
  • on ne pouvait pas coder des textes dans d'autres langues, on a donc ajouté des caractères accentués du français ou d'autres langues (Norvégien, Allemand, Espagnol, etc)

On trouvera une description plus détaillée sur cette page Wikipedia.

On notera que la distance entre les majuscules et minuscules est de 32. Ainsi pour transformer 'A' en 'a', il suffit d'ajouter 32 au code ASCII de 'A'. Du point de vue du binaire, il suffit de positionner le bit 5 à 1, puisque $2^5 = 32$.

Voici un programme C++ qui transforme une chaîne en majuscule et minuscule.

  1. #include <iostream>
  2. #include <string>
  3.  
  4.  
  5. int main() {
  6.     std::string ma_chaine = "Bonjour, le monde !";
  7.    
  8.     // ----------------------------------------------------
  9.     // Convertir en majuscule
  10.     // On parcourt les caractères de la chaîne et si le
  11.     // caractère est une lettre minuscule, on la convertit
  12.     // en majuscule en lui enlevant 32
  13.     // ----------------------------------------------------
  14.     for (size_t i = 0; i < ma_chaine.length(); ++i) {
  15.         char c = ma_chaine[i];
  16.         if ((c >= 'a') and (c <= 'z')) {
  17.             ma_chaine[i] = static_cast<char>(ma_chaine[i] - 32);
  18.             // ou
  19.             ma_chaine[i] &= ~32;
  20.         }
  21.     }
  22.     std::cout << "Chaine en majuscules: " << ma_chaine << std::endl;
  23.  
  24.     // ----------------------------------------------------
  25.     // Convertir en minuscule
  26.     // On parcourt les caractères de la chaîne et si le
  27.     // caractère est une lettre majuscule, on la convertit
  28.     // en minuscule en lui ajoutant 32
  29.     // ----------------------------------------------------
  30.     for (size_t i = 0; i < ma_chaine.length(); ++i) {
  31.         char c = ma_chaine[i];
  32.         if ((c >= 'A') and (c <= 'Z')) {
  33.             ma_chaine[i] = static_cast<char>(ma_chaine[i] + 32);
  34.             // ou
  35.             ma_chaine[i] |= 32;
  36.         }
  37.     }
  38.     std::cout << "Chaine en minuscules: " << ma_chaine << std::endl;
  39.    
  40.     return EXIT_SUCCESS;
  41. }
  42.  

3.3. Unicode et représentation UTF

Le problème de l'ASCII est qu'il ne permet de coder que 256 caractères différents ce qui est insuffisant au regard de toutes les langues qui existent ainsi que des symboles (mathématiques, physique, chimie) que l'on peut utiliser dans l'écriture courante.

Le standard Unicode dans sa version 13 permet de coder $143\_859$ caractères ce qui couvre la presque totalité des caractères connus.

On pourra trouver l'équivalence des premiers caractères sur cette page.

L'encodage par octet, UTF-8 Universal Character Set (Transformation Format), a été conçu pour coder des chaînes à la manière de ce que l'on peut faire avec l'ASCII et est très utilisé par le protocole HTML et les éditeurs de texte. Il existe trois codages UTF dits UTF-8, 16 ou 32.

Le standard Unicode définit des ensembles de caractères et chaque caractère est identifié par son point de code qui est en fait un indice entier. Par exemple le symbole € a pour point de code la valeur 8364 soit U+20AC en hexadécimal dans le standard Unicode.

L'UTF permet de transformer le point de code des caractères Unicode en une série d'octets.

L'intérêt majeur de l'UTF-8 est qu'il est rétro-compatible avec l'ASCII 7 bits :

En UTF-32 chaque caractère est codé par une valeur 32 bits ce qui prend plus de place que l'UTF-8. Comme on peut le voir ci-dessous.

Imaginons que notre fichier contienne 5 lettres puis un passage à la ligne (↵) :


aéïou↵

Voici les différents codages obtenus grâce aux utilitaires linux iconv, konwert et hexdump pour l'affichage. Il est à noter que iconv ne parvient pas à réaliser la transformation vers l'ASCII semble t-il, je l'ai donc réalisée moi-même.

Les codages sont donnés en octets en base 16 :

ASCII
00000000  61 82 8b 6f 75 0a                                     |aeiou.|
00000006

UNICODE
00000000  ff fe |61 00| e9 00| ef 00|  6f 00| 75 00| 0a 00      |..a.....o.u...|
0000000e

UTF8
00000000  61| c3 a9| c3 af| 6f| 75| 0a                          |a....ou.|
00000008

UTF-16
00000000  ff fe| 61 00| e9 00| ef 00|  6f 00| 75 00| 0a 00      |..a.....o.u...|
0000000e

UTF-32
00000000  ff fe 00 00| 61 00 00 00|  e9 00 00 00| ef 00 00 00|  |....a...........|
00000010  6f 00 00 00| 75 00 00 00|  0a 00 00 00|               |o...u.......|
0000001c

La suite de valeurs FF FE en UTF-16 bits et FF FE 00 00 en UTF-32 indique l'ordre de lecture des caractères, ici cela signifie qu'il faut commencer par la première valeur trouvée.

Dans le cas de l'UTF-32, la séquence d'octets 61 00 00 00 doit donc être considérée comme la valeur hexadécimale $00\_00\_00\_61_{16} = 97_{10} = $ 'a'.

Exercice 3.1

Coder la chaîne suivante "29, éÄ!"

  • en ASCII
  • en UTF-8

Exercice 3.2

Chiffrer la chaine "a l'aide" en remplaçant chaque lettre par la suivante et les signes de ponctuation ou les espaces par un '.'. Si on doit chiffrer la lettre 'z', on recommence à partir de 'a'. Toute les lettres doivent apparaître sans accent.

Exercice 3.3

Déchiffrer la chaine "KPHQTOG.OC.VKSWG" en remplaçant chaque lettre par la lettre qui se trouve à deux positions précédentes. Dans ce cas 'B' se transforme en 'Z' et 'A' en 'Y'.

Exercice 3.4

Combien d'octets sont nécessaires pour coder une chaîne UTF-8 contenant :

  • 10 caractères avec un point de code inférieur à 128
  • 10 caractères avec un point de code compris entre 128 et 2047.

Exercice 3.5

Combien d'octets sont nécessaires pour coder une chaîne UTF-32 contenant :

  • 10 caractères avec un point de code inférieur à 128
  • 10 caractères avec un point de code compris entre 128 et 2047.