Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Officiels > Programmation > Apprenez à programmer en C ! > [Théorie] Techniques avancées > A l'assaut des pointeurs > Lecture du tutoriel

A l'assaut des pointeurs

Avatar
Auteur : M@teo21
Difficulté : Confirmé (4 / 5)
Note : 19 / 20 (125 votes)
Visualisations : 376 137


Plus d'informations Plus d'informations
Les pointeurs. Nous y voici enfin.
Autant vous prévenir tout de suite : ce chapitre ne sera pas une ballade de plaisance. Oh que non ;)

Nous sommes encore bien loin de la fin du cours de programmation, et pourtant je peux vous dire que c'est ce chapitre qui sera votre plus grand obstacle. C'est un véritable tournant que nous allons prendre dès maintenant en découvrant ce qu'on appelle les pointeurs en C.

A titre purement informatif (et ce n'est pas parce que j'aime bien raconter ma vie :p ), il faut savoir que, plus jeune, j'avais essayé d'apprendre la programmation en C / C++ en lisant des livres. Quel que soit le livre, c'était toujours la même chose : arrivé au chapitre des pointeurs, je ne comprenais plus. Je ne comprenais pas :



Aujourd'hui le temps a passé et je sais enfin de quoi il s'agit. Je sais aujourd'hui qu'on ne peut pas faire de programme en C sans se servir de pointeurs. Même dans "Plus ou Moins", vous en avez utilisé sans le savoir ^^

Je vais faire mon maximum pour vous expliquer de ce dont il s'agit, doucement et sûrement. N'allez pas trop vite, vous pourriez vous brûler les ailes en un temps record ;)
Restez attentifs et accrochez-vous : c'est maintenant ou jamais qu'il faut quadrupler d'attention. Ceux qui seront toujours en vie à la fin de ce chapitre auront gagné un pass pour la pluie de bonnes choses qui vous attend après :)


(Ca va je vous ai pas trop fait peur là ?) :lol:
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Un problème bien ennuyeux

Un des plus gros problèmes avec les pointeurs, en plus d'être assez difficiles à assimiler pour un débutant, c'est qu'on a du mal à comprendre à quoi ça peut bien servir.
Alors bien sûr, je pourrais vous dire : "Les pointeurs c'est totalement indispensable on s'en sert tout le temps, croyez-moi c'est super utile !", mais je vois de là vos mines sceptiques :p

Alors, afin d'éviter cela, je vais vous poser un problème que vous ne pourrez pas résoudre sans utiliser de pointeurs. Ce sera en quelque sorte le fil rouge du chapitre. Nous en reparlerons à la fin de ce chapitre et verrons quelle est la solution en utilisant ce que vous aurez appris.

Voici le problème : je veux écrire une fonction qui renvoie 2 valeurs.
"Impossible" me direz-vous !

En effet, on ne peut renvoyer qu'une valeur par fonction :

Code : C
1
2
3
4
long fonction()
{
    return machin;
}


Si on indique long, on renverra un nombre de type long (grâce à l'instruction return).

On peut aussi écrire une fonction qui ne renvoie aucune valeur avec le mot-clé void :

Code : C
1
2
3
4
void fonction()
{

}


Mais renvoyer 2 valeurs à la fois... c'est pas possible. On ne peut pas faire 2 "return" ni rien.

Alors supposons que je veuille écrire une fonction à laquelle on envoie un nombre de minutes, et celle-ci renverrait le nombre d'heures et minutes correspondantes :

Si on envoie 45, la fonction renvoie 0 heures et 45 minutes.
Si on envoie 60, la fonction renvoie 1 heure et 0 minutes.
Si on envoie 90, la fonction renvoie 1 heure et 30 minutes.

Allez, soyons fous, tentons le coup :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>

/* Je mets le prototype en haut. Comme c'est un tout
petit programme je ne le mets pas dans un .h, mais
en temps normal (dans un vrai programme) j'aurais placé
le prototype dans un fichier .h bien entendu ;o) */

void decoupeMinutes(long heures, long minutes);

int main(int argc, char *argv[])
{
    long heures = 0, minutes = 90;

    /* On a une variable minutes qui vaut 90.
    Après appel de la fonction, je veux que ma variable
    "heures" vaille 1 et que ma variable "minutes" vaille 30 */

    decoupeMinutes(heures, minutes);

    printf("%ld heures et %ld minutes", heures, minutes);

    return 0;
}

void decoupeMinutes(long heures, long minutes)
{
    heures = minutes / 60;  // 90 / 60 = 1
    minutes = minutes % 60; // 90 % 60 = 30 (rappelez-vous : modulo = reste de la division, "90 divisés par 60 font 1, et il reste 30")
}


Résultat :

Code : Console
0 heures et 90 minutes


Rhaa, zut zut zut et rezut, ça n'a pas marché. Remarquez, je n'avais guère d'espoir :p

Que s'est-il passé ?
En fait, quand vous "envoyez" une variable à une fonction, une copie de la variable est réalisée. Ainsi, la variable heures dans la fonction "decoupeMinutes" n'est pas la même que celle de la fonction main ! C'est juste une copie !

Votre fonction decoupeMinutes fait son job (d'ailleurs j'ose espérer que vous auriez su l'écrire cette fonction, y'a un bon exemple d'utilisation de la division et du modulo ;) ). A l'intérieur de la fonction decoupeMinutes, la variable heures et la variable minutes valent les bonnes valeurs : 1 et 30.
Mais ensuite, la fonction s'arrête lorsqu'on arrive à l'accolade fermante. Comme on l'a appris dans les chapitres précédents, toutes les variables créées dans une fonction sont détruites à la fin de cette fonction. Votre copie de heures et votre copie de minutes sont donc supprimées.
On retourne ensuite à la fonction main, dans laquelle vos variables heures et minutes valent toujours 0 et 90. Echec !

I : notez que, comme une fonction fait une copie des variables qu'on lui envoie, vous n'êtes pas du tout obligés d'appeler vos variables de la même façon que dans le main. Ainsi, vous pourriez très bien écrire :
Code : C
1
void decoupeMinutes(long h, long m)

h pour heures et m pour minutes.
Si vos variables ne s'appellent pas de la même façon que dans le main, ça ne pose donc aucun problème !


Bref, vous aurez beau retourner le problème dans tous les sens... Vous pouvez essayer de renvoyer une valeur avec la fonction (en utilisant un return et en mettant le type long à la fonction), mais vous n'arriveriez à renvoyer qu'une des 2 valeurs. Vous ne pouvez pas renvoyer les 2 valeurs à la fois.

Voilà le problème est posé ;)
Ce n'est qu'un exemple parmi tant d'autres qui va vous montrer l'utilité des pointeurs. J'ai choisi celui-ci parce qu'il me paraissait intéressant.

Allez, maintenant on peut attaquer le chapitre !

La mémoire, une question d'adresse

Rappel des faits



Petit flash-back.
Vous souvenez-vous du chapitre sur les variables ?

Quelle que soit la réponse, je vous recommande très vivement d'aller relire la première partie de ce chapitre, intitulée "Une affaire de mémoire". Bien entendu, je ne peux pas vous obliger à le faire, mais ne venez pas pleurnicher ensuite en me disant que vous ne comprenez rien ;)

Il y avait un schéma très important dans ce chapitre, je vous le ressors pour l'occasion. C'était le schéma de la mémoire :

Image utilisateur


C'est un peu comme ça qu'on peut représenter la mémoire vive (RAM) de votre ordinateur.
Il faut lire ce schéma ligne par ligne.

La première ligne représente la "cellule" du tout début de la mémoire vive. Chaque cellule a un numéro, c'est son adresse (hyper important le vocabulaire là !). La mémoire comporte un grand nombre d'adresses, commençant à l'adresse numéro 0 et se terminant à l'adresse numéro (insérez un très grand nombre ici).

A chaque adresse, on peut stocker un nombre. Un et UN SEUL nombre.

On ne peut donc pas stocker 2 nombres par adresse.

Votre mémoire n'est faite que pour stocker des nombres. Elle ne peut pas stocker de lettres, de phrases. Pour contourner ce problème, on a inventé une table qui fait la liaison entre les nombres et les lettres. Cette table dit par exemple : "le nombre 89 représente la lettre Y".
Mais bon, la gestion de texte en C n'est pas encore pour tout de suite. Nous en parlerons dans quelques chapitres. Avant de pouvoir comprendre ça, il faut d'abord comprendre ce que sont les pointeurs.



Adresse et valeur



Revenons-y justement car c'est le sujet.
Quand vous créez une variable "age" de type long par exemple, en tapant ça :

Code : C
1
long age = 10;


... votre programme demande au système d'exploitation (Windows par exemple) la permission d'utiliser un peu de mémoire. Le système d'exploitation répond en indiquant à quelle adresse en mémoire il vous laisse le droit d'inscrire votre nombre.
C'est d'ailleurs justement un des rôles principaux d'un système d'exploitation : on dit qu'il alloue de la mémoire aux programmes. C'est un peu lui le chef, il contrôle chaque programme et vérifie qu'il se sert de la mémoire à l'endroit qu'il lui a autorisé.

C'est d'ailleurs là la cause n°1 des plantages de programmes : si votre programme essaie d'accéder à une zone de la mémoire qui ne lui appartient pas, le système d'exploitation (abrégez "OS") refuse cela et coupe brutalement le programme en guise de punition ("C'est qui le chef ici ?").
L'utilisateur, lui, voit une jolie boîte de dialogue "Ce programme va être arrêté parce qu'il a effectué une opération non conforme" (quand c'est pas trop trop grave), ou, pire : un terrible écran-bleu-de-la-mort o_O
Mais généralement, si l'OS est bien codé votre ordinateur ne devrait pas complètement se bloquer à cause d'un simple "dépassement de mémoire". Enfin, moi j'dis ça, j'dis rien :p


Je m'égare. Où en étions-nous déjà ?
Ah oui, notre variable age. La valeur 10 a été inscrite quelque part en mémoire, disons par exemple à l'adresse n°4655.
Ce qu'il se passe (et c'est le rôle du compilateur) c'est que le mot "age" dans votre programme est remplacé par l'adresse 4655 à l'exécution. Cela fait que, à chaque fois que vous avez tapé le mot age dans votre code source, cela est remplacé par 4655, et votre ordinateur voit ainsi à quelle adresse il doit aller chercher en mémoire ! Du coup, l'ordinateur se rend en mémoire à l'adresse 4655 et répond fièrement : "Ca vaut 10 !".

