Les chaines de caractères

Pour manipuler les chaines de caractères, Python possède un type str. Un caractère unique est un cas particulier d’un objet de type str de longueur 1.

A contrario le langage C dispose du type primitif char pour manipuler les caractères uniques, et la chaîne de caractères est traitée comme un tableau de n char terminé par \0.

On retrouve là les deux caractéristiques des deux langages :

  • Python est un langage haut niveau et manipule la chaine de caractère ;

  • C est un langage bas niveau et manipule le caractère.

Déclaration

La chaine de caractères étant un tableau de caractères, la déclaration peut donc se faire de façon similaire à ce qu’on a vu pour les tableaux :

char s[14] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', ' ', '!', '\0'};

On lui préfèrera cependant une façon plus compacte, où la taille sera déduite de la partie RHS de la déclaration (comme déjà vu dans le chapitre sur les tableaux) et la chaine définie par une suite de caractères délimitée par des guillemets " :

char s[] = "Hello World !";

Pour cette deuxième façon d’initialiser la chaine de caractères, le caractère terminal \0 (nécessaire pour identifier la fin) est inséré automatiquement à la fin.

Corollaire : une chaine de n caractères utilisera n+1 octets en mémoire.

A expérimenter

On considère le programme suivant :

 1// str.c
 2
 3#include <stdio.h>
 4
 5int main()
 6{
 7    char s[] = "Hello World !";
 8
 9    for (int i=0 ; s[i]!='\0' ; i++)
10        printf("i = %2d, s[%2d] = %c, &s[%2d] = %p\n", i, i, s[i], i, &s[i]);
11
12return 0;
13}

Répondez aux questions suivantes :

  • avant de l’exécuter, uniquement en l’examinant, que fait-il ?

  • prêter une attention particulière à l’écriture de la condition de continuation (ligne 9). Quelle propriété des chaines de caractères met elle en oeuvre ?

  • après exécution du programme, et observation des adresses mémoire, peut on retrouver la taille allouée à une variable de type char ?

  • pourquoi le caractère terminal \0 n’est il pas affiché ?

Affichage

L’expérimentation précédente met en oeuvre un affichage caractère par caractère, utile pour la compréhension mais sans intérêt pour l’affichage dans le terminal. La fonction printf() dispose de l’espace réservé %s pour l’affichage des chaines :

Le code

printf("%s\n", s);

produit dans le terminal:

Hello World !

Manipulation avec des pointeurs

Puisqu’une chaine de caractères est un tableau, on peut également la manipuler avec un pointeur. En particulier, si s est déclarée comme une chaine de caractères, les trois instructions ci dessous sont équivalentes et affichent l’adresse mémoire du premier caractère de la chaîne.

printf("%p\n", s);
printf("%p\n", &s);
printf("%p\n", &s[0]);

On peut également se servir de l’arithmétique des pointeurs pour l’affichage caractère par caractère :

char *p = s;

while(*p != '\0') {
        printf("%c", *p);
        p++;
}

Note

On pourrait aussi penser à utiliser un pointeur pour déclarer la chaine :

char* p = "Hello World !";

Mais cette façon de faire devra toutefois être évitée car dans ce cas la modification n’est pas une opération sûre.

Il est préférable de déclarer la chaine de façon classique, puis un pointeur pour la parcourir :

char s[] = "Hello World !";
char *p = s;

while(*p != '\0') {
    printf("%c", *p);
    p++;
}

Tableau de chaines

Une chaine de caractères étant déjà un tableau de caractères, un tableau de chaines de caractères sera donc implémenté sous la forme d’un tableau multidimensionnel :

 1// arrayofstrings.c
 2
 3#include <stdio.h>
 4
 5const int SIZE = 5;
 6
 7int main()
 8{
 9
10    char *simpsons[] = {
11        "Homer",
12        "Marge",
13        "Bart",
14        "Lisa",
15        "Maggie"};
16
17    char *p;
18
19    for (int i = 0; i < SIZE; i++)
20    {
21        p = simpsons[i];
22
23        while (*p != '\0')
24        {
25            printf("%c", *p);
26            p++;
27        }
28
29        printf("\n");
30    }
31}

Le code ci dessus présente deux particularités :

  1. l’utilisation d’une constante (ligne 5). Dans tout le code SIZE sera remplacée par le compilateur par la valeur 5 ;

  2. le double parcours du tableau à deux dimensions :
    • on accède aux chaines de caractères par indice (ligne 19) ;

    • chaque chaine est parcourue par pointeur (ligne 23).

