Vous êtes sur la page 1sur 9

Département : ATE

Module : Programmation orientée objets en C++


Enseignant : M. DIBOUNE.

Chapitre 2 : Les fonctions, les pointeurs et références, les tableaux


Partie 2 : Les pointeurs, les références, les tableaux

1. Les pointeurs et les adresses


Les pointeurs en C++ sont utilisés pour référencer des variables et des adresses machines. Cette notion
est très liée à d’autres concepts tels que les tableaux et les chaines de caractères. En effet, les tableaux
en C++ sont considérés comme un cas spécial des pointeurs référençant le début d’une zone mémoire
contiguë contenant une série de valeurs indexées de même type.
Les pointeurs sont utilisés dans les programmes pour l’accès aux cellules mémoires et à la manipulation
des adresses. Si v est une variable, &v est l’adresse de la case mémoire qui le contient.

Exemple.
p = &v ; // adresse de v
p = satatic_cast_<int*> (2529) // adresse absolue

Dans la première instruction, l’adresse de la variable v est affectée au pointeur p. C’est le compilateur
qui décide de l’adresse à assigner à la variable au moment de sa déclaration. Quant à la seconde
instruction, elle permet d’affecter à p une adresse absolue (l’utilisation du cast est nécessaire afin
d’éviter l’erreur de l’incompatibilité entre les types).
1.1. Adressage et déréférencement
Comme dans le langage C++, l’opérateur de déréférencement * est un opérateur unaire qui permet de
renvoyer la valeur de la variable référencée par un pointeur p. Ainsi, si p est un pointeur, la valeur
directe de p est une adresse mémoire, cependant *p est la valeur contenue dans l’emplacement mémoire
dont l’adresse est dans p.
Exemple.
Listing 2.2.1 : Manipulation des pointeurs en C++.
01 int v = 5;
02 int* p = &v; // pointeur contenant l’adresse de v
03 cout << *p << " = est enregistrée dans " << p << endl;
04 int w = *p + 1; // w == 6
05 p = &w; // p pointe désormais w
1.2. Les arithmétiques des pointeurs
Seules les opérations d’addition et de soustraction sont autorisées sur les pointeurs. Néanmoins, leurs
comportements diffèrent légèrement selon la taille du type auquel ils pointent.
Exemple.
Soient les trois pointeurs vers des variables de types char, int et double :
char *p_char ;
int* p_int ;
double* p_double ;

1
Supposons qu’à priori, ces pointeurs contiennent les cases mémoires suivantes : 2000, 3000 et 4000
respectivement et que le programme effectue les trois instructions suivantes :
p_char++ ;
p_int++ ;
p_double++ ;

Or on sait que la taille des données de types : char, int et double sont respectivement ; 1 octet, 4
octets et 8 octets.
Ainsi :
• p_char aura la valeur 2001 après incrémentation.
• p_int aura 3004 comme valeur, même s’il est incrémenté de 1, car l’incrémentation permet
de pointer le prochain élément de même type.
• p_double aura la valeur de 4008.

L’incrémentation des trois pointeurs est schématisée dans la figure suivante :


2001 2002 2003 2004 2005 2006

p_char

3000 3001 3002 3003 3004 3005 3006 3007 3008

p_int

4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012

p_double

Autres opérations sont résumées dans le tableau suivant :

instruction description
*p++ • Incrémente / décrémente le pointeur p
*p-- • Déréférence le contenu de l’adresse pointée.
*++p • Incrémente / décrémente le pointeur.
*--p • Déréférence le contenu de l’adresse incrémentée.
++*p • Incrémente / décrémente le contenu de la mémoire pointée par p, ++(*p) ;
--*p --(*p)
(*p)++
(*p)-- • Incrémente/ décrémente le contenu de la mémoire pointée par p.

Exercice.
Que fait cette instruction : *p++ = *q++ ?

1.3. Pointeur vers un pointeur


Le C++ permet d’utiliser des pointeurs qui pointent à d’autres pointeurs, qui à leurs tours pointent à des
données ou à même à d’autres pointeurs. La syntaxe exige seulement un astérisque (*) à chaque niveau
d’indirection.
Exemple.

2
int a ; int *b ; int **c ; int ***d ;
a = 19 ; b = &a ; c = &b ; d = &c ;

On suppose que les adresses mémoires choisies aléatoirement pour chaque variable sont : 1000, 2800,
2928, 5012.

1000 2800 2928 5012