On sait donc comment récupérer la valeur de la variable : il suffit tout bêtement de taper "age" dans son code source. Si on veut afficher l'âge, on peut utiliser la fonction printf :

Code : C
1
printf("La variable age vaut : %ld", age);


Résultat à l'écran :

Code : Console
La variable age vaut : 10


Rien de bien nouveau jusque-là.



Le scoop du jour



On sait afficher la valeur de la variable, mais saviez-vous que l'on peut aussi afficher l'adresse correspondante ? :D
... Ah oui non c'est vrai vous ne saviez pas ;)

Pour afficher l'adresse de la variable, on doit utiliser le symbole %p (le p du mot "pointeur") dans le printf. En outre, on doit envoyer à la fonction printf non pas la variable age, mais son adresse... Et pour faire cela, vous devez mettre le symbole & devant la variable age, comme je vous avais demandé de le faire pour les scanf il y a quelques temps sans vous expliquer pourquoi ;)

Tapez donc :

Code : C
1
printf("L'adresse de la variable age est : %p", &age);


Résultat :

Code : Console
L'adresse de la variable age est : 0023FF74


Ce que vous voyez là est l'adresse de la variable age au moment où j'ai lancé le programme sur mon ordinateur. Oui oui, c'est un nombre.
0023FF74 est un nombre, il est simplement écrit dans le système hexadécimal, au lieu du système décimal auquel nous avons l'habitude.

Si vous remplacez le %p par un %ld, vous devriez obtenir le nombre en système décimal (plus compréhensible pour nous pauvres humains). Toutefois, le %p a été fait spécialement pour afficher des adresses, donc je préfère en général l'utiliser à la place de %ld.


Sans rentrer dans les détails, juste pour que vous soyez pas trop perturbés, sachez que le fameux système décimal représente tous les nombres avec 10 chiffres : 0 1 2 3 4 5 6 7 8 9
En hexadécimal (un mode dans lequel l'ordinateur travaille souvent), les nombres sont représentés avec 16 chiffres : 0 1 2 3 4 5 6 7 8 9 A B C D E F (les lettres sont en fait des chiffres supplémentaires pour représenter les nombres).

Tout nombre en hexadécimal peut se convertir en décimal et inversement. Ainsi, A vaut 10, B vaut 11, C vaut 12... F vaut 15, 10 vaut 16, 11 vaut 17, 12 vaut 18 et ainsi de suite.
Si vous avez une calculatrice (au hasard la calculatrice de Windows en mode scientifique), vous pouvez convertir les nombres.

Image utilisateur
La calculatrice de Windows peut convertir les hexadécimaux


Vous devez d'abord vous assurer que vous êtes dans le mode scientifique : Affichage / Scientifique.
Ensuite, cliquez sur Hex (j'ai entouré en rouge sur ma capture d'écran). Tapez le nombre en hexadécimal que vous avez. Puis, cliquez sur Déc juste à côté pour transformer en décimal. Et voilà le travail ;)
Ca marche aussi en sens inverse bien sûr :)

Ainsi, j'ai pu voir que 0023FF74 correspondait en fait au nombre 2359156. Bon, on s'en fout un peu, ça ne changera pas notre vie, mais ça fait du bien de savoir comment ça marche non ? ^^

Si vous exécutez ce programme sur votre ordinateur, l'adresse sera très certainement différente. Tout dépend de la place que vous avez en mémoire, des programmes que vous avez lancés etc... Il est totalement impossible de prédire à quelle adresse la variable sera stockée chez vous ;)
Si vous lancez votre programme plusieurs fois d'affilée, il se peut que l'adresse soit identique, la mémoire n'ayant pas beaucoup changé entre temps. Si vous redémarrez votre ordinateur par contre, vous aurez sûrement une valeur différente.


Où je veux en venir avec tout ça ?
Eh bien en fait, je veux vous faire retenir la chose suivante toute bête :



Avec "age", l'ordinateur va lire la valeur de la variable en mémoire et vous renvoie cette valeur. Avec "&age", votre ordinateur vous dit en revanche à quelle adresse se trouve la variable.

Utiliser des pointeurs

Jusqu'ici, nous avons uniquement créé des variables faites pour contenir des nombres.
Maintenant, nous allons apprendre à créer des variables faites pour contenir des adresses : ce sont justement ce qu'on appelle des pointeurs.

Mais... Les adresses sont des nombres aussi non ? Ca revient à stocker des nombres encore et toujours !


C'est exact. Mais ces nombres auront une signification particulière : ils indiqueront l'adresse d'une autre variable en mémoire.


Créer un pointeur



Pour créer une variable de type pointeur, on doit rajouter le symbole * devant le nom de la variable.

Code : C
1
long *monPointeur;


Notez qu'on peut aussi écrire...
Code : C
1
long* pointeurSurAge;

Cela revient exactement au même.
Cependant, la première méthode est à préférer. En effet, si vous voulez déclarer plusieurs pointeurs sur la même ligne, vous serez obligés de mettre l'étoile devant le nom (première méthode) :
Code : C
1
long *pointeur1, *pointeur2, *pointeur3;



Comme je vous l'ai appris, il est important d'initialiser dès le début ses variables (en leur donnant la valeur 0 par exemple). C'est encore plus important de le faire avec les pointeurs !
Pour initialiser un pointeur (lui donner une valeur par défaut), on n'utilise généralement pas le nombre 0 mais le mot-clé NULL (les majuscules sont importantes attention) :

Code : C
1
long *monPointeur = NULL;


Là, vous avez un pointeur initialisé à NULL. Comme ça, vous saurez dans la suite de votre programme que votre pointeur ne contient aucune adresse.

Que se passe-t-il ? Ce code va réserver une case en mémoire comme si vous aviez créé une variable normale.
Cependant, et c'est ce qui change, la valeur du pointeur est faite pour contenir une adresse. L'adresse... d'une autre variable.

Pourquoi pas l'adresse de la variable age ? Vous savez maintenant comment indiquer l'adresse d'une variable au lieu de sa valeur (en utilisant le symbole &) alors zou ! Ca nous donne :

Code : C
1
2
long age = 10;
long *pointeurSurAge = &age;


La première ligne signifie : "Créer une variable de type long dont la valeur vaut 10"
La deuxième ligne signifie : "Créer une variable de type pointeur dont la valeur vaut l'adresse de la variable age".

Vous avez remarqué qu'il n'y a pas de type "pointeur" comme il y a un type "int", un type "double" ou encore un type "long".
On n'écrit donc pas :

Code : C
1
pointeur pointeurSurAge;


Au lieu de ça, on utilise le symbole *, mais on continue à écrire "long". Qu'est-ce que ça signifie ?
En fait, (accrochez-vous), on doit indiquer quel est le type de la variable dont le pointeur va contenir l'adresse. Comme notre pointeur pointeurSurAge va contenir l'adresse de la variable age (qui est de type long) alors mon pointeur doit être de type "long*" ! Si ma variable age avait été de type int, alors j'aurais dû écrire "int *monPointeur".

Vocabulaire : on dit que le pointeur pointeurSurAge pointe sur la variable age.

Un petit schéma de ce qu'il se passe en mémoire :

Image utilisateur