L’exécution donne le résultat attendu

$ gcc -Wall -Wextra arrayofstrings.c -o arrayofstrings
$ ./arrayofstrings
Homer
Marge
Bart
Lisa
Maggie
$

Lecture du clavier

Jusqu’à présent les données utilisées étaient stockées « en dur » dans le code source. Ca nous a permis de mettre en évidence les principes élémentaires du langage C mais il y aurait évidemment une plus grande souplesse à pouvoir lire quelques informations directement au clavier, durant l’exécution du programme.

La fonction fgets() permet ceci :

 1#include <stdio.h>
 2#include <string.h>
 3
 4int main(){
 5    char s[100];
 6
 7    printf("Entrez une chaine de caractères : %s\n", s);
 8    fgets(s, 100, stdin);
 9    printf("La chaine de caractères récupérée dans s : %s\n", s);
10    printf("composée de %ld caractères lus au clavier\n", strlen(s));
11}

A la ligne 8, la fonction fgets() prend en paramètres :

  • la chaine dans laquelle les caractères saisis au clavier seront stockés (elle est déclarée ligne 5) ;

  • le nombre maximal de caractères à lire ;

  • et le périphérique utilisé. stdin représente le clavier mais on pourrait tout aussi bien lire les données dans un fichier.

Lorsqu’on exécute ce programme:

$ gcc -std=c99 -Wall -Wextra read-keyboard.c -o read-keyboard
$ ./read-keyboard
Entrez une chaine de caractères :
hello
La chaine de caractères récupérée dans s : hello

composée de 6 caractères lus au clavier
$

Pour cette exécution, l’utilisateur a rentré au clavier la chaine hello et validé la saisie avec Enter.

Observer l’affichage dans le terminal et notamment :

  • le saut de ligne. D’où provient il ?

  • le nombre de caractères stockés dans la chaine s. Est il en accord avec la chaine entrée au clavier ? D’où provient l’écart ?

Exercice

Ecrire une boucle permettant d’afficher chaque caractère de la chaine sur une ligne séparée en ajoutant l’information de position. Le résultat devrait être similaire à

$ ./read-keyboard
Entrez une chaine de caractères :
hello
La chaine de caractères récupérée dans s : hello

composée de 6 caractères lus au clavier
Caractère  0 : h
Caractère  1 : e
Caractère  2 : l
Caractère  3 : l
Caractère  4 : o
Caractère  5 :

$

Conversion numérique

Il est courant que les données entrées au clavier soient numériques, sous forme d’entiers ou de nombres rééls. La fonction fgets() stocke la saisie au clavier dans une chaine de caractères. Il faut donc une opération de conversion de type. Celle ci est réalisée en C avec les fonctions suivantes, dont le prototype est défini dans <stdlib.h> :

  • atoi() pour la conversion vers un entier ;

  • atod() pour la conversion vers un double.

Exercice

Ajouter une fonction de conversion au programme précédent, ainsi qu’une manipulation numérique de la chaine convertie. Le résultat devrait être similaire à

$ ./read-keyboard
Entrez une chaine de caractères :
123456
après conversion : 123456
et multiplication par 2 : 246912

Passage des arguments en ligne de commande

Lire le clavier c’est bien, mais passer directement les arguments sur la ligne de commande, c’est mieux !

Jusqu’à présent, l’appel de la fonction main() se faisait sans aucun argument. Et la ligne de commande déclenchant l’exécution du programme ne comportait qu’un seul élément.

Par exemple:

$ ./read-keyboard

Mais le langage C permet de récupérer les caractères saisis sur la même ligne, pour les utiliser comme données d’entrée. Pour cela, il faut utiliser les deux paramètres optionnels (dont on s’est passé jusqu’à présent) pour appeler la fonction main() :

int main( int argc, char *argv[] )

Ici :

  • argc représente le nombre total d’arguments sur la ligne de commande, y compris le nom du programme exécutable ;

  • et *argv[] un pointeur vers le premier élément d’un tableau de strings qui contient les arguments.

Un exemple d’utilisation :

// cmdline.c

#include <stdio.h>

int main(int argc, char *argv[])
{
    for (int i=0; i < argc ; i++)
    {
        printf("argument %d : %s\n", i, argv[i]);
    }
    return 0;
}

Lorsqu’on exécute ce programme:

$ ./cmdline james bond 007
argument 0 : ./cmdline
argument 1 : james
argument 2 : bond
argument 3 : 007