home *** CD-ROM | disk | FTP | other *** search
-
- =========================================================================
- APPRENTISSAGE du C version ALB_10
-
- CHAPITRE 10: DES CHAINES DE CARACTERES.
- =========================================================================
-
-
- 1. QU'EST CE QU'UNE CHAINE DE CARACTERES ?
- ==========================================
-
- "Petrus", avec des guillemets anglais, est une chaîne.
-
- En C, une chaîne de caractères est un tableau de caractères
- dont le dernier élément est le caractère nul \0.
-
- +--------+--------+--------+--------+--------+--------+--------+
- | P | e | t | r | u | s | \0 |
- +--------+--------+--------+--------+--------+--------+--------+
-
- Ce tableau compte 7 éléments, un de plus que le mot.
-
- 1° Il ne faut pas confondre le caractère ASCII nul \0 avec le zéro décimal
- dont la valeur ASCII est 48.
-
- 2° Une chaîne de caractères est un tableau, donc un objet particulier,
- différent dans sa nature des caractères qui la composent.
-
- La chaîne "P" est un objet différent du caractère 'P'.
-
- int char car0= 'P',
- chaine[]= "P"; // chaine[0]= 'P' et chaine[1]= \0
-
- Nous avons déclarés et initialisé une variable car de type caractère
- et un tableau de 1 caractère du nom de chaine.
-
- 3° Nous savons déjà que tableaux et variables ordinaires n'ont pas
- les mêmes propriétés.
-
- Il sera possible de modifier car0: car0= 'Z';
- car0 est une variable, une L-value.
-
- Mais non pas le tableau: chaine= "Z"; // refus
-
- chaine est une adresse invariable, une constante et on ne peut ni la
- modifier ni à plus forte raison lui affecter une chaîne d'octets. Ce
- n'est pas une L-value.
-
- Pour modifier le contenu de la chaîne il faudra s'y prendre autrement.
- Chapitre 10: Des chaînes de caractères. page 1
-
-
-
- 2. DECLARATION ET INITIALISATION.
- =================================
-
- Les chaînes sont des tableaux. On pourra les initialiser comme les
- tableaux, avec la notation des tableaux ou avec des pointeurs, mais il y
- a quelques particularités.
-
- Nous écrirons par analogie avec int tab[3]= { 0, 1, 2};
-
- char chaine[7]= { 'P', 'e', 't', 'r', 'u', 's', '\0'};
-
- mais ce n'est pas très pratique et on écrira plutôt:
-
- char chaine0[]= "Petrus"; le \0 est ajouté automatiquement,
- ou bien char chaine0[8]= "Petrus"; en laissant 1 élément inutilisé.
-
- On pourra de la même façon utiliser un pointeur:
-
- char *chaine1;
- chaine1= "Albulus"; ou plus directement,
-
- char *chaine1= "Albulus";
-
- ************
- 2.1. Déclaration et initialisation simultanées CH10_01.C
- ---------------------------------------------- ************
-
- 1° Nous déclarons deux chaînes, l'une avec la notation tableau de huit
- caractères pour voir ce qui va se passer avec les espaces que le nom
- n'utilise pas, l'autre avec un pointeur.
- L'initialisation est faite en même temps que la déclaration.
-
- 2° Nous affichons les chaînes en utilisant le symbole s dans printf().
- Puis nous affichons caractère par caractère le contenu entier des tableaux.
-
- Pour le premier, nous gardons l'affichage avec des lettres. Il contient le
- nom Petrus complété par des caractères nuls. Ceux ci sont représentés de
- manières différentes selon les polices utilisées par votre C.
-
- Pour le second nous utilisons l'équivalence entre les lettres et les
- caractères ASCII pour sortir des nombres. On vérifie la présence du
- caractère nul en bout de chaîne.
-
- 3° A titre d'exercice, nous avons utilisé l'accès aux éléments par la
- notation tableau puis par pointeur:
- chaine0[i], *chaine1++
-
- Remarque 1: le compilateur aurait rejeté *chaine0++. Pourquoi?
- Parce que chaine0 est une adresse constante qui ne peut donc être modifiée
- par incrémentation ou de toute autre manière. Ce n'est pas une L-value.
- chaine1 par contre est une référence de pointeur, donc une variable
- adresse, qui peut être modifiée. C'est une L-value.
- Chapitre 10: Des chaînes de caractères. page 2
-
-
- Remarque 2: à la sortie de la boucle, la valeur de chaine1 est égale à sa
- valeur de départ + i * sizeof( char), il y a eu 8 incrémentations.
- Si nous voulions utiliser à nouveau ce pointeur il faudrait ramener sa
- référence à sa valeur initiale:
-
- valeur initiale de chaine1= valeur actuelle - i* sizeof( char)
-
- Ce n'est pas très pratique. Il semble préférable dans ce cas de ne pas
- toucher à la valeur initiale de chaine1 en écrivant:
-
- *( chaine1+ i) au lieu de *chaine1++
-
-
- 2.2. Saisie normale d'une chaîne de caractères.
- -----------------------------------------------
-
- => On utilise normalement scanf() en prenant la précaution de nettoyer le
- tampon d'entrée et en se limitant en général à la saisie d'une seule
- chaîne. Mais il est nécessaire dans certains cas d'en saisir plusieurs.
-
- char prenom[16], nom[24];
- printf(" Entrez votre prénom et votre nom: ");
- scanf("%s %s", prenom, nom);
-
- Il ne faudra pas utiliser le symbole & puisque prenom et nom sont des
- chaînes de caractères et donc des adresses.
- Cette fonction ne permet pas de saisir les espaces, à moins de l'écrire
- sous la forme,
- scanf("%[^\n]", chaine);
-
- => On utilise aussi les fonctions puts() et gets() qui ne permettent de
- traiter qu'une chaîne à la fois.
- La fonction gets() équivalente à scanf() ignore les séparateurs autres que
- \n qui indique la fin de la lecture et permet donc la saisie de chaînes
- contenant des espaces.
-
- char nom[24];
- printf(" Entrez votre nom: ");
- gets( nom);
-
- printf("\n Votre nom est: ");
- puts( nom);
-
- La fonction gets() renvoie un pointeur NULL sur la chaîne en cas d'échec,
- ce qui permet de contrôler la validité de la saisie.
-
- ************
- Saisie d'une chaîne de caractères : CH10_02.C
- ************
- Exécutez ce programme. Vous constaterez qu'il ne fonctionne pas
- correctement. Vous aurez l'impression que la fonction gets() a été sautée.
- En réalité, la fonction scanf() a laissé des caractères nuls dans le
- tampon. La fonction gets() les a saisi et puts() les a affichés!
-
- Effacez la ligne qui débute avec scanf() et enlevez les symboles /* et */
- qui encadrent les trois lignes du code de saisie contrôlée.
- Le tampon sera vidé après chaque appel à scanf().
- Le programme fonctionnera normalement.
- Chapitre 10: Des chaînes de caractères. page 3
-
-
-
- 3. MODIFICATION DU CONTENU D'UNE CHAINE.
- ========================================
-
- Nous avons vu qu'il n'est pas possible d'affecter directement à une chaîne
- déjà déclarée une autre chaîne.
-
- char chaine[7];
- chaine= "Petrus"; // est refusé.
-
- Mais pourquoi ne pas tenter une affectation indirecte, avec bien sûr, des
- pointeurs?
-
- Nous allons écrire une fonction CopieChaine() qui permettra de copier,
- lettre par lettre, une chaine source désignée par un pointeur à la place
- d'une chaine cible désignée elle aussi par un pointeur.
-
- ************
- Copie d'une chaîne dans une autre : CH10_03.C
- ************
-
- 1° Définition de la fonction:
- les arguments seront 2 pointeurs, le premier sur la chaîne "cible",
- le second sur la chaîne "source" avec le qualificatif "const" pour qu'elle
- ne puisse pas être modifiée par erreur.
- On affecte à un pointeur *p l'adresse "cible" qui ainsi ne sera pas
- elle même modifiée. C'est la référence de p qui sera incrémentée et non
- l'adresse "cible".
- Tant que le caractère de "source" sera différent du \0 de fin de chaîne,
- ce caractère sera remplacé par celui qui lui correspond de "cible".
- L'incrémentation parallèle des références des pointeurs assure la
- progression à l'intérieur de la chaîne.
- Pour finir il faudra copier ce caractère de fin de chaîne dans "source".
-
- 2° Nous avons fixé les dimensions des tableaux à 10 et ajouté 2 espaces
- devant le nom Albulus.
- La première ligne imprimée montre que les caractères espaces sont pris
- en compte. La seconde que CopieChaine() fonctionne correctement.
- L'impression du contenu total des 2 chaînes montre que chaine0 a été
- copiée dans chaine1, y compris son caractère \0. Mais la suite n'a pas été
- modifiée.
-
- En réalité, vous n'aurez pas à utiliser la fonction CopieChaine().
- Vous trouverez dans la bibliothèque string.h qui regroupe toutes les
- fonctions utiles à la manipulation des chaînes, une fonction strcpy() dont
- le nom est un condensé de "string copy", qui fait la même chose beaucoup
- mieux. Nous y reviendrons plus loin dans ce chapitre.
- Chapitre 10: Des chaînes de caractères. page 4
-
-
-
- 4. UTILISATION DE FONCTIONS DE LA BIBLIOTHEQUE STRING.H
- =======================================================
-
- Il faut, pour pouvoir utiliser les fonctions de cette bibliothèque,
- inclure string.h dans l'en-tête.
-
- ************
- 4.1. Mesure de la longueur d'une chaîne, strlen(). CH10_04.C
- -------------------------------------------------- ************
-
- char chaine0[20]= "Petrus Albulus";
- printf(" longueur de la chaîne: %d", strlen( chaine0) );
-
- L'argument est une adresse, la fonction renvoie le nombre de caractères
- de la chaîne jusqu'au code nul \0 NON compris.
-
- Dans CH10_04.C nous avons utilisé la fonction strlen() et l'opérateur
- sizeof() pour que vous puissiez en comparer les effets.
-
-
- 4.2. Copie d'une chaîne dans une autre, strcpy() et strncpy().
- --------------------------------------------------------------
-
- 4.2.1. Le prototype de la fonction strcpy() est:
-
- *char strcpy( char *cible, const char *source);
-
- La fonction renvoie l'adresse cible, qui peut parfois être utile.
-
- char chaine[18]= "Petrus Albulus";
- strcpy( chaine, "abcdef ghijkl"); chaîne de 14 caractères + \0.
-
- Dans CH10_04.C nous avons contrôlé, à titre d'exemple, la valeur renvoyée
- par la fonction. Est elle bien égale à l'adresse de la cible?
- Notez dans printf( ...%s...%d.., chaine, chaine, ptr), %s pour afficher
- une chaîne qui commence à l'adresse chaine et %d qui affiche l'adresse
- chaine.
-
- 4.2.2. La fonction strncpy() est un peu différente.
-
- Elle permet, en ajoutant un troisième argument qui est un nombre entier
- non signé, de limiter à sa valeur la longueur maximum de la recopie.
- L'espace, quand il existe, entre la fin de la recopie et cette longueur
- maximum est complété par des codes nuls \0.
- ATTENTION: si la chaîne source est plus longue que la longueur maximum, la
- fonction ne copie que ce nombre de caractères, mais sans \0 final.
-
- *char strcpy( char *cible, const char *source, unsigned int longmax);
-
- char chaine[18]= "Petrus Albulus";
- strncpy( chaine, "uvw xyz", 10); ************
- CH10_05.C
- ************
- La déclaration et l'initialisation de chaine a eu pour effet de placer
- 14 caractères et le \0 dans la chaîne. Les 3 éléments restants sont
- ramenés à \0 automatiquement.
- La fonction strncpy() a eu le même effet dans la limite des 10 premiers
- éléments. Les autres, au delà du dixième chaine[9], n'ont pas été touchés.
- Chapitre 10: Des chaînes de caractères. page 5
-
-
- ************
- Utilisation sans précautions de strncpy: CH10_06.C
- ************
- Nous avons allongé la chaîne source au delà de 10 caractères.
- Vous pouvez constater que les 10 premiers caractères de la source sont
- pris en compte et affectés à la cible, SANS LE CARACTERE nul \0 de
- fin de chaîne! Les caractères au delà du dixième ne changent pas. C'est
- à dire que lorsque la nouvelle chaîne sera lue, la lecture ne s'arrêtera
- qu'au premier caractère \0, qui est chaine[14] et nous aurons:
-
- "rst uvw xyulus" cela fait du beau latin de cuisine mais ce n'était
- pas ce que nous attendions!
-
- Donc, en règle générale, on emploie cette fonction avec comme dernier
- argument la taille de la chaîne cible - 1, mais on doit ajouter dans
- certains cas le \0 final.
-
- ************
- Comment copier une chaîne sans "déborder", 1ère étape: CH10_07.C
- ************
-
- Avec dim= 18, on peut écrire sans débordement 17 caractères. Il semble que
- tout se passe bien parce que la chaîne cible possède déjà un \0 de fin de
- chaîne. Vérifiez le en plaçant 4 caractères quelconques devant le P de la
- chaîne cible: "wxyzPetrus Albulus". Relancez le programme et vous verrez
- que nous n'avons plus de caractère \0 pour marquer la fin de la chaîne.
- On ne pourra donc plus la lire correctement.
-
- ************
- Saisie "contrôlée" d'une chaîne: CH10_08.C
- ************
- Il faut donc, dans ce cas, ajouter le \0 de fin de chaîne.
- La fonction LitChaine() doit permettre une saisie de chaîne avec un
- minimum de sécurité.
- Nous avons, provisoirement, utilisé un pointeur t1 pour pouvoir affecter
- la chaîne saisie avec scanf(). Nous verrons plus loin qu'il vaut mieux
- faire autrement.
- On s'assure de la présence du \0 final à l'adresse ( t0+ dimension -1),...
- en l'ajoutant systématiquement!
- On vide comme d'habitude le tampon. On applique la règle précédente avec
- strncpy(). Le traitement est relativement long, mais le temps ne joue pas
- un rôle majeur dans la plupart des processus de saisie.
- Chapitre 10: Des chaînes de caractères. page 6
-
-
-
-
- 4.3. copie d'une chaîne à la suite d'une autre: strcat(), strncat().
- ---------------------------------------------------------------------
-
- => char *strcat( char *cible, const char *source);
- La longueur de la chaîne cible après l'opération sera égale à:
-
- strlen( cible avant l'appel de la fonction)+ strlen( source).
-
- La fonction renvoie un pointeur sur la chaîne cible.
-
- => char *strncat( char *cible, const char *source, unsigned int longmax );
-
- La fonction recopie au plus "longmax" caractères et ajoute un \0 en fin de
- chaîne. La longueur de la chaîne après l'exécution de la fonction est de
- strlen( cible)+ longmax.
-
- ************
- Comment utiliser strncat() sans "déborder": CH10_09.C
- ************
-
- Nous avons choisi pour cet exemple une longueur de dim= 13 pour chaine0.
- Le nouvel enregistrement est limité à dim, moins la longueur déjà utilisée
- dans la chaîne cible, moins 1, pour le \0 de fin de chaîne.
- Le mot qui a été ajouté est tronqué mais cela est bien préférable à un
- débordement dont les conséquences sont imprévisibles.
-
- Notez, que dans ces conditions, la fin de la chaîne n'est pourvue du
- symbole \0 que s'il y était déjà. Vous pourrez le constater en mettant
- dim= 13 caractères dans la chaîne cible.
- Si on raisonne en termes de sécurité il faut l'ajouter systématiquement.
- Nous avons mis en commentaires dans le code une ligne qui fait cet ajout.
-
-
- 4.4. Les autres fonctions.
- --------------------------
-
- Nous ne ferons ici que les mentionner. La plupart des compilateurs C
- sont dotés d'une aide qui donne une liste des fonctions, leur définition
- et souvent même un exemple d'utilisation.
-
- => strlwr() et strupr() convertissent majuscules en minuscules et
- inversement.
- => strrev() inverse les caractères d'une chaîne sauf le \0 final.
- memcpy() copie de n octets.
- => strchr(), strrchr(), strstr(), strpbrk() recherchent des éléments
- de chaînes ou des caractères.
- => strcmp(), strncmp(), stricmp(), strnicmp() comparent des chaînes.
- Chapitre 10: Des chaînes de caractères. page 7
-
-
-
-
- 5. TABLEAUX DE POINTEURS ET CHAINES DE CARACTERES.
- ==================================================
-
- ************
- Chaînes de longueurs différentes (ou tableaux incomplets): CH10_10.C
- ************
- On associe dans ce programme le nom d'un jour de la semaine avec le nombre
- qui lui correspond.
- Le tableau de pointeurs est initialisé directement sans mention de taille.
- C'est intéressant parce que cela permet d'utiliser des chaînes de longueurs
- différentes. On manipulera chacune avec le pointeur qui lui correspond.
- On pourra toujours par la suite lui affecter d'autres chaînes. Mais il
- faudra alors respecter la dimension initiale de chacune sous peine de
- débordements faciles à imaginer.
-
- Nous avons déjà vu la manière de déclarer un tableau de pointeurs. Nous
- avons déclaré un pointeur du nom de jours. Il pointe sur les lignes d'un
- tableau que nous repérons avec un indice entre crochets.
- Examinez le mécanisme d'utilisation. Dans la dernière ligne du code, nous
- aurions pu remplacer la notation "tableau", jours[n- 1], par la notation
- "pointeur", *( jours+ n- 1).
-
- Nous pourrions accéder au troisième caractère de la cinquième chaîne, si
- ce caractère existe, en écrivant:
-
- *( jours[ 5-1] + (3- 1) ) ou bien si vous préférez
- *( *(jours+ 5-1) + (3- 1) ).
-
- ************
- Saisie et trie de chaînes de même longueur: CH10_11.C
- ************
- Ceci est l'adaptation d'un programme classique. Nous utilisons un tableau
- de pointeurs pour saisir et trier des chaînes ayant une dimension maximum
- définie initialement.
-
- 1° Nous avons besoin en chemin d'une fonction de comparaison strcmp() qui
- compare deux chaînes et renvoie zéro quand elles sont égales.
- La fonction de trie agit en fonction de la longueur de la chaîne et elle
- peut être facilement adaptée pour trier selon d'autres critères.
- Examinez surtout la manière d'initialiser "dynamiquement" les lignes d'un
- tableau de pointeurs déclaré au début du programme, cela mérite un instant
- de réflexion.
-
- 2° La fonction LitChaine() commence par créer "dynamiquement" une chaîne
- de longueur 256* 2 en utilisant la fonction malloc(). La fonction free()
- permettra de libérer l'espace mémoire utilisé par cette chaîne à la
- clôture de la fonction.
- Chapitre 10: Des chaînes de caractères. page 8
-
-
-
- Pourquoi créer cette chaîne dynamiquement?
-
- => Un simple pointeur char aurait pu suffire pour assurer le "portage" de
- la chaîne saisie. Mais la déclaration du pointeur n'assure pas, par elle
- même, la réservation d'une zone de mémoire suffisante pour stocker cette
- chaîne.
- => Si la chaîne cible est elle même crée dynamiquement, la fonction de
- copie pourra lui affecter sans restrictions la chaîne saisie.
- Ces restrictions tiennent au "modèle de mémoire" et au type de pointeur
- utilisé. Nous en parlerons dans le chapitre consacré à la gestion de la
- mémoire.
-
- Pourquoi une chaîne de longueur 256* 2 octets?
-
- Ce n'est qu'un bricolage, fait uniquement pour éviter un débordement dans
- une zone incontrôlée de la mémoire. Il est donc préférable de créer un
- tableau, et de lui donner une dimension a priori suffisante.
- La fonction scanf() est utilisée avec une limitation à 255 du nombre de
- caractères qui peuvent être saisis.
-
-
- ============================
-
-
- Nous en avons terminé avec la partie traditionnelle de la programmation.
- La notion de structure, que nous allons étudier, marque déjà une évolution
- majeure du langage. C'est un pas de plus vers la programmation "objet".
-
- Parmi les grands développeurs américains certains semblent regretter
- cette évolution. Pour ces programmeurs d'élite, elle limite la
- liberté d'écriture que donnait l'absence de règles contraîgnantes.
-
- On peut admirer et préférer pour des raisons esthétiques ou autres,
- ces maîtres japonais du tir à l'arc, dont le geste presque immatériel
- atteint une espèce de perfection. Mais nos règles collectivistes de
- productivité ne font pas partie de l'univers de ces maîtres du Zen.
- Fin du chapitre 10: Des chaînes de caractères. page 9
-