Chapitre 10 : Chaînes de caractères
En C, on peut présenter une chaîne de caractères sous forme de : 1. tableau des caractères : char nom [20] ;
ou de 2. pointeur vers le type caractère : char * telephone ;
1) Schéma de représentation d'une chaîne de caractères :
Si la valeur d'une chaîne de caractères nommée urgent vaut "911"
par exemple, on la présente avec le schéma suivant :
| '9' | '1' | '1' |'\0'|
urgent ---> |_____|_____|_____|____|
La chaîne se termine par le caractère invisible '\0' qui marque sa fin.
2) Déclaration, initialisation et affectation :
Écrire d'autres manières différentes pour déclarer la chaîne urgent et donner la valeur "911" à cette chaîne.
Solution :
1. Sous forme tableau des caractères :
a. Manière 1 :
char urgent[4] ; /* pour le caractère '\0' aussi */
strcpy(urgent, "911"); /* string copy : copie une chaîne à une autre chaîne (string.h) */
Il est intéressant de noter que l'affectation suivante est invalide :
urgent = "911" ;
urgent est le nom d'un tableau, il n'est pas une "lvalue"
(left value : valeur à gauche une affectation).
b. Manière 2 : déclarer et "initialiser"
char urgent[4] = "911" ;
c. Manière 3 : déclarer et "initialiser"
char urgent[4] = { '9', '1', '1', '\0' } ;
d. Manière 4 : déclarer et "affecter"
char urgent[4] ; urgent[0] = '9' ; urgent[1] = '1' ; urgent[2] = '1' ; urgent[3] = '\0';
e. Manière 5 : déclarer et lire sa valeur
char urgent[4] ;
printf("Quel est le numéro pour l'urgence ? ");
scanf("%s", urgent); /* ou gets(urgent) ; avec <string.h> */
Notez que la lecture avec scanf n'est pas assez pratique : char nomPre [20] ;
printf("Entrez le nom et prénom ");
scanf("%s", nomPre);
printf("Le nom et prénom lu : %s\n", nomPre);
Si le nom et prénom tapé est Cloutier Gilles
la valeur de nomPre est "Cloutier" seulement! : l'espace entre Cloutier et Gilles provoque la fin de la saisie. On perd une partie des informations.
2. Sous forme pointeur vers le type char
char * urgent ;
On peut faire des choses semblables comme avec un tableau des caractères :
char * urgent = "911" ; OU char * urgent ;
...
strcpy(urgent , "911"); OU char * urgent ;
...
gets(urgent);
De plus, on peut affecter (qui n'est pas le cas d'un tableau) : urgent = "911" ; /* correct ici! */
Cependant, il est préférable d'allouer la mémoire avant de faire l'affectation et surtout de la lecture :
urgent = (char *) malloc (4) ; /* avec <stdlib.h> */
urgent = "911" ;
3) Affichage du contenu d'une chaîne de caractères :
Soit char * urgent = "911" ; OU char urgent[4] = "911" ;
Longueur d'une chaîne (strlen : string length) :
strlen("911") vaut 3 (ne pas tenir compte de '\0') strlen(urgent) vaut 3
Affichage d'une chaîne (code de format %s) :
L'instruction :
printf("Numéro pour l'urgence : %s\n", urgent);
fait afficher :
Numéro pour l'urgence : 911
Mise en garde :
L'instruction :
printf("Numéro pour l'urgence : %c\n", *urgent);
fait afficher seulement : Numéro pour l'urgence : 9 Pourquoi ?
urgent est un pointeur vers le type char : | '9' | '1' | '1' |'\0'|
urgent ---> |_____|_____|_____|____|
*urgent
*urgent est de type char, *urgent vaut '9' ici.
4) La concaténation des chaînes de caractères :
char * ch1 , * ch2 ;
ch1 = (char *) malloc(80);
ch1 = "Bonjour ";
ch2 = "tout le monde!";
Après l'instruction :
strcat(ch1, ch2) ;
ch1 vaut "Bonjour tout le monde!".
Après l'instruction :
strncat(ch1, ch2, 3) ;
ch1 vaut "Bonjour tou".
5) La copie des chaînes de caractères :
a) char * strcpy(char * destination, char * source) Exemple :
...
strcpy(nomPre,"LALIBERTE PIERRE");
printf("%s", nomPre);
Le bloc affiche : LALIBERTE PIERRE
b) char * strncpy (char * destination,char * source, int k) (on copie jusqu'à k caractères au maximum)
Exemple : ...
strncpy(nomPre,"LALIBERTE PIERRE",4);
printf("%s", nomPre);
Le bloc affiche : LALI
6) La comparaison des chaînes de caractères :
a) int strcmp(char * chaine1, char * chaine2)
valeur retournée par la fonction signification < 0 chaine1 < chaine2 0 chaine1 = chaine2 > 0 chaine1 > chaine2 Exemple :
...
strcmp("Bon", "Bourse") est inférieur à 0 car 'n' < 'u' (les 2ères lettres sont pareilles)
strcmp("Bon", "Bon") vaut 0
strcmp("papa", "GRAND") est supérieur à zéro car 'p' > 'G' (code ASCII)
b) int strncmp(char * chaine1,char * chaine2, int k) Comme strcmp en se limitant aux k premiers caractères.
Exemple : ...
strncmp("Bon", "Bourse", 2) est 0 strncmp("Bon", "Bourse", 3) est < 0 etc ...
c) int stricmp (char * chaine1, char * chaine2)
int strincmp(char * chaine1, char * chaine2, int k)
Elles fonctionnent comme les deux dernières fonctions sans tenir compte de la différence entre les majuscules et les minuscules.
Exemple : ...
strincmp("Bon", "bobo", 2) est 0
7) La recherche dans une chaîne de caractères :
a) char * strchr(char * chaine, char unCar)
valeur retournée par la fonction signification un pointeur NULL unCar ne fait pas partie de la chaîne un pointeur non NULL unCar fait partie qui pointe vers le 1er de la chaîne caractère trouvé à partir
du début de la chaîne
Exemple : ...
do
{ printf("Tapez votre choix parmi les options A, T ou Q ");
fflush(stdin);
choix = getchar();
if (strchr("ATQ", choix) == NULL) printf("choix invalide\n");
}
while ( strchr("ATQ", choix) == NULL );
...
Avec char * P ; et P = strchr("Bonsoir", 'o');
on a :
| P (non NULL) |
v
| 'B' | 'o' | 'n' | 's' | 'o' | 'i' | 'r' |'\0'|
|_____|_____|_____|_____|_____|_____|_____|____|
b) char * strrchr(char * chaine,char unCar)
Elle fonctionne comme strchr en examinant la chaîne à partir de la fin :
Avec char * P ; et
P = strrchr("Bonsoir", 'o');
on a :
| P (non NULL) |
v
| 'B' | 'o' | 'n' | 's' | 'o' | 'i' | 'r' |'\0'|
|_____|_____|_____|_____|_____|_____|_____|____|
c) char * strstr(char * chaine, char * sousChaine)
Elle recherche dans la chaîne, la première occurrence complète de la sous chaîne.
Exemple :
Avec char * P ; et
P = strstr("Bonsoir", "so");
on a :
| P (non NULL) |
v
| 'B' | 'o' | 'n' | 's' | 'o' | 'i' | 'r' |'\0'|
|_____|_____|_____|_____|_____|_____|_____|____|
8) La conversion :
a) MAJUSCULE et minuscule :
strupr (chaine); ==> la chaîne sera en majuscule strlwr (chaine); ==> la chaîne sera en minuscule
b) chaîne de caractères en valeur numérique :
int atoi (char * chaine) : chaîne en entier
long atol (char * chaine) : chaîne en "long" entier
double atof (char * chaine) : chaîne en double (grand réel)
c) valeur numérique en chaîne de caractères :
char * itoa(int n, char * chaine, int base) :
un entier dans une base (2 à 36 dont 10 est la base décimale) en chaîne de caractères
char * ltoa(long n, char * chaine, int base) :
un long entier dans une base (2 à 36 dont 10 est la base décimale) en chaîne de caractères
9) La lecture dans un fichier texte :
Exemple d'un fichier de données de type texte : LONGPRE LISE 1.67 58.6 CHARTRAND ANDRE 1.72 75.4 etc ...
La lecture :
#define LONG_NP 31
char nomPre[LONG_NP] ; /* y compris le caractère '\0' */
...
while (!feof(donnees)) {
fgets (nomPre, LONG_NP, donnees); /* dans <string.h> */
fscanf(donnees,"%f%f\n", &Taille, &Poids);
etc ...
}
10) Traitement d'une chaîne par soi-même :
1. Arithmétique des pointeurs :
a. Soient les déclarations suivantes :
int t[4] = { 5, 10, 15, 20 }, * P = t ; P est un pointeur vers t[0] (car t vaut &t[0]) :
P
| Adresse (2 octets pour
└---> ╔═══════════════╗ 178 mémoriser 1 entier) t[0] ║ 5 ║ 179
╔═══════════════╗ 180 t[1] ║ 10 ║ 181 ╔═══════════════╗ 182 t[2] ║ 15 ║ 183 ╔═══════════════╗ 184 t[3] ║ 20 ║ 185 ╔═══════════════╗
En C, P + 2 par exemple a un sens, c'est l'adresse de t[2]
(182 sur le schéma) :
P + 2 = P + 2 * (sizeof(*P)) (*P est de type int) = 178 + 2 * 2
= 182 (c'est l'adresse de t[2]) Ainsi :
P++ ; qui est équivalent à P = P + 1 ;
fait pointer le pointeur P vers l'élément suivant (t[1])
b. Soient les instructions suivantes : char * ch = "Bonsoir" , * P = ch ;
| 'B' | 'o' | 'n' | 's' | 'o' | 'i' | 'r' |'\0'|
P ---> |_____|_____|_____|_____|_____|_____|_____|____|
L'instruction :
if (*P++) printf("%c", *P);
fait afficher la lettre 'o' à l'écran et fait avancer le pointeur P vers le caractère 'o' :
Notez que if ( *P++ ) <===> if (*P != '\0' , P++)
Comme *P est 'B' qui n'est pas '\0', la condition est vraie et P++ fait pointer P vers 'o'.
P | 'B' | 'o' | 'n' | 's' | 'o' | 'i' | 'r' |'\0'|
| |_____|_____|_____|_____|_____|_____|_____|____|
| ^ | | └---┘
Ainsi, printf("%c", *P) ; fait afficher le caractère pointé par P, c'est la lettre 'o'.
2. Parcourir tous les caractères d'une chaîne :
a. On utilise souvent la longueur et les indices : char souhaite[30] = "Bonne chance!";
/* Ou char * souhaite = "Bonne chance!" ; */
int longueur = strlen(souhaite), i ; for ( i = 0 ; i < longueur ; i++) printf("%c", souhaite[i]);
b. On utilise la notion de pointeur : char * urgent = "911" , * P ; P = urgent ;
while (*P) printf("%c", *P++);
Notez que la valeur de *P est le caractère pointé par P et que (*P) est équivalent à (*P != '\0').
De plus, P++ permet de faire pointer P vers le prochaine caractère.
Au début :
| '9' | '1' | '1' |'\0'|
P ---> |_____|_____|_____|____|
*P
Comme *P vaut '9' qui est différent de '\0', on affiche la valeur de *P, c'est 9 et on fait pointer P vers le prochaine caractère :
P ────────┐
v
| '9' | '1' | '1' |'\0'|
|_____|_____|_____|____|
*P
Comme *P vaut '1' qui est différent de '\0', on affiche la valeur de *P, c'est 1 et on fait pointer P vers le prochaine caractère :
P ───────────────┐
v
| '9' | '1' | '1' |'\0'|
|_____|_____|_____|____|
*P etc ....
3. Déterminer la longueur d'une chaîne par soi-même :
int longueur ( const char * chaine ) {
int N = 0 ;
while (*chaine++) N++;
return N;
}
Exercice :
Simuler la fonction avec la chaîne : "Bonsoir".
11) Exemples et exercices :
Exemple 1 :
Écrire un bloc d'énoncés permettant d'afficher les 7 journées de la semaine comme suit :
Journée 1 : dimanche Journée 2 : lundi etc ....
Solution :
int rang ;
char * jours[7] = { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" };
for ( rang = 0 ; rang < 7 ; rang++)
printf("Journée %d : %s\n", rang+1, jours[rang]);
Exemple 2 :
Écrire un programme permettant de saisir une chaîne de caractères tapés au clavier, d'afficher la chaîne, de vérifier et d'afficher si la chaîne est un palindrôme (même lecture dans deux sens : exemple "Laval").
Solution :
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <string.h>
int palindrome ( char chaine[] ) { int i , j , n = strlen(chaine);
strupr(chaine); /* convertir en MAJUSCULE */
for ( i = 0, j = n - 1 ; i < j ; i++, j-- ) if ( chaine[i] != chaine[j] ) return 0 ; return 1 ;
}
void main () { char * ch ;
ch = (char *) malloc(90) ; /* allouer la mémoire */
printf("Tapez une chaîne de caractères ");
gets(ch);
printf("La chaine %s %s\n", ch, palindrome(ch) ?
" est un palindrôme" : " n'est pas un palindrôme " );
} Exercice 1 :
Soient les instructions suivantes : char * ch = "Bonsoir" , * P = ch ; int N = 0 ;
Quel est le rôle de N dans le bloc d'instructions suivantes ? : while (*P)
if ( strchr("AEIOUY", toupper(*P++)) ) N++ ;
Exemple 3 :
Écrire un programme qui permet de saisir le nom d'un verbe régulier du premier groupe (ex. chanter, aimer, ...). Le programme conjugue le verbe au présent de l'indicatif.
Solution :
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <string.h>
#include <ctype.h>
void obtenir( char * verbe )
/* verbe régulier (pas manger ==> nous mangeons à la place de nous mangeons),
premier groupe (se termine par er (comme aimer) */
{
char * P ; do
{ printf("Entrez le nom d'un verbe régulier du 1er groupe "):
gets(verbe);
P = strstr(verbe, "er") ;
if (!P) printf("%s n'est pas du premier groupe\n", verbe);
}
while (!P);
}
void conjuger(char * verbe) { char * base ;
int i, k ;
k = strlen(verbe);
base = (char *) malloc(k-2);
for ( i = 0 ; i < k-2 ; i++ ) base[i] = verbe[i];
base[k-2] = '\0';
printf("\n\nPrésent de l'indicatif du verbe %s\n", verbe);
if (strchr("AEIOUY", toupper(*verbe))) printf("J' ");
else printf("Je ");
printf("%se\n", base);
printf("Tu %ses\n", base);
printf("Il %se\n", base);
printf("Nous %sons\n", base);
printf("Vous %sez\n", base);
printf("Ils %sent\n", base);
}
void main() { char * verbe ; do
{
verbe = (char *) malloc(80);
obtenir(verbe);
conjuger (verbe);
printf("Avez-vous un autre verbe à conjuger ? (O/N) ");
fflush(stdin);
}
while ( toupper(getchar()) == 'O');
}
Exécution :
Entrez le nom du verbe régulier du 1er groupe chanter Présent de l'indicatif du verbe chanter
Je chante Tu chantes Il chante Nous chantons Vous chantez Ils chantent
Avez-vous un autre verbe à conjuger ? (O/N) o
Entrez le nom d'un verbe régulier du 1er groupe aimer
Présent de l'indicatif du verbe aimer J' aime
Tu aimes Il aime Nous aimons Vous aimez Ils aiment
Avez-vous un autre verbe à conjuger ? (O/N) n
&d0DExemple 4&d@ :
Écrire votre "propre fonction" qui joue le rôle de la fonction "strcat" (concaténation de chaînes de caractères, page 87).
Solution :
char * concat ( char * destination, const char * source ) {
char * P = destination + strlen(destination) ; while (*source) *P++ = *source++ ;
*P = '\0';
return destination ; }
Notes :
L'instruction : while (*source) *P++ = *source++ ; peut s'écrire plus court encore comme suit :
while (*P++ = *source++) ;
dont l'expression entre ( et ) est évaluée comme suit : P = source ; /* une affectation */
*P est-il différent de '\0' (condition sous while) source++ ; /* vers le caractère suivant */
P++ ; /* vers le caractère suivant */
Une version plus "compréhensible" de cette fonction est : char * concat ( char * destination, const char * source ) {
char * P = destination ; while (*P != '\0') P++ ; while (*source != '\0') {
*P = *source ; P++ ;
source++;
}
*P = '\0';
return destination ; }
Exercice 2 :
Écrire vos "propres fonctions" qui jouent le rôle de :
1. la fonction "strcpy" (copie de chaînes de caractères) 2. la fonction "strstr" (une chaîne est-elle une sous-chaîne d'une autre chaîne ?)
3. la fonction "strlwr" (conversion toute la chaîne en minuscules).