Dans ce schéma, la variable age a été placée à l'adresse 177450 (vous voyez d'ailleurs que sa valeur est 10), et le pointeur pointeurSurAge a été placé à l'adresse 3 (c'est tout à fait le fruit du hasard hein, j'invente :p ).

Lorsque mon pointeur est créé, le système d'exploitation réserve une case en mémoire comme il l'a fait pour age. La différence ici, c'est que la valeur de pointeurSurAge est un peu particulière. Regardez bien le schéma : c'est l'adresse de la variable age !

Ceci, mesdames et messieurs, est le secret absolu de tout programme écrit en langage C (et donc aussi en langage C++ ;) ). On y est, nous venons de rentrer dans le monde merveilleux des pointeurs !

Ouah, super. Et ça fait quoi ton truc ?


Ca ne transforme pas encore votre ordinateur en machine à café, certes.

Seulement maintenant, on a un pointeurSurAge qui contient l'adresse de la variable age.
Essayons de voir ce que contient le pointeur en faisant un printf dessus :

Code : C
1
2
3
4
long age = 10;
long *pointeurSurAge = &age;

printf("%ld", pointeurSurAge);


Code : Console
177450


Hum. En fait, cela n'est pas très étonnant. On demande la valeur de pointeurSurAge, et sa valeur c'est l'adresse de la variable age (177450).
Comment faire pour demander à avoir la valeur de la variable se trouvant à l'adresse indiquée dans pointeurSurAge ? Il faut mettre le symbole * devant le nom du pointeur :

Code : C
1
2
3
4
long age = 10;
long *pointeurSurAge = &age;

printf("%ld", *pointeurSurAge);


Code : Console
10


Hourra ! :D
On y est arrivés ! En mettant le symbole * devant le nom du pointeur, on accède à la valeur de la variable age :)

Si au contraire on avait mis le symbole & devant le nom du pointeur, on aurait eu l'adresse où se trouve le pointeur (ici, c'est 3)

Je ne vois pas ce qu'on y gagne. Après tout, sans pointeur on peut très bien afficher la valeur de la variable age !


Cette question (que vous devez inévitablement vous poser) me fait sourire pour 2 raisons :




A retenir absolument



Avant d'aller plus loin, s'il y avait une chose à retenir pour le moment ce serait cela en 4 points :



Contentez-vous de bien retenir ces 4 points. Faites des tests et vérifiez que ça marche.
Voici un schéma qui va vous permettre de bien situer qui désigne quoi :

Image utilisateur


Attention à ne pas confondre les différentes significations de l'étoile ! Lorsque vous déclarez un pointeur, l'étoile sert juste à indiquer qu'on veut créer un pointeur :
Code : C
1
long *pointeurSurAge;

En revanche, lorsque vous utilisez votre pointeur ensuite en écrivant :
Code : C
1
printf("%ld", *pointeurSurAge);

... cela ne signifie pas "Je veux créer un pointeur" mais : "Je veux la valeur de la variable sur laquelle pointe mon pointeurSurAge".


Tout cela est fon-da-men-tal. Il faut savoir cela par coeur, et surtout le comprendre. Même pas la peine de continuer ce chapitre si vous n'avez pas compris cela, je préfère être franc ;)

N'hésitez pas à lire et relire ce qu'on vient d'apprendre. Je ne peux pas vous en vouloir si vous n'avez pas compris du premier coup, et ce n'est pas une honte non plus d'ailleurs.
Pour info, avant que j'arrive à comprendre cela il a dû se passer une petite semaine (bon pas à temps plein je l'avoue :p ).
Et pour comprendre la plupart des subtilités des pointeurs, je crois qu'il m'a fallu bien 2 ou 3 mois au moins (pas à temps plein là non plus, rassurez-vous ;) )

Bref, si vous vous sentez un peu perdus, pensez à ces gens qui sont aujourd'hui des grands gourous de la programmation : aucun d'entre eux n'a compris tout le fonctionnement des pointeurs du premier coup.
Et si jamais cette personne existe, croyez-moi j'aimerais la rencontrer :D

Envoyer un pointeur à une fonction

