Langage C - Pointeurs (Quelques explications)

Langage C - Pointeurs (Quelques explications) - C - Programmation

Marsh Posté le 09-10-2013 à 22:45:51    

Bonjour à tous,
 
J'ai demain une évaluation sur les pointeurs en langage C.  
J'ai bien bossé mon cours ainsi que mes TP mais tout n'est pas pour autant clair dans ma tête.
 
Ce chapitre reste toujours vague.Mes questions sont les suivantes :
 
- A quoi sert réellement un pointeur? Dans quel cas est-il vraiment utile?
 
- Je m'embrouille aussi avec l'utilisation de l'adresse (& ), du *, et de l'utilisation de la simple variable. Si quelqu'un pouvait me réexpliquer brièvement, ce serait for sympathique :)
 
Avant de poster ce message, j'ai biensur rechercher longuement sur internet, mais les explications qui sont données ne font que m'embrouiller.
Merci :)

Reply

Marsh Posté le 09-10-2013 à 22:45:51   

Reply

Marsh Posté le 10-10-2013 à 12:14:37    

J'vais tenter de tout expliquer :D
 
Un pointeur est un type de variable qui contient une adresse, et pas une valeur.
 
La déclaration :

Code :
  1. type* MonPointeur;


 
Pour la manipulation :  
 

Code :
  1. MonPointeur = MonAutrePointer; // MonPointeur contiendra la même adresse que MonAutrePointeur
  2. (*MonPointeur) = x; // Là, on utilise l'opérateur de déréférencement, c-a-d qu'on va non pas accéder à l'adresse, mais à la valeur continue à l'adresse de MonPointeur, et on l'assigne à une certaine valeur X
  3. MonPointer = &x; // Cette fois, on appelle l'opérateur & devant x, cet opérateur permet de récupérer l'adresse d'une variable. Dans ce cas, MonPointeur aura l'adresse de x.
  4. // Un truc plus " concret " :
  5. int i = 17;
  6. int* Pointer = &i; // On récupère l'adresse de la variable i
  7. (*Pointer) = 3; // Ici, on assigne la valeur contenue à l'adresse donnée par le Pointeur, comme celle-ci est précisément l'adresse de i, on va directement modifier i sans l'avoir touché explicitement
  8. printf("%i", i); // Affichera 3


 
Il faut penser à un truc :
Comme un pointeur est un type de variable comme un autre, il n'est pas initialisé par défaut, c'est à dire que de déclarer un pointeur comme ceci :  
 

Code :
  1. int* Ptr;


 
Aura pour effet de lui assigner une valeur "aléatoire" (Considère ça comme ça, c'est pas tout à fait aléatoire mais au final ça revient au même :o). Si jamais tu déférence ce pointeur comme ça :
 

Code :
  1. (*Ptr) = 10;
  2. int x = (*Ptr);


 
Tu auras environ 60% de chance pour que ton programme plante.  
Il faut toujours penser à initialiser un pointeur sur la valeur NULL, c'est un "standard" pour dire qu'un pointeur ne contient pas d'adresse.
 
 
Pour ce qui est de l'usage des pointeurs :  
 
- Ça évite la copie en appel de fonction (Quand tu fais passer des grosses structures bien lourde, vaut mieux faire passer par pointer)
- Ça peut aussi permettre de modifier des valeurs d'arguments :  

Code :
  1. void foo(int* p,int* x)
  2. {
  3. (*p) = 3;
  4. (*x) = 1;
  5. }
  6. int main(void)
  7. {
  8.    int X,Y;
  9.   foo(&X,&Y);
  10. // X = 3, Y = 1  
  11. }


 
- Ça permet de faire des tableaux dynamiques (mais faut avoir vu malloc / free d'abord).  
- C'est utilisé pour les listes chaînés
- Et pas mal d'autre trucs :D


---------------
Perhaps you don't deserve to breathe
Reply

Marsh Posté le 14-10-2013 à 08:30:22    

ludoztw a écrit :

Bonjour à tous,
 
J'ai demain une évaluation sur les pointeurs en langage C.  
J'ai bien bossé mon cours ainsi que mes TP mais tout n'est pas pour autant clair dans ma tête.
 
 
Ce chapitre reste toujours vague.Mes questions sont les suivantes :
 
- A quoi sert réellement un pointeur? Dans quel cas est-il vraiment utile?


Bonjour
Tant pis pour l'évaluation, j'arrive trop tard. Toutefois, ce n'est pas pour ça que tu doives laisser tomber.
 
Donc comme cela a été dit, un pointeur est une variable faite pour contenir une adresse, généralement l'adresse d'une autre variable
Exemple

Code :
  1. int entier=18;
  2. int *pt=&entier;     // La variable "pt" contient l'adresse de "entier"


 
A quel moment est-ce vraiment utile ?
Généralement dans 2 cas principaux
1) quand une fonction doit modifier une variable qui appartient à une autre fonction
Comme la plage d'adressage de l'ensemble des variables du programme est globale et unique ; et parce qu'une fonction ne reçoit qu'une copie de ce qu'on lui passe, si elle doit modifier une autre variable il faut alors lui passer l'adresse de cette autre variable.
Exemple

Code :
  1. void modif(int *local)
  2. {
  3.     *local=18;
  4. }
  5.  
  6. int main()
  7. {
  8.    int entier=5;
  9.    int *pt=&entier;
  10.  
  11.    modif(pt);
  12. }


 
2) Quand tu dois faire passer à une autre fonction un élément complexe (une structure, un tableau). Comme là encore la fonction recevra une copie de ce que tu lui passes, il vaut mieux lui passer l'adresse de l'élément (la copie se fera sur une simple adresse de 2 ou 4 octets plutôt que sur l'élément lui-même qui peut avoir plusieurs milliers d'octets)
Exemple

