p. 1/18 Chapitre 3 : Pointeurs et références Notion de pointeur L adresse d une variable est une valeur. On peut donc la stocker dans une variable. Un pointeur est une variable qui contient l adresse d une variable. exemple : Si p est une variable qui contient l adresse d un entier i, on dit que p est de type pointeur sur un entier. On dit aussi que p pointe sur i. La variable i ne peut pas changer d adresse. Donc si la valeur de p change, cela signifie que p pointe sur un autre entier. Déclaration d un pointeur sur un entier: int * p;
p. 2/18 Indirection et déréférencement L opérateur d indirection & placé devant une variable donne son adresse. L opérateur de déréférencement placé devant un pointeur donne la valeur de la variable pointée. Exemple : int i=0; int*p; p=&i; *p=*p+1; cout << i; i vaut 1
p. 3/18 Notion de référence Le C++ (pas le C) permet de créer des références. Une référence est un pseudonyme. Quand on déclare une variable : int i Le nom i désigne une case mémoire. Une référence ri à la variable i est simplement un autre nom pour désigner cette case mémoire. Donc quand i est modifiée, ri aussi et inversement. int i=0; int& ri=i; ri=3; cout << i << endl;
p. 4/18 Liens entre pointeurs et références Les références et les pointeurs sont étroitement liés : En effet les variables i et ri ont la même adresse. Donc ce qui précède peut s écrire : int i=0; int* pi=&i; *pi=3; cout << i << endl; Avantage des références : l écriture est plus simple!
p. 5/18 Passage de paramètres dans une fonction Dans le C/C++, le passage de paramètres à une fonction se fait par valeur. Passage par valeur : int fonction (int x); int i=5; Quand on appelle fonction(i); la valeur 5 est copiée dans une variable locale et c est cette variable qui est utilisée pour faire les calculs dans la fonction appelée. La fonction ne peut donc pas modifier la valeur de i.
p. 6/18 Passage de paramètres dans une fonction Le C/C++ ne permet pas le passage par variable. Passage par variable : On passe la variable elle-même. Pour le faire en C/C++, il faut utiliser les pointeurs ou les références. En effet : la copie d un pointeur sur i est encore un pointeur sur i!! int fonction(int * px); int i=5; fonction(&i); Cette fonction peut modifier i en utilisant px.
p. 7/18 Exemple Passage par valeur : void test (int j){j=3; return; } int main() {int i=2; test(i); cout << i << endl; }
p. 8/18 Suite Passage par variable avec les pointeurs : void test (int* pj){*pj=3; return; } int main() {int i=2; test(&i); cout << i << endl;} Passage par variable avec les références : void test (int& rj){rj=3; return; } int main() {int i=2; test(i); cout << i << endl;}
p. 9/18 Remarque importante Les passages par variables sont plus rapides et plus économes en mémoire que les passages par valeur car les étapes de la création de la variable locale et la copie de la valeur ne sont pas faites. Cependant les passages par valeur permettent d éviter de changer par erreur les variables passées en paramètre. On peut se prémunir de cela avec le mot clé CONST : void test (const int & rj) Il est recommandé de passer par référence tous les paramètres dont la copie peut prendre beaucoup de temps.
p. 10/18 Arithmétique des pointeurs Quand on écrit : int i; int* p=&i; p+3 a un sens : p + 3 = &i + 3 sizeof(int) C est-à-dire que p+3 est un pointeur d entier qui pointe sur une case mémoire qui est décalée de trois cases par rapport à i.
p. 11/18 Tableaux Déclaration d un tableau d entiers de taille 20: int tab[20] tab[0] est le nom de la variable constituant la première case du tableau. tab[19] est le nom de la variable constituant la dernière case du tableau. tab[3] est le nom de la variable constituant la quatrième case du tableau. La case mémoire de tab[3] est décalée de trois cases de taille int par rapport à la case mémoire de tab[0]. C est-à-dire : &tab[3] = &tab[0] + 3 sizeof(int)
p. 12/18 Pointeurs et tableaux Afin de pouvoir utiliser l arithmétique des pointeurs pour manipuler les éléments des tableaux, le C++ effectue les conversions implicites suivantes : tableau pointeur sur la première case. pointeur sur la première case tableau. Conséquences : tab est un pointeur sur la première case du tableau tab : tab[0] = tab, tab[1] = (tab + 1), tab[2] = (tab + 2)... les tableaux sont modifiés quand ils sont passés en paramètres d une fonction.
p. 13/18 Chaînes de caractères (qui proviennent du C) Les chaînes de caractères sont des tableaux de caractères dont le dernier caractère est \0. Ce sont également des pointeurs sur des caractères. char * mot[7]="chaine" ; De nombreuses fonctions de manipulations de chaînes sont fournies dans le fichier d en-tête <cstring>.
p. 14/18 Allocation dynamique de mémoire Exemple : Programme gérant une liste de personnes. On ne sait pas à l avance combien de personnes seront entrées : le compilateur ne peut donc pas faire la réservation de mémoire. C est au programme de la faire au cours de son exécution. L allocation dynamique de mémoire est la réservation de mémoire qui est faire au cours de l exécution du programme. Celle-ci se fait dans une zone de la mémoire appelée le tas. Les allocations faites par le compilateur (appelées allocation automatique) se font dans la pile.
p. 15/18 Opérateur new Les pointeurs, grâce à l opérateur new, sont surtout utilisés pour créer des variables en cours d exécution du programme. L instruction new int réserve une case mémoire pour un entier dans le tas et renvoie l adresse de cette case. Donc l instruction int* pi=new int; déclare un pointeur pi sur un entier et le fait pointer sur l entier réservé dans le tas grâce à new int. Pour libérer la case mémoire réservée dans le tas, il faut absolument utiliser l instruction : delete pi;
p. 16/18 Opérateur new[] Pour réserver un tableau d entiers de taille N en cours d exécution, on utilise : int * tab=new int[n]; Pour libérer cette place mémoire réservée, on utilise : delete[] tab; Bien comprendre : tab (de type int *) est dans la pile. *tab (tableau de N cases) est dans le tas. Attention : tab est supprimé à la sortie du bloc d exécution où il a été déclaré. Mais ce n est pas le cas de *tab : sa destruction nécessite l instruction delete[].
p. 17/18 Initialisation pointeurs Il faut toujours initialiser les pointeurs que vous utilisez. Il y a trois façons de les initialiser. int* p; avec une variable automatique int i; p=&i; avec une allocation dynamique p=new int; ou p=null
p. 18/18 Gestion des débordements de mémoire Si l instruction new n arrive pas à allouer de mémoire (par manque de place), l exécution s arrête. Pour que le programme s arrête "proprement", on peut utiliser l instruction set_new_handler de la manière suivante : #include <cstdlib> int main() {set_new_handler(&deborde); //...code utilisant new } void deborde() {cout << "débordement" <<endl; exit(-1); }