Le gros intérêt des pointeurs (mais ce n'est pas le seul), c'est de les envoyer à des fonctions pour qu'ils modifient directement une variable en mémoire, et non une copie comme on l'a vu.
Comment ça marche ? Il y a en fait plusieurs façons de faire. Voici un premier exemple :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void triplePointeur(long *pointeurSurNombre);

int main(int argc, char *argv[])
{
    long nombre = 5;

    triplePointeur(&nombre); // On envoie l'adresse de nombre à la fonction
    printf("%ld", nombre); // On affiche la variable nombre. La fonction a directement modifié la valeur de la variable car elle connaissait son adresse

    return 0;
}

void triplePointeur(long *pointeurSurNombre)
{
    *pointeurSurNombre *= 3; // On multiplie par 3 la valeur de la variable nombre
}


Code : Console
15


La fonction triplePointeur prend un paramètre de type long* (c'est-à-dire un pointeur sur long). Voici ce qu'il se passe dans l'ordre, en partant du début du main :

  1. Une variable nombre est créée dans le main. On lui affecte la valeur 5. Ca, vous connaissez.
  2. On appelle la fonction triplePointeur. On lui envoie en paramètre l'adresse de notre variable nombre.
  3. La fonction triplePointeur reçoit cette adresse dans pointeurSurNombre. A l'intérieur de la fonction triplePointeur, on a donc un pointeur pointeurSurNombre qui contient l'adresse de la variable nombre.
  4. Maintenant qu'on a un pointeur sur nombre, on peut modifier directement la variable nombre en mémoire ! Il suffit d'utiliser *pointeurSurNombre pour désigner la variable nombre ! Pour l'exemple, on fait un simple test : on multiplie la variable nombre par 3.
  5. De retour dans la fonction main, notre nombre vaut maintenant 15 car la fonction triplePointeur a modifié directement la valeur de nombre.


Bien sûr, j'aurais pu faire un simple return comme on a appris à le faire dans le chapitre sur les fonctions. Mais l'intérêt là, c'est que de cette manière en utilisant des pointeurs on peut modifier la valeur de plusieurs variables en mémoire (on peut donc "renvoyer plusieurs valeurs"). On n'est plus limités à une seule valeur !

Quel est l'intérêt du coup d'utiliser un return dans une fonction si on peut se servir des pointeurs pour modifier des valeurs ?


Ca dépendra de vous et de votre programme. C'est à vous de décider. Il faut savoir que les return sont bel et bien toujours utilisés en C. Le plus souvent, on s'en sert pour renvoyer ce qu'on appelle un code d'erreur : la fonction renvoie 1 (vrai) si tout s'est bien passé, et 0 (faux) s'il y a eu une erreur pendant le déroulement de la fonction.
Mais bon, on aura le temps de voir comment gérer les erreurs en C plus tard ;)


Une autre façon d'envoyer un pointeur à une fonction



Dans le code source qu'on vient de voir, il n'y avait pas de pointeur dans la fonction main. Juste une variable nombre. Le seul pointeur qu'il y avait vraiment était dans la fonction tripleNombre (de type long*).

Il faut absolument que vous sachiez qu'il y a une autre façon d'écrire le code précédent, en ajoutant un pointeur dans la fonction main :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void triplePointeur(long *pointeurSurNombre);

int main(int argc, char *argv[])
{
    long nombre = 5;
    long *pointeur = &nombre; // pointeur prend l'adresse de nombre

    triplePointeur(pointeur); // On envoie pointeur (l'adresse de nombre) à la fonction
    printf("%ld", *pointeur); // On affiche la valeur de nombre, en tapant *pointeur

    return 0;
}

void triplePointeur(long *pointeurSurNombre)
{
    *pointeurSurNombre *= 3; // On multiplie par 3 la valeur de la variable nombre
}


Pour que vous ayez les 2 codes sources côte à côte, je vous mets celui de tout à l'heure ci-dessous. Comparez-les bien, il y a de subtiles différences et vous devez arriver à comprendre pourquoi il y a ces différences entre ces 2 codes sources.

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void triplePointeur(long *pointeurSurNombre);

int main(int argc, char *argv[])
{
    long nombre = 5;

    triplePointeur(&nombre); // On envoie l'adresse de nombre à la fonction
    printf("%ld", nombre); // On affiche la variable nombre. La fonction a directement modifié la valeur de la variable car elle connaissait son adresse

    return 0;
}

void triplePointeur(long *pointeurSurNombre)
{
    *pointeurSurNombre *= 3; // On multiplie par 3 la valeur de la variable nombre
}


Et le résultat dans les deux cas est le même :

Code : Console
15


Ce qui compte, c'est d'envoyer l'adresse de la variable nombre à la fonction. Or, pointeur vaut l'adresse de la variable nombre, donc c'est bon de ce côté ! On le fait juste d'une manière différente en créant un pointeur dans la fonction main.
Dans le printf (et c'est juste pour l'exercice), j'affiche le contenu de la variable nombre en tapant *pointeur. Notez que j'aurais pu à la place taper "nombre" : ça aurait été pareil car cela désigne la même chose dans la mémoire.

J'ai mis des semaines avant de comprendre que ces 2 codes faisaient effectivement la même chose, mais d'une manière différente. Si vous arrivez à comprendre ça, alors bravo, respect, bien joué, vous avez compris tout ce que je voulais vous enseigner sur les pointeurs :)


