On peut essayer de te faire comprendre les choses sous l'angle suivant:
Tu t'en doutes surement (à défaut de dire tu le sais
), tout ce que tu manipule se trouve toujours "en mémoire", le terme mémoire étant pris dans sa plus large acception.
La mémoire est "adressée", c'est à dire que, chaque "bloc mémoire" disponible peut être représenté par une "adresse" (en fait un simple numéro d'ordre).
Pour accéder à l'informaiton contenue dans un bloc mémoire, deux solution nous sont données:
La plus simple est de déclarer une variable à laquelle nous accéderons en utilisant son nom (son "identifiant").
La deuxième, un peu plus complexe (mais pas énormément) est d'y accéder en donnant... l'adresse mémoire, ou plus souvent, en indiquant qu'il faut aller "chatouiller" "ce qui se trouve à l'adresse untel".
Un pointeur n'est qu'une variable particulière dans le sens où l'information qu'elle contient est... l'adresse à laquelle nous trouverons l'information réellement recherchée.
Pour indiquer au compilateur que nous voulons utiliser un pointeur, nous plaçons un astérisque entre le type de la variable et son nom.
Ainsi, un pointeur sur un entier nommé b sera déclaré sous la forme de
Cela signifie que b est une variable qui contient... l'adresse à laquelle nous trouverons un entier.
Mais comme un pointeur peut n'être considéré en définitive que comme une variable, particulière certes, on peut envisager de vouloir travailler avec... l'adresse à laquelle se trouve cette variable et créer un... pointeur de pointeur (il "suffit" de rajouter un astérisque, ce qui donne Type ** PdePointeur), qui peut lui-même n'être considéré que comme une variable (particulière) sur laquelle on peut envisager de... Et ainsi de suite...
Je ne sais pas si la norme prévoit une limite dans le nombre, mais, il faut avouer que, au delà de deux ou trois (pointeur de pointeur ou pointeur de pointeur de pointeur), cela devient franchement difficile à gérer 
Puis, reste le problème de "comment manipuler ces bidules"...
Il y a plusieurs cas à envisager:
Vu qu'un pointeur représente "l'adresse à laquelle se trouve une variable", il semble cohérent d'être en mesure... de récupérer l'adresse de la variable 
Cela se fait en ajoutant l'esperluette "&' devant la variable, sous une forme proche de
1 2 3 4 5
|
int a = 10; // une variable "simple" de type entier
int * b; // un pointeur sur un entier;
b = &a; // assignons au pointeur l'adresse à laquelle se trouve la variable
// a |
De même, si un pointeur représente l'adresse d'une variable, il semble tout aussi cohérent d'être en mesure de... récupérer la variable.
Nous demandons d'accéder "à ce qui est pointé par notre pointeur" en ajoutant l'astérisque devant le nom du pointeur, sous une forme proche de
1 2 3 4 5 6
|
int a = 10; // une variable "simple" de type entier
int * b; // un pointeur sur un entier;
b = &a; // assignons au pointeur l'adresse à laquelle se trouve la variable
// a
*b = 12 // assignons 12 à "ce qui est pointé par b" (autrement dit, à a :P) |
En outre, il faut savoir que les pointeurs sont souvent utilisés dans un contexte de "gestion dynamique de la mémoire".
En effet, les variables souffrent d'un "problème" qu'il est bon de pouvoir contourner: elle sont "détruites" dés que l'on quitte la portée dans laquelle elles sont déclarées.
Or, il arrive régulièrement que l'on souhaite pouvoir être seul juge du moment où la mémoire est réellement assignée à une variable et du moment où cette mémoire est effectivement libérée "pour un usage ultérieur".
Nous pouvons donc "prendre la responsabilité de la gestion de la mémoire" en le demandant expressément lors de l'allocation de... la mémoire allouée à une variable. Cela se fait avec le mot clé new.
Il ne faut alors pas oublier de libérer expressément cette mémoire quand la variable devient inutile avec le mot clé delete.
Cela prend donc souvent une forme proche de
1 2 3 4 5
| Type * ptr = new Type; // allocation dynamique de la mémoire nécessaire
// pour contenir un objet Type
/*utilisation de notre objet */
delete ptr; // quand l'objet devient inutile, on libère la mémoire qui lui est
// allouée |
Cela nous permet, en outre, de demander d'allouer directement la mémoire nécessaire pour représenter un nombre (finalement quelconque, tant qu'on dispose de suffisamment de mémoire) d'objets de manière contigüe en mémoire.
Il suffit d'indiquer le nombre d'objet que l'on souhaite entre crochets, sous la forme de
Type * ptr = new Type[nombre_d_objets_souhaité] ;
Dans ce cas, l'adresse contenue par ptr sera l'adresse à laquelle on trouve le premier élément des (nombre_d_objets_souhaité)
La seule différence est qu'il faudra alors prévenir que l'on souhaite libérer la mémoire allouée à un tableau d'objets contigus en mémoire en ajoutant les crochets au mot clé delete:
1 2 3 4 5 6
| Type * ptr = new Type[nombre_d_objets_souhaité] ;
/* utilisation de ptr */
/* et libération de la mémoire qui y est allouée quand on n'en a plus
* besoin
*/
delete[] ptr; |
Il reste, pour finir, un dernier point à aborder au sujet des pointeurs: l'arithmétique des pointeurs.
En effet, on peut déclarer un pointeur sur... strictement tout et n'importe quoi.
Et je ne te ferai pas l'affront de sous entendre que tu n'a pas conscience que la mémoire nécessaire pour représenter un objet dépend directement... des informations qu'il contient.
Hé bien, l'avantage, c'est que, si tu incrémente un pointeur (ptr++) ou que tu le décrémente (ptr--), tu n'as pas à t'inquiéter de la taille réellement utilisée en mémoire par l'objet pointé: ton pointeur prendra comme valeur l'adresse du prochain (ou du précédent, en cas de décrémentation) objet du type en cours de traitement.
C'est à dire que si Type prend - par exemple - 140 bytes, et que tu as un pointeur de sur Type, le fait de l'incrémenter fera que l'adresse contenue dans le pointeur sera... 140 bytes supérieure à l'adresse d'origine.
Et, fatalement, si tu le décrémente, l'adresse contenue dans le pointeur sera... 140 bytes inférieure à l'adresse d'origine.
Cela signifie qu'avec un type (quelconque), tu pourrais envisager un code proche de
1 2 3
|
Type * ptr = new Type[10];
ptr++; // passe à l'élément de type Type |
Sauf que tu perd alors l'adresse du premier élément, et que c'est sur cette adresse là qu'il s'agit d'invoquer delete[]...
C'est la raison pour laquelle, si tu dois modifier l'adresse représentée par un pointeur, il vaut mieux... utiliser un autre pointeur pour ne pas perdre l'adresse d'origine.
Partager