Chapitre 4 : 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 ou de : char nom [20] ; 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 : urgent -----> | '9' | '1' | '1' |'\0'| |_____|_____|_____|____| 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). Chapitre 4 : Fichiers de type texte Page 83 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] urgent[1] urgent[2] urgent[3] = = = = ; '9' ; '1' ; '1' ; '\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. Chapitre 4 : Fichiers de type texte Page 84 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 Chapitre 4 : Fichiers de type texte Page 85 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); Chapitre 4 : Fichiers de type texte Page 86 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 < 0 0 > 0 signification chaine1 < chaine2 chaine1 = chaine2 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") (code ASCII) est supérieur à zéro car 'p' > 'G' 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. Chapitre 4 : Fichiers de type texte Page 87 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 un pointeur NULL signification unCar ne fait pas partie de la chaîne un pointeur non NULL qui pointe vers le 1er caractère trouvé à partir du début de la chaîne unCar fait partie 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'); Chapitre 4 : Fichiers de type texte Page 88 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 long double atoi (char * chaine) atol (char * chaine) atof (char * chaine) : chaîne en entier : chaîne en "long" entier : 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 Chapitre 4 : Fichiers de type texte Page 89 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 CHARTRAND ANDRE etc ... 1.67 1.72 58.6 75.4 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 ╔═══════════════╗ Chapitre 4 : Fichiers de type texte Page 90 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 ; P -----> | 'B' | 'o' | 'n' | 's' | 'o' | 'i' | 'r' |'\0'| |_____|_____|_____|_____|_____|_____|_____|____| 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'. Chapitre 4 : Fichiers de type texte Page 91 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 : P -----> | '9' | '1' | '1' |'\0'| |_____|_____|_____|____| *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 Chapitre 4 : Fichiers de type texte Page 92 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 Journée 2 etc .... : : dimanche lundi Solution : int rang ; char * jours[7] = { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" }; for ( rang = 0 ; rang < 7 ; rang++) printf("Journée %d Chapitre 4 : Fichiers de type texte : %s\n", rang+1, jours[rang]); Page 93 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 #include #include #include <stdio.h> <stdlib.h> <alloc.h> <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++ ; Chapitre 4 : Fichiers de type texte Page 94 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 #include #include #include #include <stdio.h> <stdlib.h> <alloc.h> <string.h> <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); } Chapitre 4 : Fichiers de type texte Page 95 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 Exemple 4: Écrire votre "propre fonction" qui joue le rôle de la fonction "strcat" (concaténation de chaînes de caractères, page 87). Chapitre 4 : Fichiers de type texte Page 96 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' source++ ; P++ ; (condition sous while) /* vers le caractère suivant */ /* 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 ; } Chapitre 4 : Fichiers de type texte Page 97 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 minuscules). Chapitre 4 : Fichiers de type texte "strlwr" (conversion toute la chaîne en Page 98