Comme je vous le disais tout à l'heure, dans le programme "Plus ou Moins" nous avons utilisé des pointeurs sans vraiment savoir. C'était en fait en appelant la fonction scanf. En effet, cette fonction a pour rôle de lire ce que l'utilisateur a rentré au clavier et de renvoyer cela.
Pour que la fonction puisse modifier directement le contenu de votre variable afin d'y mettre la valeur tapée au clavier, elle a besoin de l'adresse de la variable :
Code : C
1
2
long nombre = 0;
scanf("%ld", &nombre);

La fonction travaille avec un pointeur sur la variable nombre, et peut ainsi modifier directement le contenu de nombre.
Comme on vient de le voir, on pourrait créer un pointeur qu'on enverrait à la fonction scanf :
Code : C
1
2
3
long nombre = 0;
long *pointeur = &nombre;
scanf("%ld", pointeur);

Attention à ne pas mettre le symbole & devant pointeur dans la fonction scanf ! Ici, pointeur contient lui-même l'adresse de la variable nombre, pas besoin de mettre un & ! Si vous faisiez ça, vous enverriez l'adresse où se trouve le pointeur, et ça, excusez mon langage, mais on s'en fout complètement :D

Qui a dit : "Un problème bien ennuyeux" ?

Le chapitre est sur le point de s'achever, il est temps de retrouver notre fil rouge :)
Si vous avez compris ce chapitre, vous devriez être capables de résoudre le problème maintenant.

...

Quoi qu'est-ce que vous attendez ? Allez au boulot tas d'feignasses ! :p

...
...

Vous voulez la solution pour comparer ? La voici ! :D

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void decoupeMinutes(long* pointeurHeures, long* pointeurMinutes);

int main(int argc, char *argv[])
{
    long heures = 0, minutes = 90;

    // On envoie l'adresse de heures et minutes
    decoupeMinutes(&heures, &minutes);

    // Cette fois, les valeurs ont été modifiées !
    printf("%ld heures et %ld minutes", heures, minutes);

    return 0;
}

void decoupeMinutes(long* pointeurHeures, long* pointeurMinutes)
{
    /* Attention à ne pas oublier de mettre une étoile devant le nom
    des pointeurs ! Comme ça, vous pouvez modifier la valeur des variables,
    et non leur adresse ! Vous ne voudriez pas diviser des adresses
    n'est-ce pas ? ;o) */
    *pointeurHeures = *pointeurMinutes / 60;
    *pointeurMinutes = *pointeurMinutes % 60; 
}


Résultat :

Code : Console
1 heures et 30 minutes


Alors, c'est qui le plus fort ? :D

Est-ce que j'ai besoin de vous expliquer encore une fois comment ça marche ? En théorie, mes explications précédentes devraient suffire, je ne peux rien vous apprendre de nouveau.
Mais bon allez, pour la forme, et parce que c'est un chapitre important, je vais me répéter encore une fois. Il paraît qu'en rabâchant les mêmes choses ça finit par rentrer, si c'est le cas tant mieux pour vous ;)

Explications :

  1. Les variables heures et minutes sont créées dans le main.
  2. On envoie à la fonction decoupeMinutes l'adresse de heures et minutes.
  3. La fonction decoupeMinutes récupère ces adresses dans des pointeurs appelés pointeurHeures et pointeurMinutes. Notez que, là encore, le nom importe peu. J'aurais pu les appeler h et m, ou même encore heures et minutes (mais je ne veux pas que vous risquiez de confondre avec les variables heures et minutes du main, qui ne sont pas les mêmes ;) )
  4. La fonction decoupeMinutes modifie directement les valeurs des variables heures et minutes en mémoire car elle possède leurs adresses dans des pointeurs. La seule contrainte, un peu gênante je dois le reconnaître, c'est qu'il faut impérativement mettre une étoile devant le nom des pointeurs si on veut modifier la valeur de heures et minutes. Si on n'avait pas fait ça, on aurait modifié l'adresse contenue dans les pointeurs, ce qui aurait servi... à rien :p

