Blog ENI : Toute la veille numérique !
💥 Un livre PAPIER acheté
= La version EN LIGNE offerte pendant 1 an !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Chaînes de caractères

Introduction

Les chaînes de caractères sont souvent, pour nous humains, un endroit où l’ordinateur stocke des messages dans un langage que nous pouvons comprendre. Pour lui, par contre, ce ne sont que des caractères les uns à la suite des autres, dans un format qui nécessite des transformations afin que les données puissent être traitées par l’unité de calcul. De nombreuses opérations existent donc sur les chaînes de caractères, afin d’effectuer ces transformations, des données vers des messages en langage humain et réciproquement.

Nous aborderons dans ce chapitre les diverses opérations qui peuvent être réalisées avec des chaînes de caractères.

C’est voir, tout d’abord, les différents moyens de stocker des chaînes en distinguant celles dites statiques et celles dites dynamiques. C’est ensuite voir comment copier une chaîne, réaliser l’analyse caractère par caractère, comment s’y prendre au moyen d’une boucle. C’est aussi savoir comment concaténer des chaînes. À ce moment là, ressurgiront les problématiques de la gestion de la mémoire à bien prendre en compte.

Les chaînes de caractères, c’est également savoir convertir une chaîne en nombre et réciproquement....

Allouer la mémoire pour une chaîne de caractères

Problème

Vous voulez connaître les différents moyens de stocker une chaîne de caractères dans un programme.

Solution

Les moyens les plus utilisés consistent en la déclaration d’une chaîne statique, l’utilisation de strdup(), et sinon, les fonctions d’allocation de mémoire et celles de copie de chaînes de caractères.

Discussion

Tout d’abord, distinguons les chaînes de caractères statiques des chaînes de caractères dynamiques. Une chaîne de caractères statique voit son espace alloué à l’endroit où elle est déclarée (sauf pour les chaînes déclarées avec le mot-clé static qui est explicité plus loin). Sa taille est donc fixée une bonne fois pour toutes lors de la compilation du programme et il n’est plus possible de modifier cette taille par la suite. À l’inverse, l’espace d’une chaîne de caractères dynamique n’est pas allouée lors de la compilation mais à l’exécution du programme. Nous la déclarons comme un pointeur sur une variable de type char et l’utilisons comme un tableau de char après avoir nous-même alloué la mémoire avec une fonction de la famille de malloc().

Une variable déclarée avec le mot-clé static signifie qu’elle est allouée dans le même espace que le programme, au lancement de celui-ci. Cet espace existe donc à tout moment lors de l’exécution...

Copier une chaîne de caractères

Problème

Vous souhaitez avoir deux exemplaires d’une même chaîne de caractères.

Solution

Utilisez strdup() ou un pointeur en fonction du besoin.

Discussion

Le besoin est-il de disposer d’un autre pointeur, sans pour autant dupliquer la chaîne de caractères, ou est-il nécessaire de la dupliquer ? Se contenter d’un pointeur supplémentaire vers la chaîne de caractères a l’intérêt de ne pas prendre de temps à allouer de la mémoire et recopier la chaîne. En revanche, il implique de veiller à ce qui est fait de la chaîne de caractères en particulier lors de la libération de la mémoire qu’elle occupe. Nous préférerons souvent considérer le nouveau pointeur comme un pointeur temporaire, et libérer la mémoire en utilisant le même pointeur qui a été utilisé pour allouer cette mémoire. S’il s’agit de dupliquer la chaîne de caractères, alors un appel à strdup() suffit amplement. Dans certains cas, une copie partielle de la chaîne de caractères suffit. Vous pouvez alors décomposer l’opération ainsi :


char *str_partiel; 
int nb; 
if (NULL == (str_partiel = malloc ((nb + 1) * sizeof *str_partiel))) 
  exit (EXIT_FAILURE); 
memcpy (str_partiel...

Analyser une chaîne de caractères, caractère par caractère

Problème

Vous devez traiter chaque caractère ou effectuer une action demandant le parcours de la chaîne de caractères, caractère par caractère.

Solution

Parcourez la chaîne de caractères comme un tableau de caractères à l’aide d’une boucle.

Discussion

Le code pour une telle action est généralement basé sur :


int i; 
for (i = 0; chaine[i]; i++) 
  { 
    /* action sur le i-ème caractère */ 
  }
 

À la fin de la boucle, la variable i contient la longueur de la chaîne de caractères. 

Le i-ème caractère de la chaîne, dans l’exemple ci-dessus, est chaine[i]. L’action sur le i-ème caractère peut ici être n’importe laquelle, comme compter le nombre d’occurrences d’un caractère particulier tel que le caractère de fin de ligne. De nombreuses applications simples peuvent être effectuées, comme la transformation des minuscules en majuscules, le compte des caractères...

Concaténer deux chaînes de caractères

Problème

Vous disposez de deux chaînes et voulez une chaîne résultant de la concaténation de ces deux chaînes.

Solution

Sans glib, l’opération consiste à disposer d’un espace suffisant pour accueillir les deux chaînes de caractères, puis à les y recopier. Avec glib, utilisez g_strjoin() ou g_string_append().

Discussion

La première question qui se pose est celle de l’espace mémoire que vous voulez utiliser. Voulez-vous agrandir la taille de l’espace mémoire alloué pour la première chaîne de caractères ? Voulez-vous créer un nouvel espace ? Disposez-vous déjà d’un espace dédié à cet usage ?

Si vous voulez réutiliser l’espace mémoire d’une chaîne de caractères, utilisez realloc() pour l’agrandir. Attention, realloc() est coûteuse en temps CPU ; dans une boucle, anticipez l’allocation mémoire pour éviter l’usage de realloc(). Remarquez par contre que si l’utilisation de realloc() est en général déconseillée à cause des nombreux bogues qu’elle entraîne, en particulier suite à une gestion très délicate des pointeurs, utiliser realloc() avec une chaîne de caractères ne devrait pas réserver de mauvaises surprises. Le seul point à retenir est que si l’opération échoue, par exemple par manque de mémoire, le contenu de l’espace mémoire à augmenter est perdu, contrairement à ce qui se passe si vous utilisez malloc() suivi de strncpy(). L’espace mémoire doit avoir pour taille la somme des longueurs des chaînes de caractères plus un octet pour le caractère ’\0’ final. Si vous préférez allouer un nouvel espace, calculez la taille qui est déterminée par celle de chacune des chaînes de caractères, plus le caractère ’\0’ de fin de chaîne.

En cas de doute sur cette taille à prévoir, allouer quelques octets de plus ne change rien du tout quand vous travaillez sur de petites chaînes. Le système alloue l’espace dans une page mémoire qu’il réserve...

Convertir un nombre en chaîne de caractères et réciproquement

Problème

Vous voulez transformer un nombre (entier ou flottant) en chaîne de caractères pour l’afficher ou vous disposez d’une telle chaîne et avez besoin d’en récupérer la valeur numérique.

Solution

Utilisez une des fonctions strtol(), strtof() ou strtod() si vous avez une chaîne de caractères et que vous voulez obtenir le nombre qu’elle contient. Utilisez snprintf() pour transformer un nombre en chaîne de caractères.

Discussion

Transformer une chaîne de caractères en sa valeur, selon le type souhaité, est facile avec les fonctions strtol() pour en obtenir un entier, strtof() pour en obtenir un nombre à virgule flottant, et strtod() pour un nombre à virgule flottante double précision. Ces fonctions feront le maximum pour récupérer la valeur souhaitée et renverront zéro si elles échouent. Pour tester si une chaîne contient un nombre, il suffit donc d’effectuer le test suivant : (strtol(chaine, NULL,10) || (’\0’ == chaine[0])). Si strtol() renvoie zéro, testez si le nombre est zéro ou si la chaîne contient autre chose qu’un nombre qui ne peut être converti en nombre.

Si vous avez besoin d’une conversion en long long, utilisez strtoul(). Pour un long double, c’est strtold().

Ces fonctions présentent plusieurs intérêts. Le premier est que le second argument peut pointer sur une valeur qui, au retour de la fonction, se trouve être le caractère suivant le dernier caractère converti dans la chaîne fournie en premier argument. Cette particularité se trouve être très intéressante pour récupérer des données dans une liste CSV (« nombre1 », « nombre2 », ...) :


typedef struct 
{ 
  double *val; 
  int nb_vals; 
} double_array_t; 
 ...

Transformer une chaîne de caractères avec des retours chariot en un tableau de chaînes de caractères

Problème

À partir d’une chaîne de caractères contenant un texte, vous souhaitez obtenir un tableau de chaînes de caractères correspondant à chaque ligne du texte.

Solution

Utilisez g_strsplit() de glib ou écrivez le code vous-même.

Discussion

La fonction g_strsplit() avec en argument la chaîne "\n" comme délimiteur, transforme un texte en un tableau de chaînes de caractères correspondant aux lignes de ce texte :


g_char texte[] = "ligne1\nligne2\nligne3\n"; 
gchar **resultat; 
int i; 
 
/* Transformation */ 
resultat = g_strsplit (texte, "\n", 0); 
 
/* Affichage du résultat */ 
for (i = 0; resultat[i]; i++) 
  g_printf ("%s\n", resultat[i]); 
g_strfreev (resultat);
 

Attention ! N’oubliez pas de libérer la mémoire avec g_strfreev().

Nous pouvons aussi coder une fonction équivalente par nous-même. Deux cas sont à distinguer. Le premier consiste à effectuer exactement la même chose que g_strsplit(). Dans le second, nous allons modifier le texte et utiliser une astuce qui permet d’économiser la mémoire et le nombre d’allocations mémoire. 


char ** 
transforme_texte_en_tableau...

Découper une chaîne de caractères en fonction d’un séparateur

Problème

Vous disposez d’une chaîne de caractères dont les champs sont séparés par des virgules, ou tout autre caractère, et souhaitez en extraire différents champs.

Solution

Utilisez g_strsplit() ou écrivez le code.

Discussion

Sans glib, vous pouvez utiliser transforme_texte_en_tableau() d’une recette précédente, en l’adaptant pour pouvoir spécifier le caractère séparateur en argument. Voici un autre exemple de code qui duplique les chaînes de caractères avant de les mettre dans un tableau, comme le fait g_strsplit().

La recette "Transformer une chaîne de caractères avec des retours chariot en un tableau de chaîne de caractères" est un cas particulier du problème, le séparateur étant le retour chariot. Avec glib, utilisez g_strsplit(texte, séparateur, 0).


#define TAILLE_BLOC 1024 
 
char ** 
transforme_texte_en_tableau_avec_separateur (const char *str, 
                                             const char *separator) 
{ 
  char **a = NULL; 
  int a_size = 0; 
  int a_len = 0; 
  int i; 
  int separator_len = strlen (separator); 
  const char *p0; 
  char *p1; 
 
  /* Initialisations */ 
  if (NULL == (a = malloc (TAILLE_BLOC * sizeof *a))) 
    return (NULL); 
  a_size...

Récupérer le chemin et le nom d’un fichier spécifiés dans une chaîne de caractères

Problème

Vous disposez d’un nom de fichier et voulez en récupérer le chemin et le nom.

Solution

Utilisez dirname() et basename().

Problème

Les deux fonctions basename() et dirname() renvoient le résultat demandé. Cependant, il faut savoir deux choses. Il existe, d’une part, une fonction basename() spécifique à GNU libc qui garantit qu’elle ne modifie pas l’argument fourni à la fonction. Mais normalement, cette fonction, ainsi que dirname(), sont susceptibles de modifier la chaîne de caractères fournie en argument. D’autre part, le résultat est stocké dans un tampon statique interne : tout appel suivant modifie donc le tampon. Ces deux fonctions ne se prêtent donc pas entre autres à l’utilisation de threads.

Les fonctions correspondantes de glib, g_path_get_dirname() et g_path_get_basename(), sont utilisables. Elles renvoient une chaîne de caractères allouée spécifiquement et que nous devons libérer avec g_free().

Si ni l’une ni l’autre de ces solutions ne vous convient, codez la vôtre vous-même, par exemple ainsi :


char * 
mon_dirname (const char *path) 
{ 
  char *r; 
  int i; 
  int l; 
  l = strlen (path); ...

Remplacer une sous-chaîne par une autre sous-chaîne dans une chaîne de caractères

Problème

Vous voulez remplacer une partie d’une chaîne de caractères par une autre chaîne de caractères.

Solution

Si les deux chaînes de caractères (la partie à remplacer et son remplacement) ont la même taille, il suffit de remplacer les caractères octet par octet, par exemple avec memcpy(). Si la chaîne à remplacer est plus petite que celle qu’elle remplace, le processus est identique, puis, vous devez décaler la fin de la chaîne originale. Si la chaîne à remplacer est plus grande que celle qu’elle remplace, créez alors une nouvelle chaîne de taille suffisante, et recopiez-y ce que vous souhaitez.

Discussion

Une fonction qui remplace une sous-chaîne par une autre a un prototype de ce genre :


char *remplace (char *original, int debut, int longueur, 
                const char *remplacement);
 

Si strlen(remplacement) est égal à longueur, le contenu de la fonction est tout simplement memcpy(original+debut, remplacement, longueur);.

Si la sous-chaîne de remplacement n’est en fait qu’un seul caractère, n’utilisez pas memcpy(), mais ceci : original[debut] = remplacement[0];.

Les autres cas s’avèrent plus complexes. Voici un exemple de fonction qui teste le cas dans lequel nous sommes...

Déterminer si une chaîne de caractères est contenue dans une autre et à quelle position

Problème

Vous voulez savoir si une chaîne de caractères est contenue dans une autre et à quelle position.

Solution

Utilisez strstr().

Discussion

Dans la plupart des cas, strstr() convient pour trouver une aiguille dans une botte de caractères. Cependant, suivant vos besoins, plusieurs variantes peuvent se révéler plus adaptées :

Variantes de strstr()

strcasestr()

Recherche une chaîne de caractères dans une autre sans tenir compte de la casse.

strnstr() et g_strstr_len()

Recherche une chaîne de caractères dans une autre entre le début et le énième caractère fourni en argument. strnstr() n’est pas standard et g_strstr_len() est une fonction de glib. Notez que strnstr() n’est pas portable.

g_strrstr()

Recherche une chaîne de caractères dans une autre en partant de la fin. g_strrstr() est une fonction de glib.

g_strrstr_len()

Recherche une chaîne de caractères dans une autre en partant du énième caractère spécifié en argument. g_strrstr_len() est une fonction de glib.

g_str_has_prefix()

Regarde si le début d’une chaîne est égal à l’autre. g_str_has_prefix() est une fonction de glib. Nous pouvons facilement coder cette fonction ainsi : strncmp(a,b,strlen(b))...

Déterminer si une chaîne de caractères correspond au motif précisé dans une expression régulière

Problème

Vous voulez tester une chaîne de caractères par rapport au motif d’une expression régulière.

Solution

Utilisez regcomp() et regexec() pour effectuer ce test.

Discussion

Les expressions régulières, moins faciles à utiliser en C que dans d’autres langages tels que Perl, Python ou PHP, ne doivent pas pour autant laisser la place à des solutions fondées sur des pointeurs, des boucles et des tests sur chaque caractère (à moins d’un cas simple). Pour les utiliser, nous avons besoin d’un espace mémoire de type regex_t qui est rempli par regcomp().

Cette fonction compile l’expression régulière fournie en argument pour la transformer en une forme interne utilisée par regexec(). Celle-ci renvoie une valeur indiquant si la chaîne testée correspond à l’expression régulière, et est capable de remplir un tableau d’éléments de type regmatch_t contenant les indices des début et fin des sous-chaînes correspondant aux motifs entre parenthèses dans l’expression régulière. Ce tableau doit contenir autant d’éléments que de parenthèses dans l’expression régulière, plus un. En effet, le premier élément du tableau contient les indices de début et fin de la chaîne complète correspondant à l’expression régulière.

Il est par ailleurs possible de simplement vérifier si une chaîne correspond à une expression régulière en ne testant que la valeur de retour de regexec(). Cette valeur est 0 si cela correspond, et REG_NOMATCH sinon. La valeur est un code d’erreur dans tous les autres cas. Si le but se limite à tester une chaîne, il est inutile d’allouer un espace pour contenir le tableau...

Trouver le nombre d’occurrences d’une chaîne de caractères dans une autre

Problème

Vous voulez savoir combien de fois une chaîne de caractères se retrouve à l’intérieur d’une autre.

Solution

Parcourez la chaîne de caractères en testant le motif à chaque fois, avec strncmp(), et en incrémentant un compteur quand le motif correspond.


int 
nb_occurrences_of_string_in_string (const char *string, 
                                    const char *pattern) 
{ 
  int lp = strlen (pattern); 
  int ls = strlen (string); 
  int i; 
  int nb = 0; 
 
  for (i = 0; i <= ls - lp; i++) 
    { 
      if (!strncmp (&(string[i]), pattern, lp)) 
        { 
          nb++; 
          i += lp; 
        } 
    } 
  return (nb); 
}
 

Discussion

Ce code renvoie le nombre d’occurrences d’une chaîne à l’intérieur d’une autre. Il existe un cas particulier a prendre en compte, par exemple la chaîne « aaa » à rechercher dans la chaîne « aaaaaaa ». Avec le code précédent, la valeur renvoyée est 2 car nous ne pouvons trouver que deux occurrences distinctes de la chaîne à chercher dans l’autre. Mais si le fait que les occurrences soit distinctes ne vous intéresse pas, la valeur renvoyée doit être 5. Pour cela, supprimez la ligne...

Traiter les blancs au début et à la fin d’une chaîne

Problème

Vous voulez supprimer les blancs (espaces, tabulations) inutiles au début et/ou à la fin d’une chaîne de caractères.

Solution

Pour les blancs de début de chaîne, il faut détecter le premier caractère non blanc, puis recopier la chaîne à partir de celui-ci sur le début de la chaîne. Pour les blancs de fin de chaîne, il suffit de remplacer le premier de ces blancs par un caractère nul.

Discussion

L’opération de suppression des blancs de début de chaîne se fait facilement à condition de ne pas tomber dans le piège consistant à utiliser memcpy() car la chaîne initiale et celle de destination risquent de se chevaucher. Il faut utiliser impérativement memmove().


void 
str_chug (char *string) 
{ 
  char *p; 
  for (p = string; (*p == '\t' || *p == ' '); p++); 
  memmove (string, p, strlen (p) + 1); 
}
 

Cette fonction existe dans glib et s’appelle g_strchug().

Par ailleurs, lors de la recopie, si vous voulez également supprimer les caractères blancs de fin de chaîne, vous pouvez anticiper en ne recopiant pas ces caractères blancs. Il faut, dans ce cas, calculer la taille de la chaîne sans les caractères blancs afin de fournir cette...

Transformer tout séparateur par une espace

Problème

Vous disposez d’une chaîne de caractères dont chaque champ est délimité par un séparateur quelconque et voulez remplacer ces séparateurs par un caractère unique comme une espace.

Solution

Parcourir la chaîne de caractères en la recopiant sur elle-même et en remplaçant en même temps les séparateurs par des espaces.

Discussion

Le parcours peut se faire avec un simple pointeur et la recherche des séparateurs avec strcmp(). Le code est :


void 
replace_separator_with_space (char *str, const char *separator) 
{ 
  int l = strlen (separator); 
  char *p, *c; 
  for (p = str, c = str; p[0]; p++) 
    { 
      if (strncmp (separator, p, l)) 
        { 
          c[0] = p[0]; 
          c++; 
        } 
      else 
        { 
          c[0] = ' '; 
          c++; 
          p += l - 1; /* longueur moins un */
        } 
    } 
  c[0] = '\0'; 
}
 

Vous pouvez également parcourir la chaîne par blocs, en recherchant la fin de chaque bloc avec strstr() et en recopiant le bloc avec memmove().


void 
replace_separator_with_space_by_blocs (char *str, const char *separator) 
{ 
  int l = strlen (separator); 
  char *p, *q, *c; 
  q = str; 
  for (p = str, c = str; q; p = q + l) 
    { 
      q = strstr (p, separator); 
      if (q) ...

Transformer une chaîne en minuscules ou en majuscules

Problème

Vous voulez convertir une chaîne de caractères en majuscules ou en minuscules ou inverser la casse.

Solution

Parcourez la chaîne de caractères en remplaçant chaque caractère si la conversion le nécessite.

Discussion

Si la chaîne ne contient pas de caractère accentué, ou que vous voulez laisser les caractères accentués tels quels, il suffit de détecter les caractères minuscules (ou majuscules) et de leur retrancher (ou ajouter) l’expression ’a’-’A’.


void 
str_case_up (char *str) 
{ 
  int i; 
  for (i = 0; str[i]; i++) 
    { 
      if ((str[i] >= 'a') && (str[i] <= 'z')) 
        str[i] += ('A' - 'a'); 
    } 
}
 

La bibliothèque glib propose des fonctions g_ascii_toupper(), g_string_ascii_up() ou g_string_ascii_down() qui exécutent un code similaire et ne travaillent que sur les caractères ASCII standard. Sont également disponibles g_ascii_strup() ou g_ascii_strdown(), fonctions qui transforment la chaîne en la modifiant. En ce qui concerne les GString, utilisez g_string_ascii_up() et g_string_ascii_down().

Si votre chaîne contient des caractères accentués que vous voulez remplacer par une majuscule (ou une minuscule), les choses sont...