Les pointeurs en C++
Chaque variable de votre programme est située quelque part dans la mémoire. Elles ont donc toutes une adresse unique qui identifie l'endroit où elles sont stockées.
Un pointeur est une variable qui peut stocker l'adresse d'une autre variable, d'un élément de données situé ailleurs en mémoire.
Pour utiliser une donnée stockée à l'adresse contenue dans un pointeur, il faut connaître le type de cette donnée. Chaque pointeur pointe donc vers un type particulier de données à cette adresse.
Création de pointeurs
Les pointeurs sont déclarés comme n'importe quelle autre variable, sauf qu'un astérisque (*) est placé entre le type de données et le nom du pointeur.
Syntaxe de déclaration
int* a {}; // pointeur sur un entier (initialisé à 0)
int *a {}; // même chose (l'astérisque peut être collé au type ou au nom)
int *a {}, *b {}, *c {}; // plusieurs pointeurs dans la même instructionL'initialisateur {} initialise les variables pointeur avec l'équivalent pointeur de zéro, qui est une adresse spéciale qui ne pointe vers rien. Cette valeur s'écrit nullptr.
Vous n'êtes pas obligé d'initialiser un pointeur, mais il est imprudent de ne pas le faire. Les pointeurs non initialisés sont plus dangereux que les variables ordinaires.
En règle générale, initialisez toujours un pointeur. Si vous ne pouvez pas encore lui donner sa valeur prévue, initialisez-le à nullptr.
Exemple 1 : Initialisation à nullptr
int* a {nullptr}; // pointeur qui ne pointe vers rienTaille des pointeurs
Quel que soit le type ou la taille des données auxquelles un pointeur fait référence, la taille de la variable pointeur elle-même sera toujours la même pour une plate-forme donnée.
Exemple 2 : Taille des pointeurs
#include <iostream>
using namespace std;
int main(void){
cout << sizeof(double) << " > " << sizeof(char16_t) << std::endl;
cout << sizeof(double*) << " == " << sizeof(char16_t*) << std::endl;
return 0;
}8 > 2 8 == 8
La taille des pointeurs dépend uniquement de la quantité de mémoire adressable de votre plate-forme cible (généralement 8 octets sur un système 64 bits).
L'opérateur d'adressage (&)
L'opérateur d'adressage, &, est un opérateur unaire qui permet d'obtenir l'adresse d'une variable.
Exemple 3 : Stocker l'adresse d'une variable
long nombre {12345L};
long* pnombre {&nombre}; // pnombre contient l'adresse de nombre&nombre produit l'adresse de nombre, donc pnombre a cette adresse comme valeur initiale.
Exemple 4 : Réaffectation d'un pointeur
long note {1454L};
long* pnombre {¬e}; // pnombre pointe maintenant vers noteL'opérateur & peut être appliqué à une variable de n'importe quel type, mais vous ne pouvez stocker l'adresse que dans un pointeur du type approprié.
Exemples 5 et 6 : Utilisation de auto avec les pointeurs
auto pnombre {&nombre}; // type déduit : long*
auto* pnombre {&nombre}; // plus explicite : auto* indique un pointeurUtilisez auto* plutôt que auto seul pour clarifier dès la déclaration qu'il s'agit d'un pointeur. Une variable déclarée avec auto* ne peut être initialisée qu'avec une valeur de type pointeur.
L'opérateur d'indirection (déréférencement) (*)
L'application de l'opérateur d'indirection, *, à un pointeur permet d'accéder au contenu de l'emplacement mémoire vers lequel il pointe.
Cet opérateur est aussi appelé opérateur de déréférencement.
Exemple 7 : Déréférencement d'un pointeur
#include <iostream>
using namespace std;
int main(void){
int nombre {10};
int* pnombre = &nombre;
cout << "Adresse de nombre : " << pnombre << endl;
cout << "Valeur de nombre : " << *pnombre << endl;
return 0;
}Adresse de nombre : 0x16f99f398 Valeur de nombre : 10
Cet exemple démontre que l'utilisation d'un pointeur déréférencé est identique à l'utilisation de la variable vers laquelle il pointe. Vous pouvez utiliser *pnombre dans une expression comme vous utiliseriez nombre.
Pointeur vers un pointeur
Parfois, il peut être utile d'avoir un pointeur qui peut pointer vers un autre pointeur. On déclare un pointeur avec deux astérisques.
Syntaxe
int** r = &pnombre; // pointeur vers pnombreExemple 8 : Utilisation d'un pointeur de pointeur
#include <iostream>
using namespace std;
int main(void){
int nombre {10};
int* pnombre = &nombre;
int** r = &pnombre; // r pointe vers pnombre
cout << "Adresse de pnombre : " << r << endl;
cout << "Adresse de nombre : " << *r << endl;
cout << "Valeur de nombre : " << **r << endl;
return 0;
}Adresse de pnombre : 0x16d6c7390 Adresse de nombre : 0x16d6c7398 Valeur de nombre : 10
Explication :
rcontient l'adresse depnombre.*rdonne l'adresse denombre(contenu depnombre).**rdonne la valeur denombre(10).
Pourquoi utiliser des pointeurs ?
Une question qui vient généralement à l'esprit : "Pourquoi utiliser des pointeurs ?" Voici les principales raisons :
Allocation dynamique
Allouer de la mémoire pour de nouvelles variables de manière dynamique, pendant l'exécution du programme. Cela permet d'adapter l'utilisation de la mémoire en fonction des besoins.
Notation tableau
La notation pointeur est équivalente à la notation classique des tableaux. Cela permet de choisir la notation la mieux adaptée.
Paramètres de fonctions
Les pointeurs sont largement utilisés pour permettre à une fonction d'accéder à de grands blocs de données définis en dehors de la fonction.
Polymorphisme
Les pointeurs sont fondamentaux pour permettre au polymorphisme de fonctionner en programmation orientée objet.
Allocation dynamique avec new et delete
L'allocation dynamique permet d'allouer de la mémoire pendant l'exécution du programme, avec l'opérateur new. Cette mémoire est stockée sur le tas (heap) et ne peut être accédée que par des pointeurs.
L'opérateur new
int* pnombre = new int; // allocation dynamique d'un entiernew prend comme argument un type de données et renvoie un pointeur vers la mémoire allouée, si la mémoire disponible est suffisante.
L'opérateur delete
La mémoire allouée dynamiquement ne sera pas libérée automatiquement. Elle doit être libérée manuellement avec delete.
delete pnombre; // libère la mémoire allouéeSi vous oubliez de supprimer la mémoire allouée avec new, le programme aura des fuites de mémoire (memory leaks).
Pointeurs suspendus (dangling pointers)
Après l'exécution de delete, le pointeur contient toujours l'adresse de la mémoire qui a été libérée. Un tel pointeur est appelé pointeur suspendu (dangling pointer).
Le déréférencement d'un pointeur suspendu peut créer un désastre. La bonne pratique est de réinitialiser le pointeur après suppression :
delete pnombre; // libérer la mémoire
pnombre = nullptr; // réinitialiser le pointeurDésormais, pnombre ne pointe plus vers rien. L'utilisation d'un pointeur contenant nullptr pour accéder à des données mettra fin au programme, ce qui est préférable à un comportement imprévisible.
Manipulation de pointeurs
Considérez le code suivant :
int a = 42;
int* ptr = &a;
int** pptr = &ptr;Questions :
- Quelle est la valeur de
*ptr? - Quelle est la valeur de
*pptr? - Quelle est la valeur de
**pptr? - Si on exécute
*ptr = 100;, que valentaet**pptr?
*ptr: vaut 42 (valeur de a)*pptr: vaut l'adresse de a (contenu de ptr)**pptr: vaut 42 (valeur de a)- Après
*ptr = 100;:avaut maintenant 100**pptrvaut maintenant 100 (car c'est la même variable)
- Un pointeur est une variable qui stocke l'adresse d'une autre variable.
- Déclaration :
type* nom;(l'astérisque peut être collé au type ou au nom). - Initialisation : toujours initialiser un pointeur (avec une adresse valide ou
nullptr). - Opérateur & : obtient l'adresse d'une variable.
- Opérateur * (déréférencement) : accède à la valeur pointée.
- La taille d'un pointeur est constante pour une plate-forme (8 octets en 64 bits).
- Un pointeur peut pointer vers un autre pointeur (
type**). - Allocation dynamique :
newalloue,deletelibère. - Après
delete, réinitialiser le pointeur ànullptrpour éviter les pointeurs suspendus. - Les pointeurs sont essentiels pour l'allocation dynamique, les tableaux, les fonctions et le polymorphisme.
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.