De nombreux lecteurs m'ont fait remarquer qu'il était possible de résoudre le "problème" sans utiliser de pointeurs. Oui, bien sûr que je sais que c'est possible, mais il faut contourner les règles que nous nous sommes fixées : on peut utiliser des variables globales (bêrk), ou encore faire un printf dans la fonction (alors que c'est dans le main qu'on veut faire le printf !)
Bref, si vous aussi vous trouvez un moyen de résoudre le problème autrement, vous emballez pas. Ce n'était qu'un exemple un peu "théorique" pour vous montrer l'intérêt des pointeurs. Dans les prochains chapitres cet intérêt vous paraîtra de plus en plus évident ;)

Q.C.M.

Lequel de ces types de variables correspond à un pointeur ?
Si je tape &bidule, qu'est-ce que j'obtiens ?
Si je tape *machin, qu'est-ce que j'obtiens ?
Par quelle valeur doit-on initialiser un pointeur ?
Soit le code suivant :

Code : C
1
2
long nombre = 8;
long *pointeur = &nombre;


On suppose que nombre se trouve à l'adresse 5000, et pointeur à l'adresse 2500.
Si dans la suite de mon programme je demande à afficher *pointeur, quelle valeur cela affichera-t-il ?
Soit le code suivant, tordu je vous préviens :

Code : C
1
2
3
long nombre = 8;
long *p1 = &nombre;
long **p2 = &p1;


On a p1 qui est un pointeur sur nombre, et p2 qui est un pointeur sur... le pointeur p1.

p2 est de type "pointeur sur un pointeur sur long". Comme p2 pointe sur un long*, et qu'on veut définir un pointeur là-dessus, on doit rajouter une seconde *, ce qui explique pourquoi p2 est de type long**.

Si je demande à afficher "p2", qu'est-ce que j'obtiens ?

Statistiques de réponses au QCM


Comme le disait mon prof d'info : "Les pointeurs c'est bon, mangez-en"
Moi, les premiers temps, ça m'a surtout donné une sacrée migraine :lol:

Y'a pas de secret, pour bien comprendre les pointeurs, il faut pratiquer.
Là encore les exemples étaient simples, mais bientôt nous ferons des programmes plus complexes (ne serait-ce que dans les prochains chapitres) et il faudra savoir être patient.
Si vous êtes comme moi, vous allez faire planter misérablement vos programmes. Et pas qu'une fois. Je vous l'ai dit, j'ai mis des mois à acquérir ce que j'appelle "le réflexe des pointeurs". Pendant ce laps de temps, je mélangeais complètement *truc, &machin, truc, machin... J'avançais à petits pas, en essayant de modifier 2-3 caractères par-ci par-là pour essayer de faire marcher mon programme et, surtout, comprendre ce que je faisais.

Aujourd'hui, j'arrive enfin à ne pas me planter trop lamentablement quand je programme. En général, je ne fais plus d'erreurs de base, même si ça arrive à tout le monde hein, même aux meilleurs ;)
Quant à vous, je ne saurais trop vous conseiller de relire ce chapitre autant de fois que nécessaire et de faire des tests. Ne vous affolez pas si les premiers temps vous n'y arrivez pas bien, vous savez désormais que c'est un phénomène complètement normal ;)



Petit résumé avant de se quitter



Les pointeurs ont un gros défaut : ils vous font mélanger plein de choses. Je le sais : dès que j'ai voulu apprendre à me servir des pointeurs je confondais tout.
Je ne peux pas vraiment éviter ça pour vous : il va falloir que vous repassiez ce chapitre encore et encore pour ne plus confondre.

Ceci étant, un énième résumé avant de terminer le chapitre ne fera de mal à personne.
Voici donc comment je résumerais les choses très simplement :




L'intérêt des pointeurs n'est pas évident. Au final, on en revient à écrire *monPointeur au lieu de maVariable tout court. Quelle perte de temps hein ?
Eh bien non, au contraire les pointeurs sont totalement indispensables en C : on l'a vu dans un petit exemple de ce chapitre (comment modifier la valeur de plusieurs variables depuis une autre fonction), et on n'arrêtera pas de découvrir l'intérêt des pointeurs dans les prochains chapitres.

Soyez donc prêts avant de passer à la suite ;)
N'abandonnez pas ! Les pointeurs seront certainement votre plus gros obstacle dans votre apprentissage du C. Le reste sera plus facile je vous le promets :)
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 29/07/2005 à 00:29:36
Modifié : le 07/10/2008 à 09:35:25
Avancement : 100%
Licence : Copie non autorisée

186 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 253 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.2087s (0.1927s)