19 1000 2800 2928

a b c d
• a est de type int et contient la valeur 19.
• b est de type int* et contient l’adresse de a : 1000.
• *b est de type int et sa valeur est le contenu de la cellule d’adresse 1000 : 19.
• c est de type int** et sa valeur est l’adresse du pointeur b : 2800.
• *c est de type int* (pointeur vers un entier) et sa valeur est le contenu de la cellule d’adresse
2800 : 1000.
• **c est de type int et sa valeur est 19.
• d est de type int*** et sa valeur est l’adresse du pointeur c : 2928.
• *c est de type int** (pointeur vers un pointeur d’entiers) et sa valeur est le contenu de la
cellule d’adresse 2928 : 2800.
• **c est de type int* et sa valeur est 1000.
• ***c est de type int et sa valeur est 19.

1.4. Passage d’arguments dans une fonction par adresse


Les adresses des variables peuvent être passées comme arguments à des fonctions pour que les valeurs
contenues dans ces variables puissent être modifiées.
Exemple.
Écrire une fonction qui permet de faire une permutation entre deux variables a et b.
Listing 2.2.1 : Manipulation des pointeurs en C++.
01 void permut(int*, int*);
02 int main()
03 {
04 int i = 2, j = 5;
05 cout << i << “\t” << j << endl; // 2 5 sera affiché
06 permut(&i, &j);
07 cout << i << “\t” << j << endl; // 5 2 sera affiché
08 return 0;
09 }
10 void permut(int* p, int* q)
11 {
12 int temp;
13 temp = *p;
14 *p = *q;
15 *q = temp;
16 }

Les paramètres p et q sont des pointeurs à des entiers. La variable temp est une variable entière locale
à la fonction permut. Tout d’abord, la valeur contenue dans la case mémoire pointée par p est affectée
à temp. Ensuite, la valeur de la case mémoire pointée par q est affectée à la case mémoire pointée par
p. Enfin, la valeur de temp est affectée à la case mémoire pointée par q (Le mécanisme derrière la

3
persistance des changements effectués sur les paramètres passés par pointeurs à une fonction sera vu
dans le chapitre 5).

1.5. Pointeurs et le mot clé const


Le C++ permet de déclarer des pointeurs constants ainsi que des pointeurs sur des objets constants.
Les instructions C++ qui permettent de déclarer des pointeurs sur des objets constants ont la syntaxe
suivante :
const type* id_pointeur ;

Exemple.
const int* p; déclare un pointeur constant p sur un entier.

Pour déclarer un pointeur sur des données constantes, la syntaxe suivante est utilisée :

Type* const id_pointeur ;


Exemple.
int* const p= &x; déclare un pointeur p sur un entier constant.

La déclaration des pointeurs constants sur des données constantes se fait selon la syntaxe suivante :

const Type* const id_pointeur ;

Exemple.
const int* const p= &x; //déclare un pointeur constant sur un entier constant.

1.6. Pointeurs sur des fonctions


Si une fonction n’est pas inline ; on peut accéder à son adresse (une fonction est dite inline si
à la compilation, le code complet de cette fonction est injecté dans le code de la fonction appelante):
Listing 2.2.1 : Manipulation des pointeurs en C++.
01 int fonction(double t){
02 /* instruction */
03 }
04 int (*fct_ptr)(double) = &fonction;
05 int y = (*fct_ptr)(10.5);

Les opérateurs d’indirection et de déréférencement sont facultatifs.


Exemple.
int (*fct_ptr)(double) = fonction;
int y = fct_ptr (10.5);

2. Les références
En C++, une référence permet de donner deux noms différents pour le même emplacement mémoire.
La déclaration d’une référence est faite comme suit :
int a = 0;
int &b = a;

a et b représentent la même case mémoire et non pas deux cases mémoires contenant la même valeur.

