De la même façon qu'un pointeur sur int peut contenir l'adresse d'un nombre isolé ou d'une composante d'un tableau, un pointeur sur char peut pointer sur un caractère isolé ou sur les éléments d'un tableau de caractères. Un pointeur sur char peut en plus contenir l'adresse d'une chaîne de caractères constante et il peut même être initialisé avec une telle adresse.
A la fin de ce chapitre, nous allons anticiper avec un exemple et montrer que les pointeurs sont les éléments indispensables mais effectifs des fonctions en C.
- Pointeurs sur char et chaînes de caractères constantes
Affectation
a) On peut attribuer l'adresse d'une chaîne de caractères constante à un pointeur sur char:
Exemple
char *C; C = "Ceci est une chaîne de caractères constante";
Nous pouvons lire cette chaîne constante (p.ex: pour l'afficher), mais il n'est pas recommandé de la modifier, parce que le résultat d'un programme qui essaie de modifier une chaîne de caractères constante n'est pas prévisible en ANSI-C.
Initialisation
b) Un pointeur sur char peut être initialisé lors de la déclaration si on lui affecte l'adresse d'une chaîne de caractères constante:
char *B = "Bonjour !";
Attention !
Il existe une différence importante entre les deux déclarations:
char A[] = "Bonjour !"; /* un tableau */ char *B = "Bonjour !"; /* un pointeur */
A est un tableau qui a exactement la grandeur pour contenir la chaîne de caractères et la terminaison '\0'. Les caractères de la chaîne peuvent être changés, mais le nom A va toujours pointer sur la même adresse en mémoire.
B est un pointeur qui est initialisé de façon à ce qu'il pointe sur une chaîne de caractères constante stockée quelque part en mémoire. Le pointeur peut être modifié et pointer sur autre chose. La chaîne constante peut être lue, copiée ou affichée, mais pas modifiée.
c) Si nous affectons une nouvelle valeur à un pointeur sur une chaîne de caractères constante, nous risquons de perdre la chaîne constante. D'autre part, un pointeur sur char a l'avantage de pouvoir pointer sur des chaînes de n'importe quelle longueur:
Exemple
char *A = "Petite chaîne"; char *B = "Deuxième chaîne un peu plus longue"; A = B;
Maintenant A et B pointent sur la même chaîne; la "Petite chaîne" est perdue:
Les affectations discutées ci-dessus ne peuvent pas être effectuées avec des tableaux de caractères:
Exemple
char A[45] = "Petite chaîne"; char B[45] = "Deuxième chaîne un peu plus longue"; char C[30]; A = B; /* IMPOSSIBLE -> ERREUR !!! */ C = "Bonjour !"; /* IMPOSSIBLE -> ERREUR !!! */
Pour changer le contenu d'un tableau, nous devons changer les composantes du tableau l'une après l'autre (p.ex. dans une boucle) ou déléguer cette charge à une fonction de <stdio> ou <string>.
Conclusions:
Perspectives et motivation
- Avantages des pointeurs sur char
Comme la fin des chaînes de caractères est marquée par un symbole spécial, nous n'avons pas besoin de connaître la longueur des chaînes de caractères; nous pouvons même laisser de côté les indices d'aide et parcourir les chaînes à l'aide de pointeurs.
Cette façon de procéder est indispensable pour traiter de chaînes de caractères dans des fonctions. En anticipant sur la matière du chapitre 10, nous pouvons ouvrir une petite parenthèse pour illustrer les avantages des pointeurs dans la définition de fonctions traitant des chaînes de caractères:
Pour fournir un tableau comme paramètre à une fonction, il faut passer l'adresse du tableau à la fonction. Or, les paramètres des fonctions sont des variables locales, que nous pouvons utiliser comme variables d'aide. Bref, une fonction obtenant une chaîne de caractères comme paramètre, dispose d'une copie locale de l'adresse de la chaîne. Cette copie peut remplacer les indices ou les variables d'aide du formalisme tableau.
Discussion d'un exemple
Reprenons l'exemple de la fonction strcpy, qui copie la chaîne CH2 vers CH1. Les deux chaînes sont les arguments de la fonction et elles sont déclarées comme pointeurs sur char. La première version de strcpy est écrite entièrement à l'aide du formalisme tableau:
void strcpy(char *CH1, char *CH2) { int I; I=0; while ((CH1[I]=CH2[I]) != '\0') I++; }
Dans une première approche, nous pourrions remplacer simplement la notation tableau[I] par *(tableau + I), ce qui conduirait au programme:
void strcpy(char *CH1, char *CH2) { int I; I=0; while ((*(CH1+I)=*(CH2+I)) != '\0') I++; }
Cette transformation ne nous avance guère, nous avons tout au plus gagné quelques millièmes de secondes lors de la compilation. Un 'véritable' avantage se laisse gagner en calculant directement avec les pointeurs CH1 et CH2 :
void strcpy(char *CH1, char *CH2) { while ((*CH1=*CH2) != '\0') { CH1++; CH2++; } }
Comme nous l'avons déjà constaté dans l'introduction de ce manuel, un vrai professionnel en C escaladerait les 'simplifications' jusqu'à obtenir:
void strcpy(char *CH1, char *CH2) { while (*CH1++ = *CH2++) ; }
Assez 'optimisé' - fermons la parenthèse et familiarisons-nous avec les notations et les manipulations du 'formalisme pointeur' ...