Introduction aux pointeurs en C++ - Déclaration et interêts

19 Mar 2022 19 Mar 2022 3951 vues ESSADDOUKI Mostafa 9 min de lecture
Introduction et syntaxe de base
1 Introduction au langage C++ 2 Entrée-sortie en C++ - cin et cout 3 Inférence de type avec le mot-clé auto en C++ 4 Classe std::string et les chaînes de caractères en C++ 5 Les structures conditionnelles (if et switch) en C++ (C++17 et C++20) 6 Les boucles en C++ (C++17 et C++20) 7 La gestion des fichiers en C++
Pointeurs et fonctions
8 Introduction aux pointeurs en C++ - Déclaration et interêts 9 Les références en C++ - déclaration et interêts 10 Les tableaux en C++ - Déclaration et interêts 11 Introduction aux fonctions en C++ 12 Passer des arguments à une fonction en C++ 13 Déclarer un paramètre const en C++ 14 Les fonctions Lambda en C++ 15 Fonctions utiles (Mathématiques et caractères) en C++
Programmation OO
16 Classes et objets en C++ 17 Spécificateurs d'accès en C++ 18 Constructeurs et destructeur d'une classe en C++ 19 Fonctions membres en C++ 20 Membres statiques d'une classe en C++ 21 Fonctions en ligne en C++ - inline 22 Fonctions et classes amies en C++ - friend 23 Surcharge des fonctions en C++ 24 Surcharge des opérateurs en C++ 25 Héritage en C++ 26 La gestion d'exceptions en C++ : déclaration, utilisation et personnalisation 27 fonctions et classes templates en C++ 28 Les nouveautés C++20 pour améliorer les templates en C++
Structures de données
29 Introduction aux structures de données 30 Les structures en C++ et la différence avec les structures en C 31 Les listes chaînées en C++ 32 Les piles en C++ 33 File d'attente en C++ 34 Arbre binaire de recherche : définition et mise en oeuvre en C++
La bibliothèque standard (STL)
35 Introduction à la bibliothèque de Template Standard STL 36 Les itérateurs en C++ - définition, déclaration et exemples 37 La classe array en C++ (bibliothèque STL) <array> 38 La classe vector de la bibliothèque STL <vector> 39 La classe deque en C++ ( Bibliothèque STL) 40 La classe list en C++ (bibliothèque STL) <list> 41 La classe stack (Pile) en C++ (bibliothèque STL) <stack> 42 La classe queue (File d'attente) en C++ (bibliothèque STL) <queue> 43 La file d'attente prioritaire (classe priority_queue) - Bibliothèque STL 44 Les ensembles en C++ (Classe set <set> - Bibliothèque STL) 45 Les dictionnaires en C++ : Classe map (Bibliothèque STL) 46 Introduction aux algorithmes de la bibliothèque STL (programmation compétitive) 47 Tri et méthodes associées en C++ - Bibliothèque STL 48 Recherche dichotomique et méthodes associées en C++ - Bibliothèque STL 49 Appliquer un prédicat ou une fonction aux éléments d'une séquence en C++ - Bibliothèque STL 50 Recherche dans une séquence et méthodes associées en C++ - Bibliothèque STL

Les pointeurs en C++

Qu'est-ce qu'un pointeur ?

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.

Information importante

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

   
Déclaration de pointeurs C++
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 instruction

L'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.

Règle importante

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 rien

Taille 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;
}
Sortie (sur système 64 bits)
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 (&)

Opérateur &

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 {&note};    // pnombre pointe maintenant vers note

L'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 pointeur
Recommandation

Utilisez 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) (*)

Opérateur *

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;
}
Sortie (exemple)
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

Pointeur de 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 pnombre

Exemple 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;
}
Sortie (exemple)
Adresse de pnombre : 0x16d6c7390
Adresse de nombre : 0x16d6c7398
Valeur de nombre : 10

Explication :

  • r contient l'adresse de pnombre.
  • *r donne l'adresse de nombre (contenu de pnombre).
  • **r donne la valeur de nombre (10).

Pourquoi utiliser des pointeurs ?

Une question qui vient généralement à l'esprit : "Pourquoi utiliser des pointeurs ?" Voici les principales raisons :

Raison 1

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.

Raison 2

Notation tableau

La notation pointeur est équivalente à la notation classique des tableaux. Cela permet de choisir la notation la mieux adaptée.

Raison 3

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.

Raison 4

Polymorphisme

Les pointeurs sont fondamentaux pour permettre au polymorphisme de fonctionner en programmation orientée objet.

Allocation dynamique avec new et delete

Allocation dynamique

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 entier

new 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

Important

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ée

Si 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)

Pointeur suspendu

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 pointeur

Dé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.

 Exercice pratique

Manipulation de pointeurs

 Niveau : Débutant

Considérez le code suivant :

int a = 42;
int* ptr = &a;
int** pptr = &ptr;

Questions :

  1. Quelle est la valeur de *ptr ?
  2. Quelle est la valeur de *pptr ?
  3. Quelle est la valeur de **pptr ?
  4. Si on exécute *ptr = 100;, que valent a et **pptr ?
Points clés à retenir
  • 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 : new alloue, delete libère.
  • Après delete, réinitialiser le pointeur à nullptr pour é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.