Licence CC 0

Les chaînes de caractères

Dernière mise à jour :

Dans ce chapitre, nous allons apprendre à manipuler du texte ou, en langage C, des chaînes de caractères.

Qu'est-ce qu'une chaîne de caractères ?

Dans le chapitre sur les variables, nous avions mentionné le type char. Pour rappel, nous vous avions dit que le type char servait surtout au stockage de caractères, mais que comme ces derniers étaient stockés dans l’ordinateur sous forme de nombres, il était également possible d’utiliser ce type pour mémoriser des nombres.

Le seul problème, c’est qu’une variable de type char ne peut stocker qu’une seule lettre, ce qui est insuffisant pour stocker une phrase ou même un mot. Si nous voulons mémoriser un texte, nous avons besoin d’un outil pour rassembler plusieurs lettres dans un seul objet, manipulable dans notre langage. Cela tombe bien, nous en avons justement découvert un au chapitre précédent : les tableaux.

C’est ainsi que le texte est géré en C : sous forme de tableaux de char appelés chaînes de caractères (strings en anglais).

Représentation en mémoire

Néanmoins, il y a une petite subtilité. Une chaîne de caractères est un plus qu’un tableau : c’est un objet à part entière qui doit être manipulable directement. Or, ceci n’est possible que si nous connaissons sa taille.

Avec une taille intégrée

Dans certains langages de programmation, les chaines de caractères sont stockées sous la forme d’un tableau de char auquel est adjoint un entier pour indiquer sa longueur. Plus précisément, lors de l’allocation du tableau, le compilateur réserve un élément supplémentaire pour conserver la taille de la chaîne. Ainsi, il est aisé de parcourir la chaîne et de savoir quand la fin de celle-ci est atteinte. De telles chaines de caractères sont souvent appelées des Pascal strings, s’agissant d’une convention apparue avec le langage de programmation Pascal.

Avec une sentinelle

Toutefois, une telle technique limite la taille des chaînes de caractères à la capacité du type entier utilisé pour mémoriser la longueur de la chaine. Dans la majorité des cas, il s’agit d’un unsigned char, ce qui donne une limite de 255 caractères maximum sur la plupart des machines. Pour ne pas subir cette limitation, les concepteurs du langage C ont adopté une autre solution : la fin de la chaîne de caractères sera indiquée par un caractère spécial, en l’occurrence zéro (noté '\0'). Les chaines de caractères qui fonctionnent sur ce principe sont appelées null terminated strings, ou encore C strings.

Cette solution a toutefois deux inconvénients :

  • la taille de chaque chaîne doit être calculée en la parcourant jusqu’au caractère nul ;
  • le programmeur doit s’assurer que chaque chaîne qu’il construit se termine bien par un caractère nul.

Définition, initialisation et utilisation

Définition

Définir une chaine de caractères, c’est avant tout définir un tableau de char, ce que nous avons vu au chapitre précédent. L’exemple ci-dessous définit un tableau de vingt-cinq char.

1
char tab[25];

Initialisation

Il existe deux méthodes pour initialiser une chaîne de caractères :

  • de la même manière qu’un tableau ;
  • à l’aide d’une chaîne de caractères littérale.

Avec une liste d’initialisation

Initialisation avec une longueur explicite

Comme pour n’importe quel tableau, l’initialisation se réalise à l’aide d’une liste d’initialisation. L’exemple ci-dessous définit donc un tableau de vingt-cinq char et initialise les sept premiers avec la suite de lettres « Bonjour ».

1
char chaine[25] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r' };

Étant donné que seule une partie des éléments sont initialisés, les autres sont implicitement mis à zéro, ce qui nous donne une chaîne de caractères valides puisqu’elle est bien terminée par un caractère nul. Faites cependant attention à ce qu’il y ait toujours de la place pour un caractère nul.

Initialisation avec une longueur implicite

Dans le cas où vous ne spécifiez pas de taille lors de la définition, il vous faudra ajouter le caractère nul à la fin de la liste d’initialisation pour obtenir une chaîne valide.

1
char chaine[] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r', '\0' };

Avec une chaîne littérale