4
Une référence pointe toujours vers la même case mémoire ; elles sont généralement utilisées dans une
fonction en tant qu’argument ou valeur de retour. Son intérêt principal est qu’elle permet de laisser le
choix au compilateur de mettre en œuvre les instructions adéquates pour implémenter le transfert par
adresse .i.e. la variable est utilisée via son identifiant sans se soucier de préciser si on utilise son adresse
ou sa valeur (comme c’est le cas pour les pointeurs par des symboles * ou &).
2.1. Arguments transmis par référence
Le C++ peut entièrement prendre en charge la transmission des arguments par adresse à travers
l’utilisation des références. L’intérêt du concept de référence est qu’il laisse au compilateur le choix
pour mettre en œuvre “les mécanismes” adéquats pour la transmission des arguments par adresse.
Ainsi, le programmeur utilisateur de la fonction n’a, dès lors, plus à connaitre la nature des arguments à
transmettre (une valeur ou une adresse).
Ainsi, en utilisant la notion de références, le listing 2.2.1 devient
Listing 2.2.2 : arguments transmis par références
01 void permut(int& p, int& q);
02 int main()
03 {
04 int i = 2, j = 5;
05 cout << i << “\t” << j << endl; // 2 5 sera affiché
06 permut(i, j);
07 cout << i << “\t” << j << endl; // 5 2 sera affiché
08 return 0;
09 }
10 void permut(int& p, int& q)
11 {
12 int temp;
13 temp = p;
14 p = q;
15 q = temp;
16 }

Exercice.
Écrire une fonction qui d’ajoute une valeur transmise en argument à une variable transmise également
en argument. Par exemple : À l’appel de la fonction fct : fct (2*p, n) ; la valeur de l’expression
2*p est ajoutée à la variable n.

3. Les Tableaux
Un tableau en C++ est une suite d’éléments de même type placés dans un zone mémoire contiguë, l’accès
aux différents éléments se fait en ajoutant un indice à l’identifiant du tableau.

Exemple.
Un tableau de six (06) éléments de type int appelé tab peut être représenté comme suit :

Indice = 0 Indice = 1 Indice = 2 Indice = 3 Indice = 4 Indice = 5


tab: Tab[0] Tab[1] Tab[2] Tab[3] Tab[4] Tab[5]

3.1. Déclaration d’un tableau


La déclaration d’un tableau à une dimension en C++ permet de réserver un espace mémoire contiguë
dans lequel les éléments du tableau sont rangés.

5
Type_elements nom_tableau [Taille] ;

Exemples.
int tab_1[6] ; // tab_1 est un tableau de 6 entiers
char tab_2[20] ; // tab_2 est un tableau de 20 caractères

3.2. Initialisation des éléments d’un tableau


Les éléments dans un tableau peuvent être explicitement initialisés à des valeurs spécifiques au moment
de sa déclaration, en enfermant les valeurs des différents éléments entre {}.
Exemple.

int tab[6] = {2,4,6,8,10,12};

Cette instruction permet de déclarer un tableau de six éléments initialisés à 2, 4, 6, 8, 10, 12 qui peut
être représenté comme suit

indice = 0 indice = 1 indice = 2 indice = 3 indice = 4 indice = 5


tab: 2 4 6 8 10 12

Il est possible de ne pas initialiser tous les éléments du tableau.


Exemple.
int tab[6] = {2,4,6}; // les éléments non initialisés explicitement
sont initialisés par défaut à 0

Le tableau déclaré peut être représenté comme suit

indice = 0 indice = 1 indice = 2 indice = 3 indice = 4 indice = 5


tab: 2 4 6 0 0 0

3.3. Accéder à un élément d’un tableau


L’accès à un élément quelconque d’un tableau se fait en indiquant son indice.
Nom_tableau[indice] ;

Exemple.
int tab[6] ; // déclaration d’un tableau de 6 éléments
tab[2] = 5 ; // le troisième élément de tab reçoit la valeur 5
int a = tab[2] ; // la variable a reçoit la valeur du troisième élément du tableau
3.4. Tableaux multidimensionnels
Les tableaux multidimensionnels peuvent être considérés comme étant des tableaux de tableaux. Comme
exemple un tableau à deux dimensions × peut être vu comme un vecteur (tableau à une dimension)
de N éléments et que chacun de ces éléments est un vecteur de taille M. Tous les éléments du tableau
sont de même type.
Exemple.
0 1 2 3
0 tab[1][0] tab[1][1] tab[1][2] tab[1][3]
tab 1 tab[1][0] tab[1][1] tab[1][2] tab[1][3]
2 tab[1][0] tab[1][1] tab[1][2] tab[1][3]

6
T[0][0] T[0][1] T[0][2] T[0][3] T[1][0] T[1][1] T[1][2] T[1][3] T[2][0] T[2][1] T[2][2] T[2][3]

tab représente un tableau bidimensionnel de taille 3 par 4 éléments de type int. La syntaxe C++ qui
permet de déclarer cette structure est :
int tab [3][4]; // déclaration d’un tableau de 3 lignes et 4 colonnes

