CINI – Li115 1 Semaine 10 Les pointeurs ● Notion d'adresse ● Déclaration et utilisation de pointeurs ● "Types pointeur" et initialisation des pointeurs ● Fonctions : passage de paramètres par référence ● Suppression des variables globales
CINI – Li115 2 Notion d'adresse adresse : localisation d'une variable en mémoire 123 int var_a = 123 (stockée pointeur p = adresse de var_a pointeur : variable qui a pour valeur une adresse. on ne s'intéresse pas directement à la valeur d'un pointeur. on s'intéresse à la variable se trouvant à l'adresse contenue par le pointeur Notation pour la suite pour adresse de la variable de nom a
CINI – Li115 3 Déclaration *var_pointeur; var_pointeur variable de type pointeur pointeur sur une variable de type int *mon_pointeur; déclaration de la variable mon_pointeur de type pointeur sur un entier réservation de la place mémoire pour stocker une adresse Pas de réservation mémoire pour stocker l'entier
CINI – Li115 4 Utilisation Récupérer l'adresse d'une variable : &une_variable Accès à une valeur à partir d'un pointeur : *var_pointeur Attention : on accède à une valeur de type correspondant à la déclaration de mon_pointeur Déclarations ● *var_pointeur; ● une_variable; Exemple :int ma_variable = 123; int *mon_pointeur = &ma_variable; int aux = *mon_pointeur;
CINI – Li Utilisation de son adresse pour accéder à une variable int *p; ? p(adr ? int i; p = *p = -7; -7 adresse d'un entier valeur entière CINI_print_string("Valeur : "); CINI_print_int(*p); i = 65; Valeur : - 7 CINI_print_string("Valeur : "); CINI_print_int(*p); Valeur : 65 p 65 p i
CINI – Li115 6 Exemples d'utilisation int main(){ int * p1; char * p2; float * p3; int i = 12; char c = 'a'; float f = 12.9; p1 = &i; p2 = &c; p3 = &f; CINI_print_string("Affichage de la valeur de i : "); CINI_print_int(i); CINI_print_string(" ou "); CINI_print_int(*p1); CINI_newline(); CINI_print_string("Affichage de la valeur de c : "); CINI_print_char(c); CINI_print_string(" ou "); CINI_print_char(*p2); CINI_newline(); CINI_print_string("Affichage de la valeur de f : "); CINI_print_float(f); CINI_print_string(" ou "); CINI_print_float(*p3); CINI_newline(); return 0; } Affichage de la valeur de i : 12 ou 12 Affichage de la valeur de c : a ou a Affichage de la valeur de f : ou
CINI – Li115 7 Utilisation de plusieurs pointeurs int *p1, *p2; ? p1(adr ? int a,b; p1 = &a; *p1 = -3; ? p2(adr ? p1 = &b; *p1 = -7; p2 = p1; *p2 = 9; ? p1a ? ? p2 b ? p1a ? ? p2 b ? -7 p1a ? ? p2 b ?
CINI – Li115 8 Types pointeur *pointeur; *pointeur est de type var; &var adresse d'une variable de type Si ≠ alors * ≠ * *pointeur et var de même type pointeur et &var de même type Soient int *p1; float *p2; *p1 (de type int) et *p2 (de type float) ne sont pas de même type p1 et p2 ne sont pas de même type
CINI – Li115 9 Initialisation d'un pointeur valeur d'un pointeur = adresse d'une variable adresse d'une variable : - déterminée lors de l'exécution - ne peut pas être modifiée - l'utilisateur ne peut pas la choisir Initialisation d'un pointeur : - à partir d'une variable (&nom_variable) - à partir d'un autre pointeur (correctement initialisé), s'ils pointent sur une variable de même type (pointeur1 = pointeur2) (ne jamais initialiser avec un littéral (valeur numérique), il est impossible de savoir s'il s'agit bien de l'adresse d'une variable)
CINI – Li Initialisation d'un pointeur Exemples : #include int main(){ int * p1, *p2; char * p3, *p4; float * p5, *p6; int i = 12; char c = 'a'; float f = 12.9; p1 = &i; p3 = &c; p5 = &f; p2 = p1; p4 = p3; p6 = p5; return 0; }
CINI – Li Pointeurs : règles de bonne conduite La valeur d'un pointeur doit toujours correspondre à l'adresse d'une variable déclarée Initialisation par une instruction de la forme ● mon_pointeur = &ma_variable; ● avec mon_pointeur et ma_variable déclarées comme suit : ● *mon_pointeur; ● ma_variable; Initialisation par une instruction de la forme ● mon_pointeur1 = mon_pointeur2; ● avec mon_pointeur1 et mon_pointeur2 déclarées comme suit : ● *mon_pointeur1, *mon_pointeur2; ● et mon_pointeur2 a pour valeur l'adresse d'une variable déclarée
CINI – Li Récapitulatif ● Déclaration : * nom_pointeur; ● Indirection : opérateur & (donne l'adresse de la variable) ● nom_pointeur = & nom_variable; – Avec *mon_pointeur; – ma_variable; ● Déréférencement : opérateur * (donne la valeur pointée) ● * nom_pointeur ; → retourne une valeur de type ● Initialisation : ● nom_pointeur = & nom_variable; – Avec *mon_pointeur; – ma_variable; ● mon_pointeur1 = mon_pointeur2; – Avec *mon_pointeur1, *mon_pointeur2; – Et mon_pointeur2 initialisé.
CINI – Li Utilisation des pointeurs int main(){ float * p_f1, * p_f2, * p_f3 ; float f1 = 13.5, f2 =24.6; p_f1 = &f1; p_f2 = &f2; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); p_f3 = p_f1; p_f1 = p_f2; p_f2 = p_f3; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); return 0 ; } Les valeurs pointées par p_f1 et p_f2 ont été échangées. Les valeurs de p_f1 et p_f2 ont été échangées. Les valeurs de f1 et f2 sont inchangées.
CINI – Li Utilisation des pointeurs ● Que se passe-t-il en mémoire ? int main(){ float * p_f1, * p_f2, * p_f3 ; float f1 = 13.5, f2 =24.6; p_f1 = &f1; p_f2 = &f2; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); p_f3 = p_f1; p_f1 = p_f2; p_f2 = p_f3; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); return 0 ; } main p_f1 p_f2 adr p_f3 f1 f
CINI – Li Utilisation des pointeurs int main(){ float * p_f1, * p_f2 ; float f1 = 13.5, f2 =24.6, f3; p_f1 = &f1; p_f2 = &f2; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); f3 = *p_f1; *p_f1 = *p_f2; *p_f2 = f3; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); return 0; } Les valeurs pointées par p_f1 et p_f2 ont été échangées. Les valeurs de f1 et f2 ont été échangées. Mais les valeurs de p_f1 et p_f2 sont inchangées.
CINI – Li Un nouvel exemple ● Que se passe-t-il en mémoire ? main p_f1 p_f2 adr f3 f1 f adr int main(){ float * p_f1, * p_f2 ; float f1 = 13.5, f2 =24.6, f3; p_f1 = &f1; p_f2 = &f2; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); f3 = *p_f1; *p_f1 = *p_f2; *p_f2 = f3; CINI_print_float(*p_f1); CINI_print_string(" "); CINI_print_float(*p_f2); CINI_print_string(" "); CINI_print_float(f1); CINI_print_string(" "); CINI_print_float(f2); CINI_newline(); return 0 ; } adr
CINI – Li Retour sur le passage de paramètres ● Jusqu'à présent les paramètres utilisés dans nos fonctions étaient passés par valeur (ou par copie) : ● dans la fonction, on travaille sur une copie des valeurs passées en paramètre ● les modifications réalisées sur ces valeurs ne sont donc pas répercutées en dehors de la fonction ● Exemple : #include void ajouteN(int val, int n){ val = val + n; } int main(){ int val = 45,n = 3; CINI_print_int(val); CINI_print_string(" "); CINI_newline(); ajouteN(val, n); CINI_print_int(val); CINI_print_string(" "); CINI_newline(); return 0; }
CINI – Li AjouteN : exécution main val n 45 3 ajoute N val n 3 45 à l'appel ajouteN(val, n) 48 après l'appel ajouteN(val, n) val = val + n;
CINI – Li Passage par valeur et par référence ● Comment faire pour que la valeur de la variable val du main soit modifiée après l'appel à ajouteN ? – → passer en paramètre non pas la valeur de val mais son adresse, c'est le passage par référence ● Exemple : #include void ajouteN(int* valAd, int n){ *valAd = *valAd + n; } int main(){ int val = 45,n = 3; CINI_print_int(val); CINI_print_string(" "); CINI_newline(); ajouteN(&val, n); CINI_print_int(val); CINI_print_string(" "); CINI_newline(); return 0; }
CINI – Li AjouteN : exécution main val n 3 ajoute N valAd n 3 45 à l'appel ajouteN(&val, n) après l'appel ajouteN(&val, adr 48 *valAd = *valAd + n;
CINI – Li Fonctions : passage de paramètres par référence Variables accessibles dans une fonction - variables globales, locales ou paramètres - variables dont on a l'adresse (et qui se trouvent encore dans la pile) Passer une adresse en paramètre - passage par référence - permet de lire et de modifier la valeur de la variable dont on a l'adresse
CINI – Li Calcul de la multiplication sans fonction #include int main() { int val1,val2,mult; /* saisie de val1 et val2 */... mult = val1 * val2 ; CINI_print_int(mult); CINI_newline(); return 0; }
CINI – Li Calcul de la multiplication avec une fonction qui retourne le résultat #include int main() { int val1,val2,mult; /* saisie de val1 et val2 */... mult = multiplication1(val1,val2) ; CINI_print_int(mult); CINI_newline(); return 0; } int multiplication1(int a, int b) { return a*b; }
CINI – Li Calcul de la multiplication avec une fonction qui ne retourne pas de valeur Il faut récupérer le résultat grâce à un paramètre #include int main() { int val1,val2,mult; /* saisie de val1 et val2 */... multiplication2(val1,val2,&mult) ; CINI_print_int(mult); CINI_newline(); return 0; } void multiplication2(int a, int b, int *res) { *res = a*b; }
CINI – Li Multiplication : exécution main val1 val2mult adr après saisie 45 3 multiplication2 a bres 3 à l'appel multiplication2(val1,val2,&mult)
CINI – Li main val1 val2mult adr après saisie 45 3 multiplication2 a bres 3 *res = a * b 135 après appel de mutiplication multiplication2 a *res = a * b
CINI – Li Calcul de la division euclidienne fonction qui calcule le quotient et le reste de la division de deux entiers positifs a = b * quotient + reste (reste < a) la fonction calcule deux résultats : - obligation de transmettre (au moins) un des résultats par un paramètre - nous choisissons de transmettre les deux résultats en paramètres la fonction prend 4 paramètres : - les entiers a et b - les adresses des variables représentant le quotient et le reste
CINI – Li Calcul de la division euclidienne #include void div_euclid(int a, int b, int *q, int *r) { } int main() { int val1,val2,quot,res; /* saisie de val1 et val2 */... CINI_print_int(quot); CINI_newline(); CINI_print_int(res); CINI_newline(); return 0; } div_euclid(val1,val2,",&res) ; *q = a / b; *r = a % b;
CINI – Li Division euclidienne : exécution main val1 val2 quot après saisie 67 7 div_euclid a b q 7 à l'appel div_euclid(val1,val2,",&res) res
CINI – Li main val1 val2 quot après saisie 67 7 div_euclid a b q 7 à l'appel div_euclid(val1,val2,",&res) res *q = a / b 9 *r = a % b 4 après appel de div_euclid 67 7 div_euclid a b à l'appel div_euclid(val1,val2,",&res) *q = a / b *r = a % b Division euclidienne : exécution
CINI – Li Cas d'utilisation du passage par référence ● Le passage par référence est utilisé : ● Si on souhaite modifier une variable déclarée en dehors de la fonction : on passe l'adresse de cette variable en paramètre Par exemple : exemple précédent sur la multiplication → on souhaite modifier une variable déclarée dans la fonction main. ● Si on souhaite que la fonction retourne plusieurs résultats : ajouter autant de paramètres que de résultats Par exemple : fonction calculant le reste et le quotient de la division euclidienne (cf. exemple précédent), fonction calculant les racines d'un polynôme du second degré, fonction calculant le min et le max d'un tableau,... ● Afin d'éviter la recopie de structures importantes (plus de détails dans les UE LI215 et LI213) : on passe en paramètre l'adresse de la structure plutôt que la structure complète → gain en efficacité.
CINI – Li Fonctions et pointeurs : règles de bonne conduite Une fonction ne produit jamais comme résultat l'adresse d'une de ses variables locales (aussi bien comme valeur d'un paramètre passé par référence qu'en valeur de retour) Pourquoi ? Variable locale à une fonction accessible tant que la fonction n'est pas finie son adresse n'est plus pertinente une fois la fonction finie (et l'espace associé libéré dans la pile) L'adresse est alors inutilisable de là où l'appel à la fonction a été effectué
CINI – Li Vers la suppression des variables globales Remplacer chaque variable globale par une variable locale (déclarée dans la fonction main en général) Ajouter un paramètre aux fonctions (autres que main) qui utilisaient la variable globale - passage par valeur si la fonction ne fait que lire la valeur de la variable - passage par référence si la fonction modifie la valeur de la variable
CINI – Li Variables globales : exemple #include int a, b; void f() { b = a*2; } void g() { b = b*a; a = a*2; } int main() { a=2; b=7; f(); g(); CINI_print_int(a); CINI_newline(); CINI_print_int(b); CINI_newline(); return 0; } Affichage 4 8 ab 27 b est modifiée par f() 4 a et b sont modifiées par g() 84
CINI – Li Variables globales : suppression #include int a, b; void f() { b = a*2; } void g() { b = b*a; a = a*2; } int main() { a=2; b=7; f(); g(); CINI_print_int(a); CINI_newline(); CINI_print_int(b); CINI_newline(); return 0; } void f(int v1, int *p1) { *p1 = v1*2; } f(a,&b); void g(int *p1, int *p2) { *p2 = *p2 * *p1; *p1 = *p1 * 2; } g(&a,&b); int a=2, b=7;
CINI – Li Variables globales ou paramètres par référence ? Une variable globale est utilisable partout dans le programme Plus d'inconvénients que d'avantages => NE PAS UTILISER DE VARIABLES GLOBALES mais il est difficile de comprendre comment la valeur d'une variable globale évolue - compréhension du programme difficile - modification du programme difficile - localisation d'une erreur difficile lorsqu'elle porte sur la valeur d'une variable globale