Bien que tout à fait valide, cette première solution est toutefois assez fastidieuse. Aussi, il en existe une seconde : recourir à une chaîne de caractères littérales pour initialiser un tableau. Une chaîne de caractères littérales est une suite de caractères entourée par le symbole ". Nous en avons déjà utilisé auparavant comme argument des fonctions printf() et scanf().

Techniquement, une chaîne littérale est un tableau de char terminé par un caractère nul. Elles peuvent donc s’utiliser comme n’importe quel autre tableau. Si vous exécutez le code ci-dessous, vous remarquerez que l’opérateur sizeof retourne bien le nombre de caractères composant la chaîne littérale (n’oubliez pas de compter le caractère nul) et que l’opérateur [] peut effectivement leur être appliqué.

1
2
3
4
5
6
7
8
9
#include <stdio.h>


int main(void)
{
    printf("%u\n", (unsigned)sizeof "Bonjour");
    printf("%c\n", "Bonjour"[3]);
    return 0;
}
1
2
8
j

Ces chaînes de caractères littérales peuvent également être utilisées à la place des listes d’initialisation. En fait, il s’agit de la troisième et dernière exception à la règle de conversion implicite des tableaux.

Initialisation avec une longueur explicite

Dans le cas où vous spécifiez la taille de votre tableau, faites bien attention à ce que celui-ci dispose de suffisamment de place pour accueillir la chaîne entière, c’est-à-dire les caractères qui la composent et le caractère nul.

1
char chaine[25] = "Bonjour";
Initialisation avec une longueur implicite

L’utilisation d’une chaîne littérale pour initialiser un tableau dont la taille n’est pas spécifiée vous évite de vous soucier du caractère nul puisque celui-ci fait partie de la chaîne littérale.

1
char chaine[] = "Bonjour";

Utilisation de pointeurs

Nous vous avons dit que les chaînes littérales n’étaient rien d’autre que des tableaux de char terminés par un caractère nul. Dès lors, comme pour n’importe quel tableau, il vous est loisible de les référencer à l’aide de pointeurs.

1
char *ptr = "Bonjour";

Néanmoins, les chaînes littérales sont des constantes, il vous est donc impossible de les modifier. L’exemple ci-dessous est donc incorrect.

1
2
3
4
5
6
7
int main(void)
{
    char *ptr = "bonjour";

    ptr[0] = 'B'; /* Incorrect. */
    return 0;
}

Notez bien la différence entre les exemples précédents qui initialisent un tableau avec le contenu d’une chaîne littérale (il y a donc copie de la chaîne littérale) et cet exemple qui initialise un pointeur avec l’adresse du premier élément d’une chaîne littérale.

Utilisation

Pour le reste, une chaîne de caractères s’utilise comme n’importe quel autre tableau. Aussi, pour modifier son contenu, il vous faudra accéder à ses éléments un à un.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>


int main(void)
{
    char chaine[25] = "Bonjour";

    printf("%s\n", chaine);
    chaine[0] = 'A';
    chaine[1] = 'u';
    chaine[2] = ' ';
    chaine[3] = 'r';
    chaine[4] = 'e';
    chaine[5] = 'v';
    chaine[6] = 'o';
    chaine[7] = 'i';
    chaine[8] = 'r';
    chaine[9] = '\0'; /* N'oubliez pas le caractère nul ! */
    printf("%s\n", chaine);
    return 0;
}
1
2
Bonjour
Au revoir

Afficher et récupérer une chaîne de caractères

Les chaînes littérales n’étant rien d’autre que des tableaux de char, il vous est possible d’utiliser des chaînes de caractères là où vous employiez des chaînes littérales. Ainsi, les deux exemples ci-dessous afficheront la même chose.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <stdio.h>


int main(void)
{
    char chaine[] = "Bonjour\n";

    printf(chaine);
    return 0;
}
1
2
3
4
5
6
7
8
#include <stdio.h>


int main(void)
{
    printf("Bonjour\n");
    return 0;
}
1
Bonjour

Toutefois, les fonctions printf() et scanf() disposent d’un indicateur de conversion vous permettant d’afficher ou de demander une chaîne de caractères : s.

Printf

L’exemple suivant illustre l’utilisation de cet indicateur de conversion avec printf() et affiche la même chose que les deux codes précédents.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <stdio.h>


int main(void)
{
    char chaine[] = "Bonjour";

    printf("%s\n", chaine);
    return 0;
}
1
Bonjour

Scanf

Le même indicateur de conversion peut être utiliser avec scanf() pour demander à l’utilisateur d’entrer une chaîne de caractères. Cependant, un problème se pose : étant donné que nous devons créer un tableau de taille finie pour accueillir la saisie de l’utilisateur, nous devons impérativement limiter la longueur des données que nous fournit l’utilisateur.

Pour éviter ce problème, il est possible de spécifier une taille maximale à la fonction scanf(). Pour ce faire, il vous suffit de placer un nombre entre le symbole % et l’indicateur de conversion s. L’exemple ci-dessous demande à l’utilisateur d’entrer son prénom (limité à 254 caractères) et affiche ensuite celui-ci.

La fonction scanf() ne décompte pas le caractère nul final de la limite fournie ! Il vous est donc nécessaire de lui indiquer la taille de votre tableau diminuée de un.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    char chaine[255];

    printf("Quel est votre prénom ? ");

    if (scanf("%254s", chaine) != 1)
    {
        printf("Erreur lors de la saisie\n");
        return EXIT_FAILURE;
    }

    printf("Bien le bonjour %s !\n", chaine);
    return 0;
}
1
2
Quel est votre prénom ? Albert
Bien le bonjour Albert !

Chaîne de caractères avec des espaces

Sauf qu’en fait, l’indicateur s signifie : « la plus longue suite de caractère ne comprenant pas d’espaces » (les espaces étant ici entendu comme une suite d’un ou plusieurs des caractères suivant : ' ', '\f', '\n', '\r', '\t', '\v')…

Autrement dit, si vous entrez « Bonjour tout le monde », la fonction scanf() va s’arrêter au mot « Bonjour », ce dernier étant suivi par un espace.

Comment peut-on récupérer une chaîne complète alors ? :euh:

Eh bien, il va falloir nous débrouiller avec l’indicateur c de la fonction scanf() et réaliser nous même une fonction employant ce dernier au sein d’une boucle. Ainsi, nous pouvons par exemple créer une fonction recevant un pointeur sur char et la taille du tableau référencé qui lit un caractère jusqu’à être arrivé à la fin de la ligne ou à la limite du tableau.

Et comment sait-on que la lecture est arrivée à la fin d’une ligne ?

La fin de ligne est indiquée par le caractère \n.
Avec ceci, vous devriez pouvoir construire une fonction adéquate.
Allez, hop, au boulot et faites gaffe aux retours d’erreur ! :pirate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>


int lire_ligne(char *chaine, size_t max)
{
    size_t i;
    char c;

    for (i = 0; i < max - 1; ++i)
    {
        if (scanf("%c", &c) != 1)
            return 0;
        else if (c == '\n')
            break;

        chaine[i] = c;
    }

    chaine[i] = '\0';
    return 1;
}


int main(void)
{
    char chaine[255];

    printf("Quel est votre prénom ? ");

    if (lire_ligne(chaine, sizeof chaine))
        printf("Bien le bonjour %s !\n", chaine);

    return 0;
}
1
2
Quel est votre prénom ? Charles Henri
Bien le bonjour Charles Henri !

Gardez bien cette fonction sous le coude, nous allons en avoir besoin pour la suite. ;)

Lire et écrire depuis et dans une chaîne de caractères

S’il est possible d’afficher et récupérer une chaîne de caractères, il est également possible de lire depuis une chaîne et d’écrire dans une chaîne. À cette fin, deux fonctions qui devraient vous sembler familières existent : sprintf() et sscanf().

La fonction sprintf

1
int sprintf(char *chaine, char *format, ...);

Les trois petits points à la fin du prototype de la fonction signifient que celle-ci attend un nombre variable d’arguments. Nous verrons ce mécanisme plus en détail dans la troisième partie du cours.

La fonction sprintf() est identique à la fonction printf() mise à part que celle-ci écrit les données produites dans une chaîne de caractères au lieu de les afficher à l’écran. Celle-ci retourne le nombre de caractères écrit (sans compter le caractère nul final !) ou bien un nombre négatif en cas d’erreur.

Cette fonction peut vous permettre, entre autres, d’écrire un nombre dans une chaîne de caractères.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>

int main(void)
{
    char chaine[16];
    int n = 64;

    sprintf(chaine, "%d", n);
    printf("%s\n", chaine);
    return 0;
}
1
64

La fonction sprintf() n’effectue aucune vérification quant à la taille de la chaîne de destination, vous devez donc vous assurer qu’elle dispose de suffisamment de place pour accueillir la chaîne finale (caractère nul compris !).

Comment dès lors s’assurer qu’il n’y aura aucun débordement ? Malheureusement, il n’est pas possible de spécifier une taille comme avec l’indicateur s de la fonction scanf(), aussi, deux solutions s’offrent à vous :

  • vérifier que le nombre en question ne dépasse pas un certain seuil ;
  • compter la quantité de chiffres composant le nombre avant d’appeler sprintf().

Ainsi, l’exemple ci-dessous ne pose pas de problèmes puisque nous savons que si le nombre est inférieur ou égal à 999 999 999, il n’excèdera pas neuf caractères (n’oubliez pas de compter le caractère nul final !).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    char chaine[10];
    long n;

    printf("Entrez un nombre : ");

    if (scanf("%ld", &n) != 1 || n > 999999999L)
    {
        printf("Erreur lors de la saisie\n");
        return EXIT_FAILURE;
    }

    sprintf(chaine, "%ld", n);
    printf("%s\n", chaine);
    return 0;
}
1
2
3
4
5
Entrez un nombre : 890765456789
Erreur lors de la saisie

Entrez un nombre : 5678
5678

La fonction sscanf

1
int sscanf(char *chaine, char *format, ...);

La fonction sscanf() est identique à la fonction scanf() si ce n’est que celle-ci extrait les données depuis une chaîne de caractères plutôt qu’en provenance d’une saisie de l’utilisateur. Cette dernière retourne le nombre de conversions réussies ou un nombre inférieur si elles n’ont pas toutes été réalisées ou enfin un nombre négatif en cas d’erreur.

Voici un exemple d’utilisation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    char chaine[10];
    int n;

    if (sscanf("5 abcd", "%d %9s", &n, chaine) != 2)
    {
        printf("Erreur lors de l'examen de la chaîne\n");
        return EXIT_FAILURE;
    }

    printf("%d %s\n", n, chaine);
    return 0;
}
1
5 abcd

Notez que la fonction sscanf() ne souffre pas du même problème que scanf() en ce qui concerne de potentiels caractères non lus, nous y reviendrons un peu plus tard.

Les classes de caractères

Pour terminer cette partie théorique sur une note un peu plus légère, sachez que la bibliothèque standard fournie un en-tête <ctype.h> qui permet de classifier les caractères. Onze fonctions sont ainsi définies.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int isalnum(int c);
int isalpha(int c);
int iscntrl(int c);
int isdigit(int c);
int isgraph(int c);
int islower(int c);
int isprint(int c);
int ispunct(int c);
int isspace(int c);
int isupper(int c);
int isxdigit(int c);

Chacune d’entre elles attend en argument un caractère et retourne un nombre positif ou zéro suivant que le caractère fourni appartienne ou non à la catégorie déterminée par la fonction.