De manière plus générale, un tableau multidimensionnel se définit en C++ selon la syntaxe suivante :
type_element nom_du_tableau [a1][a2][a3] ... [aN];

• Les éléments entre les crochets représentent le nombre d'éléments dans chaque dimension.
• Le nombre de dimensions N est quelconque (illimité).

3.4.1. Initialisation des éléments

L'initialisation des éléments d'un tableau multidimensionnel se fait presque de la même façon que pour
les tableaux unidimensionnels.

Méthode 1 : Initialisation élément par élément


nom_tableau [0][0] = 2;
nom_tableau [0][1] = 3;

Méthode 2 : Initialisation par des boucles


On utilise des boucles imbriquées ; chacune correspond à un indice d'une dimension.

Exemple.
les éléments de notre tableau tab [3][4] pourront être initialisés à 0 par les instructions suivantes :
Listing 2.2.2 : Initialisation d’un tableau en utilisant des boucles
01 for (int i=0; i<3; i++){
02 for (int j=0; j<4; j++){
03 Tableau[i][j] = 0;
04 }
05 }

Méthode 3 : Initialisation à la définition

type Nom_du_tableau [Taille1][Taille2]...[TailleN] = {a1, a2, ... aN};

Les valeurs sont attribuées aux éléments successifs en incrémentant d'abord les indices les plus à droite.
Par exemple, dans un tableau à 2 dimensions ; on commence à initialiser les éléments d’indices [0][0],
[0][1], [0][2] ... puis [1][0], [1][1], etc.

Exercice.
Écrire une fonction C++ qui permet de chercher les points selles d’une matrice. Sachant que les points
selles d’une matrice sont des éléments qui sont maximums sur leurs colonnes et minimum sur leurs
lignes.

7
4. Mémoire dynamique

Les différents programmes traités jusqu’à présent utilisent des espaces mémoire dont la capacité est
déterminée lors de la compilation, avant l’exécution du programme.
Ainsi, la taille de la mémoire réservée aux données est allouée à la déclaration des variables utilisées par
le programme.
Cependant, il y a des situations ou la taille de la mémoire utilisée est déterminée lors de l’exécution du
programme, exemple, lorsque la taille d’un tableau doit être entrée par l’utilisateur durant l’exécution
du programme. Dans ces cas de figure, l’allocation dynamique de la mémoire est nécessaire. Les
opérateurs C++ qui permettent d’assurer ça sont new et delete.

4.1. Les opérateurs new et new []


La syntaxe des expressions qui permettent l’allocation de la mémoire dynamique en C++ est la suivante :
pointeur = new type_donnee ;
pointeur = new type_donnee [N] ;

La première expression alloue de l’espace mémoire à un seul élément de type type_donnee. La


seconde permet d’allouer un bloc (un tableau) de N éléments de type type_donnee et renvoie
l’adresse de son premier octet dans pointeur.

Exemple.

int * p ;
p = new int [5] ;

Dans cet exemple, le compilateur alloue de l’espace mémoire à cinq éléments de type int et retourne
un pointeur vers le premier élément de la séquence (affecté à p). Désormais, le pointeur p contient
l’adresse du début d’un tableau de cinq éléments de type int.
Supposant que l’adresse aléatoire choisie par le compilateur pour le premier élément du tableau
dynamique est 1000. La figure suivante schématise la zone mémoire allouée par le compilateur.
1000 1004 1008 1012 1016

int
p == 1000

4.2. Les opérateurs delete et delete[]


Une zone mémoire allouée dynamiquement doit être libérée explicitement avec l’opérateur delete[]
afin qu’elle soit réutilisable par de nouvelles requêtes d’allocations de la mémoire dynamique. La
syntaxe des instructions C++ permettant la libération de la mémoire dynamique sont les suivantes :
delete pointer;
delete[] pointer;
La première instruction permet de libérer un seul élément alloué par l’opérateur new; la seconde permet
de libérer la mémoire allouée à un tableau en utilisant l’expression new type[taille].

Si les opérateurs delete et delete[] sont appliqués sur des pointeurs nuls, ils ne font rien.

8
Exercice
Ecrire un programme C++ qui permet d’allouer puis de libérer de l’espace mémoire à un tableau
dynamique bidimensionnel ensuite à un tableau tridimensionnel. Sachant que la taille est spécifiée par
l’utilisateur.