Introduction : Pourquoi ce sujet rend-il même les développeurs C++ expérimentés perplexes ?
\n\nSi vous avez déjà ouvert du code C++ et croisé une ligne comme const int * const data, vous avez probablement eu envie de fermer l'éditeur et d'aller boire un café. Cette syntaxe étrange avec const placé de chaque côté de l'astérisque est un véritable casse-tête. La question « What is the difference between const int*, const int * const, and int * const ? » cumule des centaines de milliers de vues sur Stack Overflow. Et ce n'est pas un hasard.
Comprendre ces constructions n'est pas un simple caprice de perfectionniste. C'est la clé pour écrire un code sûr, auto-documenté et, surtout, pour réussir vos entretiens techniques. Aujourd'hui, nous allons démêler cette confusion une bonne fois pour toutes, en utilisant une règle simple qui fonctionne à tous les coups.
\n\nLes trois piliers : que comparons-nous ?
\n\nEn C++, les pointeurs et la constance peuvent se combiner de quatre manières (dont trois sont les plus courantes). Voici les voici :
\n\n- \n
- Cas 1 :
const int* ptr(ouint const* ptr, c'est identique). \n - Cas 2 :
int * const ptr. \n - Cas 3 :
const int * const ptr(la combinaison des deux premiers). \n
L'idée clé : const s'applique toujours à ce qui se trouve à sa gauche. S'il n'y a rien à gauche, il s'applique à ce qui est à droite. Cela semble simple, mais en pratique, cette règle est une bouée de sauvetage.
\n\nCas 1 : const int* ptr (Pointeur vers un entier constant)
\n\nLisez de droite à gauche : « ptr est un pointeur vers un entier qui est constant ».
\n\nQu'est-ce que cela signifie concrètement ?
\n- \n
- Vous ne pouvez pas modifier la valeur de la variable pointée par ptr via ce pointeur. \n
- Vous pouvez modifier le pointeur lui-même (le faire pointer vers une autre variable). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n const int* ptr = &a; // ptr est un pointeur vers un const int\n\n // *ptr = 15; // ERREUR de compilation ! Impossible de modifier via const int*\n std::cout << "*ptr = " << *ptr << std::endl; // Affiche 10\n\n ptr = &b; // OK ! On change le pointeur lui-même\n std::cout << "*ptr = " << *ptr << std::endl; // Affiche 20\n\n return 0;\n}\n\nCe type est souvent utilisé pour passer des données à une fonction, lorsque vous voulez garantir qu'elle ne modifiera pas les données d'origine. Par exemple, void print(const int* data) est une promesse de « lecture seule ».
Cas 2 : int * const ptr (Pointeur constant vers un entier)
\n\nLisez de droite à gauche : « ptr est un pointeur constant vers un entier ».
\n\nIci, c'est l'inverse :
\n- \n
- Vous pouvez modifier la valeur de la variable pointée par ptr. \n
- Vous ne pouvez pas modifier le pointeur lui-même (il doit toujours pointer vers la même zone mémoire). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n int * const ptr = &a; // ptr est un pointeur constant vers un int\n\n *ptr = 15; // OK ! On modifie la valeur via le pointeur\n std::cout << "*ptr = " << *ptr << std::endl; // Affiche 15\n\n // ptr = &b; // ERREUR de compilation ! Impossible de modifier le pointeur\n\n return 0;\n}\n\nImaginez que int * const est un pointeur « verrouillé ». Il est souvent utilisé pour manipuler des registres matériels ou dans les cas où un pointeur vers un tampon ne doit pas « s'échapper » vers un autre emplacement.
Cas 3 : const int * const ptr (Pointeur constant vers un entier constant)
\n\nLisez de droite à gauche : « ptr est un pointeur constant vers un entier qui est constant ».