Licence CC BY-NC-SA

Les chaînes de caractères

Après les tableaux, voici un nouveau type composite Ada : les chaînes de caractères ou strings. Rentrez les langues les garçons, il n'y aura rien de sexuel dans ce chapitre. À dire vrai, vous avez déjà manipulé des strings par le passé (je veux dire, en Ada) et ce chapitre ne fera que mettre les choses au clair car vous disposez dors et déjà de toutes les connaissances nécessaires.

Présentation des Chaînes de Caractères

Souvenez-vous, vous avez déjà manipulé des string ou chaînes de caractères par le passé. Notamment lorsque vous écriviez :

1
Put("Bonjour !") ;

Le texte entre guillemets "Bonjour !" est un string.

Ah… c'est pas ce que j'avais pensé… :( On va faire tout un chapitre dessus ?

Ce chapitre ne sera pas long. Nous avons toutes les connaissances utiles, il s'agit juste de découvrir un type composite que nous utilisons depuis longtemps sans nous en rendre compte. Qu'est-ce finalement qu'une chaîne de caractères ? Eh bien le string ci-dessus se résume en fait à ceci :

n°1

n°2

n°3

n°4

n°5

n°6

n°7

n°8

n°9

'B'

'o'

'n'

'j'

'o'

'u'

'r'

' '

'!'

Eh oui ! Ce n'est rien d'autre qu'un tableau de caractères indexé à partir de 1. Notez que certains langages comme le célèbre C, commencent leurs indices à 0 et ajoutent un dernier caractère à la fin : le caractère de fin de texte. Ce n'est pas le cas en Ada où le type String est très aisément manipulable.

Déclaration et affectation d'un string

Nous allons créer un programme ManipString. Nous aurons besoin du package ada.text_io. De plus, avant de créer des instructions, nous allons devoir déclarer notre string de la manière suivante :

1
2
3
4
5
6
7
8
9
with ada.text_io ; 
use ada.text_io ;

procedure ManipString is
   txt : string(1..6) ;
begin
   txt := "Salut!" ; 
   put(txt) ; 
end ManipString

Eh oui ! Un string n'étant rien d'autre qu'un tableau de caractères, il est donc contraint : son nombre de cases est limité et doit être indiqué à l'avance. Par conséquent, il est possible de lui affecté la chaîne de caractères "Salut!" mais pas la chaîne "Salut !" qui comporte 7 caractères (n'oubliez pas qu'un espace est le caractère ' ' !). Il est aussi possible de déclarer notre objet txt tout en lui affectant une valeur initiale :

1
2
3
4
5
6
7
8
with ada.text_io ; 
use ada.text_io ;

procedure ManipString is
   txt : string := "Salut!" ;
begin
   put(txt) ; 
end ManipString

L'objet txt est automatiquement indexé de 1 à 6.

Quelques opérations sur les strings

Accès à une valeur

Comme pour un tableau, il est possible d'accéder à l'une des valeurs contenues dans le string. Par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
with ada.text_io ; 
use ada.text_io ;

procedure ManipString is
   txt : string := "Bonjour jules !" ;
   indice : integer ; 
begin
   put_line(txt) ; 
   for i in txt'range loop
      if txt(i) = 'e'
         then indice := i ; 
      end if ; 
   end loop ; 
   txt(indice) := 'i' ; 
   txt(indice+1) := 'e' ; 
   put("oups. ") ; 
   put(txt) ; 
end ManipString ;

Ce programme cherche la dernière occurence de la lettre 'e' et la remplace par un 'i'. Il affichera ainsi :

1
2
Bonjour Jules !
Oups. Bonjour Julie !

Remarquez au passage qu'il est toujours possible d'utiliser les attributs.

Accès à plusieurs valeurs

Nous voudrions maintenant modifier "Bonjour Jules !" en "Bonsoir Julie !". Cela risque d'être un peu long de tout modifier lettre par lettre. Voici donc une façon plus rapide :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
with ada.text_io ; 
use ada.text_io ;

procedure ManipString is
   txt : string := "Bonjour Jules !" ;
begin
   put_line(txt) ; 
   txt(4..7) := "soir" ;
   for i in txt'range loop
      if txt(i) = 'e'
         then txt(i) := 'i' ; 
              txt(i+1) := 'e' ; 
              exit ; 
      end if ; 
   end loop ; 
   put("oups. ") ; 
   put(txt) ; 
end ManipString ;

Il est ainsi possible d'utiliser les slices (tranches) pour remplacer tout un paquet de caractères d'un coup. Vous remarquerez que j'en ai également profité pour condenser mon code et supprimer la variable indice. Personne n'est parfait : il est rare d'écrire un code parfait dès la première écriture. La plupart du temps, vous tâtonnerez et peu à peu, vous parviendrez à supprimer le superflu de vos codes.

Modifier la casse

Autre modification possible : modifier la casse (majuscule/minuscule). Nous aurons besoin pour cela du package Ada.characters.handling. Le code suivant va ainsi écrire "Bonjour" en majuscule et transformer le 'j' de Jules en un 'J' majuscule.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
with ada.text_io, Ada.characters.handling ; 
use ada.text_io, Ada.characters.handling ;

procedure ManipString is
   txt : string := "Bonjour jules !" ;
begin
   put_line(txt) ; 
   txt(1..7) := to_upper(txt(1..7)) ; 
   for i in txt'range loop
      if txt(i) = 'e'
         then txt(i) := 'i' ; 
              txt(i+1) := 'e' ;  
      elsif txt(i) = 'j'
         then txt(i) := to_upper(txt(i)) ; 
      end if ; 
   end loop ; 
   put("oups. ") ; 
   put(txt) ; 
end ManipString

La fonction To_Upper() transforme un character ou un string pour le mettre en majuscule. Il existe également une fonction To_Lower() qui met le texte en minuscule.

Concaténation

Nous allons maintenant écrire un programme qui vous donne le choix entre plusieurs prénom (entre plusieurs strings) et affiche ensuite une phrase de bienvenue. Les différents prénoms possibles seront : "Jules", "Paul" et "Frederic". Contrainte supplémentaire : il faut utiliser le moins d'instructions Put() pour saluer notre utilisateur.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
with Ada.Text_IO ; 
use Ada.Text_IO ; 

procedure ManipString is
   txt : string := "Bienvenue, " ; 
   Nom_1 : string := "Jules." ; 
   Nom_2 : string := "Paul." ; 
   Nom_3 : string := "Frederic." ; 
   choix : integer ; 
begin
   Put_line("Comment vous appelez-vous ?") ; 
   Put_line("1 - " & Nom_1) ; 
   Put_line("2 - " & Nom_2) ; 
   Put_line("3 - " & Nom_3) ; 
   Get(choix) ; skip_line ; 
   case choix is
      when 1 => Put(txt & Nom_1) ; 
      when 2 => Put(txt & Nom_2) ; 
      when others => Put(txt & Nom_3) ; 
   end case ; 
end ManipString ;

Nous avons utilisé le symbole & (appelé esperluette) pour effectuer une opération de concaténation. Cela signifie que l'on met bout-à-bout deux strings (ou plus) pour en obtenir un plus long.

Attention ! Les strings obtenus après ces concaténations ont des longueurs variables. Vous ne pourrez donc pas écrire le code suivant :

1
2
3
4
5
case choix is
   when 1 => txt := txt & Nom_1 ; 
   when 2 => txt := txt & Nom_2 ; 
   when others => txt := txt & Nom_3 ; 
end case ;

Eh oui ! Le string txt a été initialisé avec une longueur de 11 characters. Et comme tout tableau, sa longueur ne peut être modifiée ou variable. Or « txt & Nom_1 » a une longueur de 17 characters (11+6) et « txt & Nom_2 » a une longueur de 16 (11+5).

Transformer une variable en string et inversement

Transformer une variable en string

Et si au lieu de "Bienvenue ####", je veux afficher une ligne disant "Vous avez fait le choix n°#, vous vous appelez donc ####", je peux concaténer des strings et des variables de types integer ?

Euh… non ! Rappelez-vous, je vous ai dit que le langage Ada avait un fort typage, autrement dit on ne mélange pas les choux et les carottes, les strings et les integer ! Par contre, il y a un moyen de parvenir à votre but en respectant ces contraintes de fort typage, grâce aux… attributs (eh oui encore) !

1
Put("Vous avez fait le choix n°" & integer'image(choix) & ", vous vous appelez donc " & Nom_1) ;

L'attribut integer'image() renvoie un string, une «image» de l'Integer placé entre parenthèses. Bien entendu, cet attribut n'est pas réservé au type Integer et s'applique à tous types de variables.

Transformer un string en variable

Et l'inverse ? Si je veux transformer un string en variable ?

Eh bien tout dépend du type de variable souhaité. Si vous voulez une variable du type integer, alors vous pourrez utilisé l'attribut Integer'value(). Entre les parenthèses, vous n'aurez qu'à écrire votre string et l'attribut 'value le transformera en un integer (ou un natural, ou un float…).

Comparaison

Oui, il est possible de comparer des strings ! :-° Il est possible de tester leur égalité avec l'opérateur = ; il est aussi possible d'utiliser les opérateurs d'ordre <, >, <=, >= !

Et il compare quoi le programme ? Ca veut dire quoi si j'écris "Avion" > "Bus" ?

Les opérateurs d'ordres utilisent l'ordre alphabétique et sont insensibles à la casse (Majuscule/Minuscule). La comparaison "Avion" > "Bus" devrait donc renvoyer FALSE, à ceci prêt que le compilateur ne voudra pas que vous l'écriviez tel quel. Il préfèrera :

1
2
3
4
5
6
...
   txt1 : string := "Avion" ; 
   txt2 : string := "Bus" ; 
BEGIN
   if txt1 > txt2 
      then ...

Saisie au clavier

Et si je souhaite saisir un nom directement au clavier ?

Alors il existe une fonction pour cela : get_line. Elle saisira tout ce que vous tapez au clavier jusqu'à ce que vous appuyiez sur Entrée (donc pas besoin d'utiliser skip_line par la suite) et renverra ainsi une chaîne de caractères :

1
txt := get_line ;

La longueur du string renvoyé par get_line est inconnue ! Or celle du string txt est connue et fixe. Si le texte saisi est trop long, vous vous exposez à des erreurs, s'il est trop court et que txt n'a pas été initialisé (en le remplissant de ' ' par exemple), vous risquez des erreurs lors de l'affichage. Contraignant n'est-ce pas ?

Chaînes de caractères non contraintes

Déclarer des strings illimités !

Il existe une façon de gérer des chaînes de caractères sans être limité par leur longueur. Pour cela, vous aurez besoin du package Ada.Strings.Unbounded. Et nous déclarerons notre variable ainsi :

1
txt : unbounded_string ;

Cela signifie que txt sera une «chaîne de caractère illimitée» ! ! !

:p Wouhouh ! J'essaye ça tout de suite ! T'aurais du commencer par là !!

Attendez ! Les unbounded_string ne s'utilisent pas de la même manière que les string normaux ! Simplement parce que leur nature est différente. Lorsque vous déclarez un tableau, par exemple :

1
txt : string(1..8) ;

L'ordinateur va créer en mémoire l'équivalent de ceci :

1

2

3

4

5

6

7

8

?

?

?

?

?

?

?

?

Lorsque vous déclarez un unbounded_string comme nous l'avons fait tout à l'heure, l'ordinateur créera ceci :

{ }

C'est en fait une liste, vide certes mais une liste tout de même. À cette liste, le programme ajoutera des caractères au fur et à mesure. Il n'est plus question d'indices, ce n'est plus un tableau. Nous verrons les listes après avoir vu les pointeurs, ce qui vous éclairera sur les limites du procédé.

Opérations sur les unbounded_string

Nous pouvons ensuite affecter une valeur à nos unbounded_string. Seulement il est impossible d'écrire ceci :

1
2
3
4
5
...
   txt : unbounded_string ; 
BEGIN
   txt := "coucou !" ;
...

Cela reviendrait à affecter un String à un objet de type unbounded_string ! Or nous savons que Ada est un langage à fort typage ! Un Unbounded_String ne peut recevoir comme valeur un string !

Vous devrez donc utiliser les fonctions To_Unbounded_string() et To_String() pour convertir de la manière suivante :

1
2
3
4
5
6
...
   txt : unbounded_string ; 
BEGIN
   txt := To_Unbounded_String("coucou !") ;
   put(To_String(txt)) ; 
...

Notre objet txt aura alors l'allure suivante :

{'c','o','u','c','o','u',' ','!'}

Il est toutefois possible d'opérer des concaténations grâce à l'opérateur &. Mais cette fois, plusieurs opérateurs & ont été créés pour pouvoir concaténer :

  • un string et un string pour obtenir un string
  • un unbounded_string et un string pour obtenir un unbounded_string
  • un string et un unbounded_string pour obtenir un unbounded_string
  • un unbounded_string et un unbounded_string pour obtenir un unbounded_string
  • un character et un unbounded_string pour obtenir un unbounded_string
  • un unbounded_string et un character pour obtenir un unbounded_string

Nous pouvons ainsi écrire :

1
txt := txt & "Comment allez-vous ?"

Notre objet txt ressemblera alors à ceci :

{'c','o','u','c','o','u',' ','!','C','o','m','m','e','n','t',' ','a','l','l','e','z','-','v','o','u','s',' ','?' }

Pourquoi tu ne présentes pas ça sous la forme d'un tableau, ce serait plus clair et on n'aurait pas besoin de compter pour connaître l'emplacement d'un caractère ?

N'oubliez pas que txt est un unbounded_string, pas un tableau (ni un string) ! C'est une liste, et il n'est pas possible d'écrire : txt(4) ou txt(1..3) ! Ces écritures sont réservées aux tableaux (les strings sont des tableaux ne l'oublions pas) et sont donc fausses concernant notre objet txt ! Pour tout vous dire, un Unbounded_String n'a accès qu'à la première lettre. Celle-ci donne accès à la seconde, qui elle-même donne accès à la troisième et ainsi de suite.

En effet, de nombreuses fonctionnalités liées aux tableaux ne sont plus disponibles. Voici donc quelques fonctions pour les remplacer :

1
2
3
4
5
6
7
8
9
n := length(txt) ;             --la variable n (natural) prendra comme valeur la 
                               --longueur de l'unbounded_string txt

char := Element(txt,5) ;       -- la variable char (character) prendra la 5ème valeur 
                               --de l'unbounded_string txt

Replace_Element(txt,6,char) ;  --le 6ème élément de txt est remplacé par le character char

txt := Null_Unbounded_String ; --txt est réinitialisé en une liste vide

Une indication tout de même, si vous ne souhaitez utiliser qu'une certaine partie de votre unbounded_string et que vous voudriez écrire txt(5..8), vous pouvez vous en sortir en utilisant To_String() et To_Unbounded_String() de la manière suivante :

1
2
3
4
5
6
put(to_string(txt)(5..8)) ;

               --OU

txt:= to_unbounded_string(to_string(txt)(5..8)) ; 
put(to_string(txt)) ;

Comme vous pouvez vous en rendre compte, la manipulation des unbounded_strings est plus lourde que celle des strings. Voila pourquoi nous préfèrerons utiliser en général les strings, nous réserverons l'utilisation des unbounded_strings aux cas d'extrêmes urgence. :D

Nous voila à la fin de ce chapitre sur les strings et les unbounded_strings. Cela constitue également un bon exercice sur les tableaux. Mais ne vous croyez pas sortis d'affaire : nous n'en avons pas terminé avec les tableaux. Les prochains chapitres, consacrés à la programmation modulaire (l'utilisation et la création de packages) ou à la manipulation des fichiers, seront l'occasion de travailler encore avec les tableaux, les strings ou les unbounded_strings.


En résumé :

  • Un string n'est rien de plus qu'un tableau unidimensionnel composé de caractères.
  • Par conséquent, si vous déclarer un String, vous devez absolument définir sa taille : il s'agit d'un type contraint.
  • Les Strings supportent les tests d'égalité et d'inégalité. Ces derniers comparent en fait leur classement dans l'ordre alphabétique. À cela s'ajoute l'opération de concaténation, symbolisée par l'esperluette &, qui permet de mettre bout à bout deux chaînes de caractère.
  • Préférez toujours les String pour leur simplicité. Si réellement la contrainte de taille devient un handicap, optez alors pour un Unbounded_String. Vous aurez alors besoin du package Ada.Strings.Unbounded.