pdf - e-book - archive - github.com

1.7  Chaînes de caractères

1.7.1  Exemple

Etant donné le programme dont l’exécution est tracée ci dessous :

Saisissez une phrase :
Les framboises sont perchees sur le tabouret de mon grand-pere.
Vous avez saisi :
Les framboises sont perchees sur le tabouret de mon grand-pere.
Cette phrase commence par une majuscule.
Cette phrase se termine par un point.

Comment faire pour saisir et manier des séquences de caractères de la sorte ?

1.7.2  Définition

Une chaîne de caractères est un tableau de char contenant un caractère nul. Le caractère nul a 0 pour code ASCII et s’écrit 0’. Les valeurs significatives de la chaîne de caractères sont toutes celles placées avant le caractère nul. On remarque donc que si le caractère nul est en première position, on a une chaine de caractères vide.

Par exemple, la phrase "Toto" sera codée de la sorte :

'T' 'o' 't' 'o' 0

Prenez bien note du fait que le dernier caractère de la chaine est suivi d’un caractère nul.

1.7.3  Déclaration

Comme une chaîne de caractères est un tableau de char, on le déclare :

  char <nom_chaine>[<taille_chaine>];

Par exemple, on déclare une chaine c de 200 caractères de la sorte :

  char c[200];

Attention ! Le nombre maximal de lettres qu’il sera possible de placer dans c ne sera certainement pas 200 mais 199, car il faut placer après le dernier caractère de la chaîne un caractère nul !

1.7.4  Initialisation

On initialise une chaîne à la déclaration, et seulement à la déclaration de la sorte :

  char <nom_chaine>[<taille_chaine>] = <valeur_initialisation>;

Où la valeur d’initiallisation contient la juxtaposition de caractères formant la chaîne entourée de guillemets (double quotes). Par exemple,

  char c[50] = "Toto";

Cette instruction déclare une chaîne de caratères c initialisée à "Toto". Les 5 premiers éléments du tableau seront occupés par les 4 caractères de la chaîne ainsi que par le caratère nul, les autres contiendront des valeurs non significatives. Observez bien l’exemple suivant :

  char c[4] = "Toto";

Cette déclaration engendrera un warning à la compilation et probablement une erreur à l’exécution car l’affectation du caractère nul à la 5-ème position du tableau donnera lieu à un débordement d’indice.

1.7.5  Accès aux éléments

Du fait qu’une chaine de caractère est un tableau, il est aisé d’en isoler un élément. Ainsi c[i] est le i+1-ème élément de la chaîne c. On teste donc si le premier caratère de c est une majuscule de la sorte :

  if (c[0] >= 'A' && c[0] <= 'Z')
    printf("Cette phrase commence par une majuscule.\n");
  else
    printf("Cette phrase ne commence pas par une majuscule.\n");

Cette propriété permet aussi d’afficher une chaîne caractère par caractère :

  int i = 0;
  while(c[i] != 0)
    printf("%c", c[i++]);

Notez que le corps de la boucle while est itéré jusqu’à ce que le caractère nul soit rencontré. Il est donc impératif que votre chaîne se termine par le caractère nul et que le caractère nul se trouve dans la plage d’indices du tableau. Est-ce que le code suivant est correct ?

  char c[26]; 
  int i;
  for(i = 0 ; i < 26, i++)
    c[i] = 'a' + i;

Si la question vous est posée, vous pouvez présumer que ce code n’est pas correct. Le fait que chaque élément du tableau contienne un caractère non nul ne peut que corroborer cette présomption... Si l’on souhaite placer l’alphabet dans une chaîne, on procède de la sorte :

  char c[27]; 
  int i;
  for(i = 0 ; i < 26, i++)
    c[i] = 'a' + i;
  c[26] = 0;

Notez bien que le tableau contient 27 éléments si l’on compte le caractère nul, et que celui-ci est placé à la fin du tableau juste après la boucle.

1.7.6  Affichage

Nous avons vu qu’il était possible d’utiliser le fait qu’une chaîne est un tableau pour l’afficher. Il existe une méthode plus simple, en utilisant printf avec la chaîne de format "%s". Par contre soyez attentifs au fait que si votre chaîne ne contient pas de caractère nul ou que le caractère nul se trouve en dehors de la plage d’indice de votre chaîne, il faudra vous attendre aux pires horreurs à l’exécution ! Dans le code donné en exemple nous pouvons donc écrire l’instruction d’affichage de la chaîne saisie par l’utilisateur :

  printf("Vous avez saisi :\n%s", c);

1.7.7  Saisie

C’est maintenant que les choses se corsent, il faut être très attentif lors des saisies : tout débordement d’indice et/ou absence de caratère nul peut donner lieu à des bugs très difficiles à trouver ! La plus grande vigilance est donc de mise. Beaucoup d’amateurs utilisent des fonctions comme gets. Par exemple,

#include<stdio.h>

#define N 20

int main()
{
  char chaine[N];
  int i;
  printf("Saisissez une phrase :\n");
//  gets(chaine);
  for (i = 0 ; chaine[i] != 0 ; i++)
    printf("chaine[%d] = %c (code ASCII : %d)\n", i, chaine[i], chaine[i]);
  printf("chaine[%d] = %c (code ASCII : %d)\n", i, chaine[i], chaine[i]);
  return 0;
}

Télécharger le fichier

fichier source

Tout d’abord compilons ce programme :

[klaus@Isengard chaines]$ gcc -Wall mauvaiseSaisie.c -o mauvaiseSaisie.c
/home/klaus/tmp/ccyKd0hf.o: In function `main':
mauvaiseSaisie.c:(.text+0x24): warning: the `gets' function is
dangerous and should not be used.

La première réaction du compilateur est une insulte. A-t-il raison ? Testons ce programme :

[klaus@Isengard chaines]$ ./mauvaiseSaisie
Saisissez une phrase :
Les framboises sont perchees sur le tabouret de mon grand pere.
chaine[0] = L (code ASCII : 76)
chaine[1] = e (code ASCII : 101)
chaine[2] = s (code ASCII : 115)
chaine[3] =   (code ASCII : 32)
chaine[4] = f (code ASCII : 102)
chaine[5] = r (code ASCII : 114)
chaine[6] = a (code ASCII : 97)
chaine[7] = m (code ASCII : 109)
chaine[8] = b (code ASCII : 98)
chaine[9] = o (code ASCII : 111)
chaine[10] = i (code ASCII : 105)
chaine[11] = s (code ASCII : 115)
chaine[12] = e (code ASCII : 101)
chaine[13] = s (code ASCII : 115)
chaine[14] =   (code ASCII : 32)
chaine[15] = s (code ASCII : 115)
chaine[16] = o (code ASCII : 111)
chaine[17] = n (code ASCII : 110)
chaine[18] = t (code ASCII : 116)
chaine[19] =   (code ASCII : 32)
chaine[20] =  (code ASCII : 20)
chaine[21] =  (code ASCII : 0)
Erreur de segmentation

Que se passe-t-il ? Des horreurs ! La fonction gets est la pire des choses qui puisse arriver à un programme C ! Ne l’utilisez sous aucun prétexte ! Maintenant, nous allons envisager une façon propre de saisir une chaîne de caractère : fgets. La syntaxe est la suivante :

  fgets(<chaine>, <taille>, stdin);

La taille de la chaîne saisie est limitée par <taille>, caractère nul compris. Le résultat est placé dans <chaine>. Tous les caractères supplémentaires saisis par l’utilisateur ne sont pas placés dans <chaine>, seuls les taille−1 premiers caractères sont récupérés par fgets. Nous saisirons donc la phrase de notre programme de la sorte :

  fgets(c, 200, stdin);

1.7.8  Problèmes liés à la saisie bufferisée

Lorsque vous saisissez des caractères avec fgets, les caractères ignorés ne sont pas éliminés du buffer. Cela signifie qu’ils iront parasiter la prochaine saisie. Il convient donc tout d’abord de vérifier s’il reste des caractères dans buffer et de les éliminer si nécessaire. Commençons par le premier point : comment s’assurer que tous les caractères saisis ont été lus ? La réponse est très simple : grâce au caractère d’échappement n’. La saisie est validée par le caratère d’échappement, il s’agit donc toujours du dernier caractère saisi. Le buffer a donc été entirèrement lu si caractère d’échappement a été lu. Si ce caractère a été lu, alors il figure juste avant le caractère nul dans la chaîne. On le repère donc de la sorte :

  while(c[i] != 0)
    i++;
  if (c[i-1] != '\n')
    printf("La saisie est incomplete");
  else
    printf("La saisie est complete");  

Si la chaîne c a été saisie correctement, aucun risque de débordement d’indice ou de bouclage infini ne peut se présenter. La boucle au début de l’extrait recherche le caractère nul dans la chaîne. On sort de la boucle quand c[i] == 0, donc l’indice du caratère nul est i. Si l’avant dernier caractère de la chaîne, donc celui d’indice (i − 1) est le caractère d’échappement, alors la saisie a été complète et le buffer est vide. Sinon, c’est que des caractères ont été laissés dans le buffer par fgets. Il convient donc, si l’on souhaite effectuer d’autres saisies par la suite, de vider le buffer. getchar est une instruction retournant un caractère lu dans le buffer. Si le buffer est vide, getchar attend une saisie de l’utilisateur non validée par le caractère d’échappement ! On vide donc le buffer de la sorte :

  while(getchar() != '\n');

Le point-virgule à la fin de l’instruction indique au compilateur que la boucle n’a pas de corps. Cette boucle lit le buffer jusqu’à ce que le caractère d’échappement ait été lu. Pour davantage de lisibilité, on définit une macro-instruction vidant le buffer :

  #define CLEAR_BUFFER while(getchar() != '\n')

Vérifiez bien avant de lancer cette instruction que le buffer n’est pas vide, sinon le programme bouclera jusqu’à ce que l’utilisateur ait saisi un caractère d’échappement.

1.7.9  La bibliothèque string.h

Cette bibliothèque propose des fonctions de maniement de chaînes de caractères, à savoir :

Il vous est conseillé d’examiner de quelle façon fonctionnent ces fonctions, et comment elles gèrent le caractère nul.

1.7.10  Exemple

#include<stdio.h>

#define N 10
#define CLEAR_BUFFER while(getchar() != '\n')

int main()
{
  char chaine[N];
  int i;
  printf("Saisissez une phrase :\n");
  fgets(chaine, N, stdin);
  i = 0;
  while(chaine[i] != 0)
    i++;
  if (i > 0 && chaine[i-1] != '\n')
    CLEAR_BUFFER;
  else
    chaine[i-1] = 0;
  printf("Vous avez saisi :\n%s\n", chaine);
  if (chaine[0] >= 'A' && chaine[0] <= 'Z')
    printf("Cette phrase commence par une majuscule.\n");
  else
    printf("Cette phrase ne commence pas par une majuscule.\n");
  i = 0;
  while(chaine[i] != 0 && chaine[i] != '.')
    i++;
  if (chaine[i] == '.')
    if (chaine[i+1] == 0)
      printf("Cette phrase se termine par un point.\n");
    else
      printf("Vous avez saisi plus d'une phrase.\n"); 
  else
    printf("Cette phrase ne se termine pas par un point.\n"); 
  return 0;
}

Télécharger le fichier