Bon, parce que j'ai encore jamais compris ce que c'était et que j'ai envie une bonne fois pour toutes de me débarasser de mon ignorance à ce sujet, je m'y mets sérieusement et méthodiquement. Qu'est-ce qu'un pointeur, à quoi ça sert ? Des questions que je vais tenter d'aborder. Non, des questions que je vais aborder.
Un pointeur est une variable qui contient une adresse mémoire.
p = &c;
Affecte l'adresse de c à la variable p, on dit que p pointe sur c. L'opérateur & s'applique uniquement aux données en mémoire : les variables et les éléments de tableaux. Il ne peut pas s'appliquer à des expressions ou des constantes.
Il s'agit de l'opérateur dit d'indirection ou de déréférence. Il sert à donner accès à la donnée pointée par le pointeur à qui il est appliqué. Voyons un exemple simple :
int x = 1, y = 2, z[10]; int * p; /* p est un pointeur sur un int */ p = &x; /* p pointe vers x */ y = *p; /* y vaut désormais 1 (x) */ *p = 0; /* x vaut 0 */ p = &z[0] /* p pointe maintenant sur z[0] */
Pour résumer, si p pointe vers x, on peut écrire *p partout où on pourrait écrire x.
*p = *p + 10;
ajoute 10 à *p. Les opérateurs & et * sont beaucoup plus forts que les opérateurs arithmétiques, donc si on écrit y = *p + 1;, on déréférence le pointeur p, on ajoute 1 et on met le résultat dans y. Par contre dans le cas de (*p)++, les parenthèses sont obligatoires. En effet les opérateurs tels que * et ++ sont évalués de droite à gauche.
Enfin, tout pointeur est une variable (voir nature de l'élément) donc on peut faire ceci :
p = q;
où p et q sont des pointeurs. Cette instruction a pour effet de copier le contenu de q dans p, donc de faire pointer p vers la même donnée que q.
Comme vous avez pu le remarquer, on ne peut pas changer la valeur des arguments directement dans une fonction. Le code suivant l'illustre bien.
#include <stdio.h>
void echanger(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main(void)
{
int a, b;
a = 2;
b = 3;
printf("a : %d et b : %d\n", a, b);
echanger(a, b);
printf("a : %d et b : %d\n", a, b);
return 0;
}Ce qui produira :
a : 2 et b : 3 a : 2 et b : 3
Les pointeurs permettent de passer des arguments aux fonctions par référence plutot que par copie. La méthode est la suivante : dans la fonction appelante, il faut passer en argument de echanger des pointeurs sur les valeurs à modifier : echanger(&a, &b); . L'opérateur & donne l'adresse d'une variable, &a est donc un pointeur sur a. Il faut donc modifier la fonction echanger comme il suit.
void echanger(int * a, int * b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
et l'appel de la fonction par echanger(&a, &b);.
Voici un example complet qui illustre ce qui précède.
#include <stdio.h>
void echanger(int a, int b) /* Erreur */
{
int temp;
temp = a;
a = b;
b = temp;
}
void echanger2(int * a, int * b) /* OK */
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int a, b;
a = 2;
b = 3;
printf("a : %d et b : %d\n", a, b);
printf("On essaye sans pointeur.\n");
echanger(a, b);
printf("a : %d et b : %d\n", a, b);
printf("Cette fois on essaye avec les pointeurs. \n");
echanger2(&a, &b);
printf("a : %d et b : %d\n", a, b);
return 0;
}
Remarque : Un pointeur qui n'a pas été initialisé n'a pas la valeur NULL (ni 0). Le test if (p == NULL) permet de savoir si un pointeur ne contient pas d'adresse mémoire (ce qui est rarement le cas).
Les pointeurs et les tableaux sont très liés. Je ne vais pas m'étendre sur le sujet, mais analysez le code suivant.
#include <stdio.h>
int main (void)
{
int a[100];
int * p;
a[3] = 3;
p = a;
printf("%d et %d \n", a[3], *(p+3)); // Affiche "3 et 3"
// ou encore
printf("%d \n", p[3]); // affiche 3
return 0;
}
En fait un tableau a n'est rien d'autre qu'une adresse vers le premier élément du tableau : a[0]. Autrement dit p = &a[0]; et p = a; sont équivalents. Encore plus fort, lorsque le compilateur voit a[i], il le convertit immédiatement en *(a+i) où type est le type de a ; ces deux formes sont équivalentes. Mais attention (WARNINGS) un tableau n'est pas une variable alors qu'un pointeur si : a++ ou a = p sont incorrectes.
Bon, oui, je ne vais pas y echapper mais attention ici : WARNINGS. Le programmeur est apparement tout seul face à son compilateur pour lui dire quoi faire de la mémoire. Pas de ramasse-miettes ou autre gadget qui pourrait faire le sale boulot. Je me permet d'aborder le sujet ici car les pointeurs et la gestion de la mémoire ont l'air étroitement liés. Je me pencherai certainement plus en avant (ce qui alimentera peut-être la collection de pages) sur le sujet de la gestion mémoire en C. Pour le moment, voyons les quatre fameuses fonctions : malloc, realloc, calloc, et free.
C'est la fonction la plus simple. Elle prend en argument la taille que l'on veut allouer : une variable de type size_t qui est habituellement retournée par la fonction sizeof. malloc renvoie un pointeur vers la zone mémoire allouée ou NULL si la demande a échoué.
#include <stdlib.h> void * malloc(size_t taille);
Utilisation avec les pointeurs
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
int i = 3;
int * p;
printf("\n\n");
printf("Avant l'initialisation de p :\n");
printf("la valeur de i : %d\n", i);
printf("l'adresse de i : %p\n", &i);
printf("l'adresse de p : %p\n", p);
/* On initialise p et on attribue un size */
p = malloc(sizeof(int));
printf("On a initialisé p avec malloc\n");
printf("l'adresse de p : %p\n", p); // différente de i
*p = i;
printf("On a mis i dans *p\n");
printf("l'adresse de p : %p\n", p); // toujours différente de i
printf("la valeur de p : %d\n", *p);
return 0;
}
qui donne :
Avant l'initialisation de p : la valeur de i : 3 l'adresse de i : 0xbff0f030 l'adresse de p : 0xb7f10cc0 On a initialisé p avec malloc l'adresse de p : 0x804a008 On a mis i dans *p l'adresse de p : 0x804a008 la valeur de p : 3
Ce qu'il faut comprendre ici c'est que *p = i est complètement différent de p = &i. En effet dans le premier cas on met dans la donnée pointée par p le contenu de la variable i, et dans le second cas on met dans la variable (pointeur) p l'adresse de i. Dans ce dernier cas p pointerait ensuite vers i.
Pour en revenir à malloc, il n'est pas necessaire de caster la fonction malloc avec (int *) ou un autre type. En effet, le type void* est implicitement casté en C. Ceci n'est pas valable en C++.