Université Moulay Ismail Faculté des Sciences et Techniques Département d’Informatique Errachidia Filière Cycle d'Ingénieur « Génie Informatique » Les Pointeurs en C Prof. Y FARHAOUI 1 Année universitaire : 2021/2022 Les pointeurs ● La définition d’un pointeur ● L’utilisation des pointeurs ● La déclaration et l’initialisation des pointeurs ● L’utilisation des pointeurs avec des variables simples et des tableaux ● Opérateurs unaires pour manipuler les pointeurs ● Passage de paramètres de fonction par référence ● Le passage des tableaux à une fonction avec les pointeurs ● Utilisation des pointeurs avec les chaines de caractères 2 Les pointeurs, c'est quoi? Un pointeur est une variable particulière, dont la valeur est l'adresse d'une autre variable. • Pointeur p: valeur 5A0F3 (adresse hexadécimale) • Adresse 5A0F3: valeur 17 (correspondant à la valeur d'un entier i) i=17 p=5A0F3 5A0F3 17 p i On peut accéder indirectement à la variable et donc la modifier. Un pointeur est une adresse mémoire. On dit que le pointeur p pointe vers i, puisque p pointe vers l’emplacement mémoire où est enregistrée i. 3 Les pointeurs: pourquoi ? • Les pointeurs sont nécessaires pour: – effectuer les appels par référence (i.e. écrire des fonctions qui modifient certains de leurs paramètres) – manipuler des structures de données dynamiques (liste, pile, arbre,…) – allouer dynamiquement de la place mémoire 4 Les pointeurs: pourquoi ? L’étape suivante consiste à stocker l’adresse de vari dans la variable p_vari en langage C, p_vari pointe sur vari, ou p_vari est un pointeur vers vari p_vari vari En résumé Un pointeur est une variable qui contient l’adresse d’une autre variable. 5 Déclaration de pointeurs Le symbole * est utilisé entre le type et le nom du pointeur int i; • Déclaration d‟un entier: Déclaration d‟un pointeur vers un entier: int *p; Exemples de déclarations de pointeurs int *pi; float *pf; /* pi est un pointeur vers un int *pi désigne le contenu de l'adresse */ /* pf est un pointeur vers un float */ char c, d, *pc; /* c et d sont des char*/ /* pc est un pointeur vers un char */ double *pd, e, f; /* pd est un pointeur vers un double*/ /* e et f sont des doubles */ double **tab; /* tab est un pointeur pointant sur un pointeur qui pointe sur un flottant double */ 6 Exemple 1 Déclaration de pointeurs #include <stdio.h> int main(void) { int x = 2; /* déclaration d’une variable x */ int *p; /* déclaration d’un pointeur p */ p = &x; /* p pointe sur x */ /* la valeur de p est l’adresse de x */ scanf("%d", p); /* lecture de la valeur de x au clavier */ printf("%d", x); /* affichage de la nouvelle valeur de x */ return 0; } RQ: Ne pas confondre l’usage de l’étoile lors de la déclaration d’une variable de type pointeur avec l’usage de l’étoile qui permet d’accéder à l’objet pointé par le pointeur. *p = 3; /* l’objet pointé par p prend pour valeur 3 */ 7 Exemple 2 Déclaration de pointeurs #include <stdio.h> int main(void) { int x=2; int *p = &x; /* x et *p deviennent synonymes */ *p = 3; printf("La nouvelle valeur de x est %d \n", x ); return 0; } /* doit afficher la valeur 3 */ 8 Déclaration de pointeurs Exemple 3 #include <stdio.h> int main(void) { /* affiche 2 */ int x=2; int *p = &x; /* x et *p deviennent synonymes */ printf("La valeur de x est %d\n", *p); x=5; printf("La nouvelle valeur de x est %d\n", *p ); /* doit afficher la valeur 5 */ return 0; } /* doit afficher la valeur 5 */ 9 Opérateurs unaires pour manipuler les pointeurs, & (adresse de) et * (contenu) Exemple: int i = 8; printf("VOICI i: %d\n",i); printf("VOICI SON ADRESSE EN HEXADECIMAL: %p\n",&i); nom_de_Pointeur = &nom_de_variable void { main(void) char char p c 'a' 0x1132 c = 'a', d = 'z'; *p; 0x1132 p p = &c; *p = c; printf("%c\n", *p); p = &d; } printf("%c\n", *p); d 'z' 0x91A2 0x91A2 L‟opérateur * (“valeur pointée par”) a z 10 * et ++ *p++ signifie: int a[]={0,1,5}; int*p; *p++ trouver la valeur pointée ! p=a;//&a[0]==a *p++ passer à l’adresse suivante Int b=*p++;// p a (*p)++ signifie: (*p)++; //*p=2 (*p)++ trouver la valeur pointée Int c= (*p)++; //c=3 (*p)++ incrémenter cette valeur (sans changer le p pointeur) a *++p signifie: *++p incrémenter d’abord le pointeur Int d= *++p; / /d==5 *++p trouver la valeur pointée p ++*P a 100 104 108 0 1 5 100 104 108 0 3 5 100 104 108 0 3 5 11 #include <stdio.h> void main() { Exercice int *p, x, y; p = &x; /* p pointe sur x */ x = 10; /* x vaut 10 */ y = *p - 1; printf(" y= *p - 1 =? = %d\n" , y); y vaut ? *p += 1; printf(" *p += 1 =? *p = x= ? = %d %d\n" , *p, x); x vaut ? (*p)++; printf(" (*p)++ =? *p = x= ? = %d %d alors y=%d \n" , *p, x, y); incrémente aussi de 1 la variable pointée par p, donc x vaut ??. y vaut 9 *p=0; printf(" *p=0 x=? = %d\n" , x); comme p pointe sur x, maintenant x vaut ? *p++; *p=20; printf(" *p++ x=? = %d\n" , x); } comme p ne pointe plus sur x, x vaut 12 tjr ? Utiliser des pointeurs • On peut donc accéder aux éléments par pointeurs en faisant des calculs d‟adresses (addition ou soustraction) long v[6] ={1,2, 3,4,5,6 }; long *p; p = v; // v==&v[0] printf("%ld\n", *p); p++; printf("%ld\n", *p); p += 4; printf("%ld\n", *p); p += 4 p++ 1 2 6 p 1000 1 v 2 3 4 5 6 1016 1000 1008 1012 1020 1004 13 Exercice • • • • • • • int t[] = {2,5,7}; int *p = t; int a, b, c ; //*p= b = *p++; // b = b = *p; // b = a = (*p)++; // a= c = *++p // c = 14 Pointeurs En résumé, lorsque p pointe sur x, la valeur de p est l’adresse de x, toute modification de *p modifie x et toute modification de x modifie *p. La raison est que *p et x sont sur le même emplacement mémoire dans la mémoire RAM. Conseils À faire Veillez à bien comprendre ce que représente un pointeur et comment il travaille. Le langage C et les pointeurs vont de pair. À ne pas faire N’utilisez pas un pointeur qui n’a pas été initialisé, 15 Pointeurs et types de variables Les différents types de variables du langage C n’occupent pas tous la même mémoire (int prend quatre octets, une variable double en prend huit, etc) int vint = 12252; char vchar = 90; //asci de Z double vdouble = 1200.156004; La variable int occupe quatre octets, la variable char en occupe un, et la variable double huit. 16 Pointeurs et types de variables Voici la déclaration et l’initialisation des pointeurs vers ces trois variables : int *p_vint; char *p_vchar; double *p_vdouble; /* des instructions peuvent être insérées ici */ p_vint = &vint; p_vchar = &vchar; p_vdouble = &vdouble; 17 Passage des paramètres par valeur et par adresse #include <stdio.h> void ech(int x,int y) { int tampon; tampon = x; x = y; y = tampon; } a et b: variables locales à main(). La fonction ech ne peut donc pas modifier leur valeur. On le fait donc en passant par l'adresse de ces variables. void main() { int a = 5 , b = 8; ech(a,b); a=? printf(“ a=%d\n ”, a) ; b=? #include <stdio.h> void ech(int *x,int *y) { int tampon; tampon = *x; *x = *y; *y = tampon; } void main() { int a = 5 , b = 8 ; ech(&a,&b); printf(“ a=%d\n ”, a) ; a=? b=? printf(“ b=%d\n ”, b) ; } PASSAGE DES PARAMETRES printf(“ b=%d\n ”,b) ; } PASSAGE DES PARAMETRES 18 Passage de paramètre par valeur Lorsqu’on passe un paramètre à une fonction, la fonction ne peut pas modifier la variable. La variable est automatiquement recopiée et la fonction travaille sur une copie de la variable. La modification de la copie n’entraîne pas une modification de la variable originale. C’est le passage de paramètre par valeur. Exemple: #include <stdio.h> void NeModifiePas(int x) { x = x+1; /* le x local est modifié, pas le x du main */ } int main(void) { int x=1; NeModifiePas(x); printf("%d", x); /* affiche 1 : valeur de x inchangée */ return 0; } 19 Passage de paramètre par adresse L’idée du passage par adresse est que, pour modifier une variable par un appel de fonction, il faut passer en paramètre non pas la variable, mais un pointeur qui pointe sur la variable. Ainsi, le paramètre est l’adresse de x. Lorsqu’on modifie la mémoire à cette adresse, la donnée x est modifiée, car on travaille bien sur l’emplacement mémoire de x 20 Exemple (PASSAGE DE PARAMÈTRE PAR ADRESSE) #include <stdio.h> /* la fonction suivante prend en paramètre un pointeur */ void Modifie(int *p) { *p = *p+1; /* p pointe sur x, la copie de p aussi */ /* le x du main est modifié */ } int main(void) { int x=1; /* la varible x n’est pas un pointeur */ int *p; p = &x; /* pointeur qui pointe sur x */ Modifie(p); /* affiche 2 */ printf("%d", x); return 0; } 21 Exemple (PASSAGE DE PARAMÈTRE PAR ADRESSE) RQ: L’utilisation explicite d’un pointeur dans le main est superflue, on peut passer directement l’adresse de x à la fonction, sans avoir besoin de définir une variable de type pointeur dans le main. #include <stdio.h> void Modifie(int *p) /* paramètre de type pointeur */ { *p = *p+1; /* ce pointeur p a pour valeur &x (x du main) */ } int main() { int x=1; Modifie(&x); /* on passe directement l’adresse de x */ printf("%d", x); /* affiche 2 */ return 0; } Exemple La fonction scanf, qui lit des variables au clavier, doit modifier le contenu de ces variables. Pour cette raison, les variables sont passées à scanf par22 adresse, ce qui explique la nécessité des & devant les variables. Pointeurs et tableaux Les pointeurs sont très utiles pour travailler avec les variables, mais ils le sont encore plus quand on les utilise avec les tableaux. En fait, les index de tableaux ne sont rien d’autre que des pointeurs. Noms de tableau et pointeurs Un nom de tableau sans les crochets s’utilise la plupart du temps comme un pointeur vers le premier élément du tableau. Si vous avez déclaré le tableau T[] , T aura la valeur de l’adresse de T [0] et sera donc équivalent à l’expression &T[0]. T------------------------------------ &T [0] T+1 -------------------------------- &T [1] T+i -------------------------------- &T[i] T[i]---------------------------------- * (T+i) nous obtenons les relations suivantes : *(T) == T[0] *(T+1) == T[1] *(T+2) == T[2] etc. 23 *(T+i) == T[i] Pointeurs et tableaux char cTableau[]="abc"; /* cTableau pointe sur une zone de 4 octets /* cTableau[0] = 'a', ….cTableau[3]='\0' */ char* cPointeur="abc"; /* l'allocation mémoire est faite directement */ */ /* à la compilation Attention : ♦ cTableau est un pointeur constant; le nombre d'octets pointés est constant. Néanmoins, la chaîne (le contenu) n'est pas constante, puisqu'il s'agit d'un tableau. ♦ cPointeur est modifiable car il s'agit d'une adresse. Cependant, on ne peut pas changer directement son contenu. char cLigne[]="Ali"; cTableau=cLigne; /* Erreur, cTableau est constant, */ /* on ne peut pas changer son adresse */ strcpy(Ctableau, cLigne); /* Correct car changement de contenu */ cPointeur=cLigne; /* Correct car changement d'adresse */ strcpy(cPointeur,"efg"); ok 24 Stockage des éléments d’un tableau Les éléments d’un tableau sont stockés dans des emplacements mémoire séquentiels, le premier élément ayant l’adresse la plus basse. 25 Stockage des éléments d’un tableau Conclusion Vous pouvez constater, à partir de ces exemples, qu’un pointeur devra être incrémenté de quatre pour accéder à des éléments successifs d’un tableau de type int, et de huit dans le cas d’un tableau de type double. Généralement Pour accéder à des éléments successifs d’un tableau contenant un type de donnée particulier, le pointeur devra être incrémenté de la valeur sizeof (typedonnée). L’opérateur sizeof renvoie la taille en octets du type de donnée reçu en argument. RQ: Le C ne manipule simplement que des tableaux 1D (des vecteurs). Il s’ensuit qu’un tableau 2D (matrice) sera considéré comme un vecteur dont chaque élément est un vecteur. 26 Ce principe peut être étendu à des tableaux à n entrées Eemple : Création d’un tableau de taille quelconque #include <stdio.h> #include <stdlib.h> // pour la fonction printf() // pour la fonction malloc() void main(){ int i , dim; long* tableau; // compteur et taille du tableau // pointeur pour stocker l‟adresse du tableau scanf (“%d”, &dim); // saisie par l'utilisateur de la taille du tableau tableau = (long*) malloc(dim * sizeof(long)); //allocation (dynamique) du tableau // remplissage du tableau, on utilise les indices 0 à (dim -1) for (i = 0; i < dim; i++) tableau[i] = i; // affichage du contenu du tableau for (i = 0; i < dim; i++) printf("%ld\n", tableau[i]); // destruction du tableau : libération de la mémoire réservée free(tableau); } 27 Un nom de tableau est un pointeur constant Cas des tableaux à plusieurs indices Comme pour les tableaux à un indice, l’identificateur d’un tableau, employé seul, représente toujours son adresse de début. Toutefois, si l’on s’intéresse à son type exact, il ne s’agit plus d’un pointeur sur des éléments du tableau. Lorsque le compilateur rencontre une déclaration telle que : int t[4] [5] ; t désigne un tableau de 4 éléments, chacun de ces éléments étant luimême un tableau de 5 entiers. Autrement dit, si t représente bien l’adresse de début de notre tableau t, il n’est plus de type int * mais d’un type « pointeur sur des blocs de 4 entiers » t+1correspond à l’adresse de t, augmentée de 4 entiers (et non plus d’un seul !). 28 Un nom de tableau est un pointeur constant Cas des tableaux à plusieurs indices Un tableau à plusieurs dimensions est un pointeur de pointeur. les notations t et &t[0][0] correspondent toujours à la même adresse t[0] représente l’adresse de début du premier bloc (de 5 entiers) de t, « Pointeurs » vers des vecteurs : t[1], celle du second bloc... 0 les notations suivantes sont totalement équivalentes : t[0] équiva &t[0][0] t[1] équiva &t[1][0] 1 2 tableau 1D dont chaque élément « pointe » vers un vecteur 3 0 0 0 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 5 5 t[2][4], t[2] est l’adresse du début du vecteur d’indice 2, c’est un pointeur vers t[2][0] Espace de stockage du tableau 29 Pointeurs et tableaux à plusieurs dimensions Soit la déclaration d‟un tableau A et d‟un pointeur P :int A[M][N], *p; l‟instruction P = A[0] crée une liaison entre le pointeur p et la matrice A en mettent dans p l‟adresse du premier élément de la première ligne de la matrice A ( P = &A[0][0]). A partir du moment ou P = A[0], la manipulation de la matrice A peut se faire par le biais du pointeur P. En effet : p p+1 p+N p+N+1 p+i*N+j désigne désigne désigne désigne désigne Ou &A[0][0] &A[0][1] &A[1][0] &A[1][1] &A[i][j] et et et et et *p *(p+1) *(p+N) *(p+N+1) *(p+i*N+j) désigne A[0][0] désigne A[0][1] désigne A[1][0] désigne A[1][1] désigne A[ i ][ j ] i є [0 , M-1] et j є [0 , N-1] 30 Pointeurs et tableaux à plusieurs dimensions main() { //création d’une matrice de 10 lignes et 20 colonnes int k=10, n=20; int **tab; tab = (int**)malloc(k * sizeof(int*)); for (i = 0; i < k; i++) tab[i] = (int*)malloc(n * sizeof(int)); .... for (i = 0; i < k; i++) free(tab[i]); free(tab); } 31 Les opérateurs réalisables sur des pointeurs Pointeur arithmétique Nous venons de voir que le pointeur du premier élément d’un tableau doit être incrémenté d’un nombre d’octets égal à la taille des données du tableau pour pointer sur l’élément suivant. Pour pointer sur un élément quelconque en utilisant une notation de type pointeur, on utilise le pointeur arithmétique. Incrémenter les pointeurs Incrémenter un pointeur consiste à en augmenter la valeur. Si vous incrémentez un pointeur de 1, le pointeur arithmétique va augmenter sa valeur pour qu’il accède à l’élément de tableau suivant. Exemple Si ptr_int pointe sur un élément de tableau de type int, l’instruction suivante : ptr_int++; incrémente la valeur de ce pointeur de 4 pour qu’il pointe sur l’élément int suivant. De la même façon, si vous augmentez la valeur du pointeur de n, C va incrémenter ce pointeur pour qu’il pointe sur le n-ième élément suivant : ptr_int += 2; Cette instruction va augmenter de 8 la valeur du pointeur, pour qu’il pointe322 éléments plus loin. Les opérateurs réalisables sur des pointeurs Décrémenter les pointeurs La décrémentation des pointeurs suit le même principe que l’incrémentation. Si vous utilisez les opérateurs (– –) ou (– =) pour décrémenter un pointeur, le pointeur arithmétique va diminuer sa valeur automatiquement en fonction de la taille des données pointées. Exemple int v[6]= {1,2,3,4,5,6}; Int *p=v, *p1,*p2; ptr_int - - ; ptr_int - = 2; p1++ pointe sur v[3] p2-- pointe sur v[4] p2-p1 donne 3 p += 4 p++ p 1000 v 1 2 3 4 5 6 1016 1000 1008 1012 1020 1004 p1 p2 33 Les opérateurs réalisables sur des pointeurs La comparaison de pointeurs Les opérateurs de comparaison = =, !=, >, <, >=et <= peuvent aussi être utilisés. Les premiers éléments d’un tableau ont toujours une adresse plus basse que les derniers. Ainsi, si ptr1et ptr2 sont deux pointeurs d’un même tableau, la comparaison : ptr1 < ptr2 : est vraie si ptr1 pointe sur un élément d’index plus petit que ptr2. Par exemple, voici, en parallèle, deux suites d’instructions réalisant la même action : mise à 1 des 10 éléments du tableau t : 34 Les opérateurs réalisables sur des pointeurs Autre utilisation des pointeurs La dernière opération que l’on peut effectuer avec des pointeurs est la différence entre deux pointeurs. Si vous avez deux pointeurs sur un même tableau, le résultat de leur soustraction correspond au nombre d’éléments les séparant. Exemple : ptr1 – ptr2 : donne le nombre d’éléments qui séparent les deux éléments pointés par ptr1et ptr2. Beaucoup d’opérations arithmétiques sont réservées aux variables simples, car elles n’auraient aucun sens avec les pointeurs. Par exemple, si ptr est un pointeur, l’instruction : ptr *= 2; générera un message d’erreur. 35 Le Tableau vous donne la liste des six opérations possibles avec les pointeurs. Description Opérateur Affectation Vous pouvez attribuer une valeur à un pointeur. Cette valeur doit correspondre à une adresse obtenue avec l‟opérateur d‟adresse (&,), ou à partir d‟un pointeur constant. Indirection L‟opérateur indirect (*) donne la valeur stockée à l‟emplacement pointé. Adresse de Vous pouvez utiliser l‟opérateur d‟adresse (&)pour trouver l‟adresse d‟un pointeur et obtenir un pointeur vers un pointeur. Incrément On peut ajouter un nombre entier à la valeur d‟un pointeur pour pointer sur un emplacement mémoire différent. Décrément On peut soustraire un entier à la valeur d‟un pointeur pour pointer sur un emplacement mémoire différent. Différence Vous pouvez soustraire un entier de la valeur d‟un pointeur pour pointer sur un emplacement mémoire différent. Comparaison Ces opérateurs ne sont valides que pour deux pointeurs d‟un même tableau. 36 Précautions d’emploi Quand vous utilisez des pointeurs dans un programme, une grosse erreur est à éviter : utiliser un pointeur non initialisé à gauche d’une instruction d’affectation. Par exemple, dans la déclaration suivante : int *ptr; le pointeur n’est pas initialisé. Cela signifie qu’il ne pointe pas sur un emplacement connu. Si vous écrivez : *ptr = 12; la valeur 12 va être stockée à l’adresse (inconnue) pointée par ptr. Cette adresse peut se situer n’importe où en mémoire, au milieu du code du système d’exploitation par exemple. La valeur 12 risque d’écraser une donnée importante, et le résultat peut aller de simples erreurs dans un programme à l’arrêt complet du système. À ne pas faire Effectuer des opérations mathématiques comme des divisions, des multiplications ou des modulos avec des pointeurs. Les seules opérations possibles sont l’incrémentation ou le calcul de la différence entre deux pointeurs d’un même 37 Fonctions permettant la manipulation des chaînes string.h ou stdlib.h void *strcat(char *chaine1,char *chaine2) //Concatène les 2 chaînes, résultat dans chaine1. void *strcpy(char *chaine1,char *chaine2) //Copie la chaine2 dans chaine1. + '\0‘ void *strncpy(char *chaine1,char *chaine2, NCmax) // idem strcpy mais limite la copie au nombre de caractères NCmax. int strcmp(char *chaine1,char *chaine2) //Compare les chaînes de caractères chaine1 et chaine2, renvoie un nombre: - positif si la chaîne1 est supérieure à la chaine2 (au sens de l'ordre alphabétique) - négatif si la chaîne1 est inférieure à la chaîne2 - nul si les chaînes sont identiques. int strlen(char *chaine) // renvoie la longueur de la chaine ('\0' non comptabilisé). int atoi(const char *s) // convertit l'argument s en un int, 38 Les fonctions de concaténation de chaînes La fonction strcat L’appel de strcat se présente ainsi (nous placerons souvent en regard de la présentation de l’appel d’une fonction le nom du fichier qui en contient le prototype) : strcat ( but, source ) (string.h) Cette fonction recopie la seconde chaîne (source) à la suite de la première (but), après en avoir effacé le caractère de fin. 39 Les fonctions de concaténation de chaînes Exemple : #include <stdio.h> #include <string.h> main() { char ch1[50] = "bonjour" ; char * ch2 = " monsieur" ; printf ("avant : %s\n", ch1) ; Notez la différence entre les deux strcat (ch1, ch2) ; déclarations (avec initialisation) de printf ("après : %s", ch1) ; chacune des deux chaînes Ch1 et ch2. } La première permet de réserver un emplacement plus grand que la constante chaîne qu’on y place initialement. RESULTAT avant : bonjour après : bonjour monsieur 40 Les fonctions de concaténation de chaînes La fonction strncat Cette fonction dont l’appel se présente ainsi : strncat (but, source, lgmax) (string.h) travaille de façon semblable à strcat en offrant en outre un contrôle sur le nombre de caractères qui seront concaténés à la chaîne d’arrivée (but). 41 Les fonctions de concaténation de chaînes Exemple : fonction strncat #include <stdio.h> #include <string.h> main() { char ch1[50] = "bonjour" ; char * ch2 = " monsieur" ; printf ("avant : %s\n", ch1) ; strncat (ch1, ch2, 6) ; printf ("après : %s", ch1) ; } avant : bonjour après : bonjour monsi 42 Les fonctions de copie de chaînes La fonction strcpy strcpy ( but, source ) (string.h) recopie la chaîne située à l’adresse source dans l’emplacement d’adresse destin but. Là encore, il est nécessaire que la taille du second emplacement soit suffisante pour accueillir la chaîne à recopier, sous peine d’écrasement intempestif. Cette fonction fournit comme résultat l’adresse de la chaîne but. 43 Les fonctions de copie de chaînes La fonction strncpy strncpy ( but, source, lgmax ) (string.h) procède de manière analogue à strcpy, en limitant la recopie au nombre de caractères précisés par l’expression entière lgmax. Notez bien que, si la longueur de la chaîne source est inférieure à cette longueur maximale, son caractère de fin (\0) sera effectivement recopié. Mais, dans le cas contraire, il ne le sera pas. 44 Les fonctions de copie de chaînes La fonction strncpy strncpy ( but, source, lgmax ) L’exemple suivant illustre les deux situations : Fonctions de recopie de chaînes : strcpy et strncpy #include <stdio.h> #include <string.h> main() { char ch1[20] = "123456789 10 11 12" ; char ch2[20] ; printf (« Donnez un mot : ") ; gets (ch2) ; strncpy (ch1, ch2, 6) ; printf (" ch1 : %s", ch1) ; } Donnez un mot : Génie Informatique ch1 : Génie 789 10 11 12 45 Les fonctions de comparaison de chaînes Il est possible de comparer deux chaînes en utilisant l’ordre des caractères définis par leur code. La fonction : strcmp ( chaîne1, chaîne2 ) compare deux chaînes dont on lui fournit l’adresse et elle fournit une valeur entière définie comme étant : ● positive si chaîne1 > chaîne2(c’est-à-dire si chaîne1arrive après chaîne2, au sens de l’ordre défini par le code des caractères) ; ● nulle si chaîne1 = chaîne2(c’est-à-dire si ces deux chaînes contiennent exactement la même suite de caractères) ; ● négative si chaîne1 < chaîne2. 46 Les fonctions de comparaison de chaînes Par exemple (quelle que soit l’implémentation) : strcmp ("bonjour", "monsieur") est négatif et : strcmp ("paris2", "paris10") est positif. #include <stdio.h> #include <string.h> int main () { char password[] = "123456"; char tmp_password[80]; do { printf ("Tapez votre mot de passe: "); gets (tmp_password); } while (strcmp(password,tmp_password) != 0); printf ("Bienvenu!"); return 0; } 47 Les fonctions de comparaison de chaînes La fonction strncmp : strncmp ( chaîne1, chaîne2, lgmax ) travaille comme strcmp mais elle limite la comparaison au nombre maximal de caractères indiqués par l’entier lgmax. Par exemple : strncmp ("bonjour", "bon", 4) est positif tandis que : strncmp ("bonjour", "bon", 2) vaut zéro. 48 Les fonctions de comparaison de chaînes Enfin, deux fonctions : stricmp ( chaîne1, chaîne2 ) (string.h) strnicmp ( chaîne1, chaîne2, lgmax ) (string.h) travaillent respectivement comme strcmp et strncmp, mais sans tenir compte de la différence entre majuscules et minuscules (pour les seuls caractères alphabétiques) 49 Les fonctions de recherche dans une chaîne On trouve, en langage C, des fonctions classiques de recherche de l’occurrence dans une chaîne d’un caractère ou d’une autre chaîne (nommée alors sous-chaîne). Elles fournissent comme résultat un pointeur de type char * sur l’information cherchée en cas de succès, et le pointeur nul dans le cas contraire. Voici les principales. strchr ( chaîne, caractère ) (string.h) recherche, dans chaîne, la première position où apparaît le caractère mentionné. 50 Les fonctions de recherche dans une chaîne strrchr ( chaîne, caractère ) (string.h) réalise le même traitement que strchr, mais en explorant la chaîne concernée à partir de la fin. Elle fournit donc la dernière occurrence du caractère mentionné. strstr ( chaîne, sous-chaîne ) (string.h) recherche, dans chaîne, la première occurrence complète de la sous-chaîne mentionnée. 51 La fonction strlen La fonction strlen fournit en résultat la longueur d’une chaîne dont on lui a transmis l’adresse en argument. Cette longueur correspond tout naturellement au nombre de caractères trouvés depuis l’adresse indiquée jusqu’au premier caractère de code nul, ce caractère n’étant pas pris en compte dans la longueur. Par exemple, l’expression : strlen ("bonjour") vaudra 7; de même, avec : char * adr = "salut" ; l’expression : strlen (adr) vaudra 5. 52 Les fonctions de conversion Conversion d’une chaîne en valeurs numériques Il existe trois fonctions permettant de convertir une chaîne de caractères en une valeur numérique de type int, long ou double. Ces fonctions ignorent les éventuels espaces de début de chaîne et, à l’image de ce que font les codes de format %d, %ld et %f, utilisent les caractères suivants pour fabriquer une valeur numérique. Le premier caractère invalide arrête l’exploration. En revanche, ici, si aucun caractère n’est exploitable, ces fonctions fournissent un résultat nul. atoi ( chaîne ) (stdlib.h) fournit un résultat de type int. atol ( chaîne ) (stdlib.h) fournit un résultat de type long. atof ( chaîne ) (stdlib.h) fournit un résultat de type double. 53 Les fonctions de conversion Notez que ces fonctions effectuent le même travail que sscanf appliquée à une seule variable, avec le code de format approprié. Par exemple (si n est de type int et adr de type char *) : n = atoi (adr) ; fait la même chose que : sscanf (adr, "%d", &n) ; 54 Les fonctions de conversion Conversion de valeurs numériques en chaîne La norme ne prévoit pas de fonctions de conversion d’une valeur numérique en chaîne, c’est-à-dire de fonctions jouant le rôle symétrique des fonctions atoi, atol, et atof. En revanche, elle prévoit une fonction sprintf, symétrique de sscanf. Elle permet de convertir en chaîne une succession de valeurs numériques, en y incorporant, le cas échéant, d’autres caractères. Par exemple, si n, de type int, contient 15 et si p, de type float, contient 785.35 et si tab est un tableau de caractères de taille suffisante, l’instruction suivante : sprintf (tab, "%d articles coutent %f F", n, p) ; placera dans tab, la chaîne suivante (elle sera bien terminée par un caractère \0) : 15 articles coutent 785.35 F 55 sizeof void main() { int i; char c; float f; double d; printf („‟caractère : %d \n „‟, sizeof( c)); printf („‟entier : %d \n „‟, sizeof (i)); caractère : 1 entier : 2 ou 4 réel : 4 double : 8 printf („‟réel : %d \n „‟, sizeof(f)); printf („‟double : %d \n „‟, sizeof (d)); } 56 Exercice Écrire un programme qui lit un verbe du premier groupe (se termine avec "er") au clavier et qui affiche la conjugaison au présent de l'indicatif de ce verbe. Contrôlez s'il s'agit bien d'un verbe qui se termine avec "er" avant de conjuguer. 57