Fonction Catégorie Description Par défaut
isupper Majuscule Détermine si le caractère entré est une lettre majuscule 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' ou 'Z'
islower Minuscule Détermine si le caractère entré est une lettre minuscule 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'o', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y' ou 'z'
isdigit Chiffre décimal Détermine si le caractère entré est un chiffre décimal '0', '1', '2', '3', '4', '5', '6', '7', '8' ou '9'
isxdigit Chiffre hexadécimal Détermine si le caractère entré est un chiffre hexadécimal '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e' ou 'f'
isspace Espace Détermine si le caractère entré est un espace ' ', '\f', '\n', '\r', '\t' ou '\v'
iscntrl Contrôle Détermine si le caractère est un caractère dit « de contrôle » '\0', '\a', '\b', '\f', '\n', '\r', '\t' ou '\v'
ispunct Ponctuation Détermine si le caractère entré est un signe de ponctuation '!', '"', '#', '%', '&', ''', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', '\', ']', '^', '_', '{', '<barre droite>', '}' ou '~'
isalpha Alphabétique Détermine si le caractère entré est une lettre alphabétique Les deux ensembles de caractères de islower() et isupper()
isalnum Alphanumérique Détermine si le caractère entré est une lettre alphabétique ou un chiffre décimal Les trois ensembles de caractères de islower(), isupper() et isdigit()
isgraph Graphique Détermine si le caractère est représentable graphiquement Tout sauf l’ensemble de iscntrl() et l’espace (' ')
isprint Affichable Détermine si le caractère est « affichable » Tout sauf l’ensemble de iscntrl()

La suite <barre_droite> symbolise le caractère |.

Le tableau ci-dessous vous présente chacune de ces onze fonctions ainsi que les ensembles de caractères qu’elles décrivent. La colonne « par défaut » vous détail leur comportement en cas d’utilisation de la locale C. Nous reviendrons sur les locales dans la troisième partie de ce cours ; pour l’heure, considérez que ces fonctions ne retournent un nombre positif que si un des caractères de leur ensemble « par défaut » leur est fourni en argument.

L’exemple ci-dessous utilise la fonction isdigit() pour déterminer si l’utilisateur a bien entré une suite de chiffres.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/* lire_ligne() */


int main(void)
{
    char suite[255];
    unsigned i;

    if (!lire_ligne(suite, sizeof suite))
    {
        printf("Erreur lors de la saisie.\n");
        return EXIT_FAILURE;
    }

    for (i = 0; suite[i] != '\0'; ++i)
        if (!isdigit(suite[i]))
        {
            printf("Veuillez entrer une suite de chiffres.\n");
            return EXIT_FAILURE;
        }

    printf("C'est bien une suite de chiffres.\n");
    return 0;
}
1
2
3
4
5
122334
C'est bien une suite de chiffres.

5678a
Veuillez entre une suite de chiffres.

Notez que cet en-tête fourni également deux fonctions : tolower() et toupper() retournant respectivement la version minuscule ou majuscule de la lettre entrée. Dans le cas où un caractère autre qu’une lettre est entré (ou que celle-ci est déjà en minuscule ou en majuscule), la fonction retourne celui-ci.

Exercices

Palindrome

Un palindrome est un texte identique lorsqu’il est lu de gauche à droite et de droite à gauche. Ainsi, le mot radar est un palindrome, de même que les phrases engage le jeu que je le gagne et élu par cette crapule. Normalement, il n’est pas tenu compte des accents, trémas, cédilles ou des espaces. Toutefois, pour cet exercice, nous nous contenterons de vérifier si un mot donné est un palindrome.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int palindrome(char *s)
{
    size_t len = 0;
    size_t i;

    while (s[len] != '\0')
        ++len;

    for (i = 0; i < len; ++i)
        if (s[i] != s[len - 1 - i])
            return 0;

    return 1;
}

Compter les parenthèses

Écrivez un programme qui lit une ligne et vérifie que chaque parenthèse ouvrante est bien refermée par la suite.

1
2
Entrez une ligne : printf("%u\n", (unsigned)sizeof(int);
Il manque des parenthèses.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>

/* lire_ligne() */


int main(void)
{
    char s[255];
    char *t;
    long n = 0;

    printf("Entrez une ligne : ");

    if (!lire_ligne(s, sizeof s))
    {
        printf("Erreur lors de la saisie.\n");
        return EXIT_FAILURE;
    }

    for (t = s; *t != '\0'; ++t)
        if (*t == '(')
            ++n;
        else if (*t == ')')
            --n;

    if (n == 0)
        printf("Le compte est bon.\n");
    else
        printf("Il manque des parenthèses.\n");

    return 0;
}

Le chapitre suivant sera l’occasion de mettre en pratique ce que nous avons vu dans les chapitres précédents à l’aide d’un TP.