Code :
  1. int main()
  2. {
  3.    int tab[]={1, 2, 3, 3, 2, 1};
  4.  
  5.    affiche(tab, 6);        // Pourquoi "tab" et pas "&tab" puisqu'on passe une adresse ? Parce que le nom "tab" correspond à l'adresse de son premier élément. Ainsi tab <=> &tab[0]
  6. }
  7.  
  8. void affiche(int *tab, int nb)
  9. {
  10.     int i;
  11.     for (i=0; i < nb; i++)
  12.         printf("tab[%d]=%d\n", i, tab[i]);
  13.     // Comme la fonction ne sait pas où tab est sensé s'arrêter, on est obligé aussi de lui passer le nombre d'éléments du tableau
  14. }


 
Tous les autres cas où on utilise les adresses ne dérivent que de ces deux cas principaux.
 

ludoztw a écrit :

- Je m'embrouille aussi avec l'utilisation de l'adresse (& ), du *, et de l'utilisation de la simple variable. Si quelqu'un pouvait me réexpliquer brièvement, ce serait for sympathique :)


Pas de souci
Là encore ça se divise en 2 cas
1er cas: tu dois utiliser le pointeur (ou plus précisément le contenu de l'adresse). Dans ce cas, il te faut mettre autant d'étoiles que le pointeur en a
Exemple: int ***pt: Adresse de l'adresse de l'adresse d'un entier
Si tu veux obtenir cet entier, il te faut demander ***pt
Exemple: int var=***pt;
 
2° cas: tu veux remplir un pointeur. Dans ce cas, il te faut juste connaitre la nature de ce que tu as en main pour savoir comment le référencer pour que le pointeur soit correctement rempli
Prenons mon second exemple

Code :
  1. void modif(int *local)
  2. {
  3.     *local=18;
  4. }


La fonction "modif" est sensé recevoir l'adresse d'un entier ; et stocker cette adresse dans la variable "local".
 
Exemple n° 1: tu as, de ton coté, un entier => int i=100
Si tu veux passer ce "i" à la fonction, comme la fonction doit recevoir une adresse, il te faut lui passer l'adresse de cet entier

Code :
  1. int i=100;
  2. modif(&i);


 
Exemple n° 2: tu as maintenant l'adresse d'un entier => int *pt
Puisque pt est déjà une adresse, il te suffit alors de le passer directement à la fonction

Code :
  1. int i=100;
  2. int *pt=&i;
  3. modif(pt);


 
Exemple n° 3: tu as un tableau d'entiers => int tab[]={...,...,...}
Puisque la fonction n'est prévue que pour l'adresse d'un entier, et que chaque tab[x] correspond à un entier, il te suffit de passer l'adresse de cet entier (&tab[x]) à la fonction. Toutefois, puisque l'arithmétique des pointeurs dit que &tab[x] équivaut à tab + x, tu peux alors utiliser cette équivalence pour simplifier l'écriture (attention cela n'optimise que l'écriture et en rien le code car dans les deux cas il y a toujours une addition de faite)

Code :
  1. int tab[]={1, 2, 3, 4, 5, 6};
  2. int i;
  3. for (i=0; i < 6; i++)
  4. {
  5.     modif(&tab[i]);        // Ces deux écritures
  6.     modif(tab + i);        // Sont totalement équivalentes
  7. }


 
Remarque sur l'exemple n° 3: dans le cas où tu travailles souvent sur tab[x], tu sais que chaque appel à tab[x] se soldera par un décalage de x positions à partir de tab (donc une addition) ; tu peux pour optimiser les appels, mémoriser ce décalage dans un pointeur dédié puis utiliser ce pointeur à la place de tab[x]. Ainsi, le décalage (l'addition) ne se fera qu'une fois.  
Exemple
Code de base

Code :
  1. int tab[]={1, 2, 3, 4, 5, 6};
  2. int i;
  3. for (i=0; i < 6; i++)
  4.     printf("j'affiche 3 fois chaque élément i: %d - %d - %d\n", tab[i], tab[i], tab[i]);   // A chaque tab[i], le compilo se tape le décalage à partir de tab


 
Code optimisé

Code :
  1. int tab[]={1, 2, 3, 4, 5, 6};
  2. int *pt;
  3. int i;
  4. for (i=0; i < 6; i++)
  5. {
  6.     pt=&tab[i];    // Ou bien pt=tab + i
  7.     printf("j'affiche 3 fois chaque élément i: %d - %d - %d\n", *pt, *pt, *pt);  // Comme j'ai mémorisé l'adresse de tab[i] dans pt, je peux afficher le contenu "*pt" donc le contenu de tab[i]
  8. }


 
Et on peut en plus utiliser les possibilités de la virgule pour le raccourcir de cette façon

Code :
  1. for (i=0, pt=tab; i < 6; i++, pt++)
  2.     printf("j'affiche 3 fois chaque élément i: %d - %d - %d\n", *pt, *pt, *pt);  // Puisque pt s'incrémente avec i, alors *pt reste solidaire de tab[i]
  3. }


 
Cet exemple pourrait être le 3° cas d'utilisation des pointeurs mais comme c'est facultatif je préfère le laisser comme cas particulier qui dérive du cas n° 2


Message édité par Sve@r le 14-10-2013 à 08:54:31

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 14-10-2013 à 20:54:16    

Bonjour :)
 
Je n'avais pas laisser tomber ne t'en fait pas :) Grâce à toi j'ai mieux compris & eu une note correcte :)
 
Merci pour tes suppléments Sve@r ;)

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed