Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Les Pointeurs

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 27

Langage C

ENSEIGNANT ABBAS FOFANA NIVEAU LICENCE 1 IAM 2020-2021


CT : 16 TD : 4 TP : 20
2 CONTRÔLES(AVEC AU MOINS UN CONTRÔLE SUR TABLE)
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
 Les pointeurs : Les pointeurs représentent une des notions les plus délicates du langage C,
 Il est impossible de programmer en langage C sans les connaître et bien les comprendre.
Les pointeurs sont omniprésents, nous les avons d'ailleurs déjà utilisés sans le savoir. Un des
plus gros problèmes avec les pointeurs, en plus d'être assez délicats à assimiler pour des
débutants, c'est qu'on a du mal à comprendre à quoi ils peuvent bien servir. On va donc
poser un problème qu’on ne pourra résoudre sans utiliser de pointeurs. Voici le problème : je
veux écrire une fonction qui renvoie deux valeurs. « Impossible » me direz-vous ! En effet, on
ne peut renvoyer qu'une valeur par fonction : Si on indique int,
on renverra un nombre de type int(grâce à l'instruction
return).On peut aussi écrire une fonction qui ne renvoie
aucune valeur avec le mot-clé void: Mais renvoyer deux valeurs à la fois… c'est impossible. On
ne peut pas faire deux return.
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
 Supposons que je veuille écrire une fonction à laquelle on envoie un nombre de minutes.
Celle-ci renverrait le nombre d'heures et minutes correspondantes :
1. si on envoie 45, la fonction renvoie 0 heure et 45 minutes ;
2. si on envoie 60, la fonction renvoie 1 heure et 0 minutes ;
3. si on envoie 90, la fonction renvoie 1 heure et 30 minutes.
#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 */
void decoupeMinutes(int heures, int minutes);
int main(int argc, char *argv[])
{
int 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("%d heures et %d minutes", heures, minutes);
return 0;
}
void decoupeMinutes(int heures, int minutes)
{
heures = minutes / 60; // 90 / 60 = 1
minutes = minutes % 60; // 90 % 60 = 30
}
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
2.Les pointeurs
ça n'a pas marché. 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 simplement une copie !
Votre fonction decoupeMinutes fait son job. À l'intérieur de decoupeMinutes, les variables heures
et minute sont les bonnes valeurs : 1 et 30. Mais ensuite, la fonction s'arrête lorsqu'on arrive à
l'accolade fermante. toutes les variables créées dans une fonction sont détruites à la fin de cette
fonction. Vos copies de heures et de minutes sont donc supprimées. On retourne ensuite à la
fonction main, dans laquelle vos variables heures et minutes valent toujours 0 et 90. C'est un
échec ! 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 int à la
fonction), mais vous n'arriveriez à renvoyer qu'une des deux valeurs. Vous ne pouvez pas
renvoyer les deux valeurs à la fois. Voilà, le problème est posé. Comment les pointeurs vont-ils
nous permettre de le résoudre ?
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs
2.Les pointeurs

La mémoire, une question d'adresse

La mémoire, une question d'adresse


Rappel : flash-back. Vous souvenez-vous du chapitre sur les variables ?
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 (le vocabulaire est très important, retenez-le).
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). Le nombre d'adresses disponibles dépend en fait de la quantité
de mémoire dont dispose votre ordinateur. À chaque adresse, on peut
stocker un nombre. Un et UN SEUL nombre.
On ne peut pas stocker deux nombres par adresse.
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

La mémoire, une question d'adresse

Votre mémoire n'est faite que pour stocker des nombres. Elle ne peut stocker
ni lettres ni 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 ».
Adresse et valeur
Quand vous créez une variable age de type int par exemple, en tapant ça :
int 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 là 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
que ce dernier a l'autorisation de se servir de la mémoire à l'endroit où il le fait.
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs
2.Les pointeurs

La mémoire, une question d'adresse

Revenons à 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, il 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 : « La variable age 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:printf("La
variable age vaut : %d", age);
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
Le scoop du jour
On sait afficher la valeur de la variable, mais saviez-vous que l'on peut aussi
afficher l'adresse correspondante ? 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 onl le
fait pour les scanf.Tapez donc :
printf("L'adresse de la variable age est : %p", &age);
Ce que vous verrez là est l'adresse de la variable age au moment où vous avez le
programme sur votre ordinateur. Oui, oui, 0023FF74 est un nombre, il est
simplement écrit dans le système hexadécimal, au lieu du système décimal dont
nous avons l'habitude. Si vous remplacez %p par %d, vous obtiendrez un nombre
décimal .
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
Où veut on en venir avec tout ça ? Eh bien en fait, vous devez retenir ceci :
age: désigne la valeur de la variable ;
&age: désigne l'adresse de la variable.
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 ? Ça 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. int *monPointeur;
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

Créer un pointeur :

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, c'est-à-
dire lui donner une valeur par défaut, on n'utilise généralement pas le nombre 0 mais le mot-
clé NULL(veillez à l'écrire en majuscules) :
int *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 &),Ça nous donne : int age = 10; int *pointeurSurAge = &age;
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

Créer un pointeur :

int age = 10;


int *pointeurSurAge = &age;

La première ligne signifie : « Créer une variable de type int dont la valeur vaut 10 ». La seconde ligne
signifie : « Créer une variable de type pointeur dont la valeur vaut l'adresse de la variable age». La
seconde ligne fait donc deux choses à la fois. pour ne pas tout mélanger, sachez qu'on peut la
découper en deux temps :
int age = 10;
int *pointeurSurAge; // 1) signifie "Je crée un pointeur"
pointeurSurAge = &age; // 2) signifie "pointeurSurAge contient l'adresse de
la variable age"
La première ligne signifie : « Créer une variable de type int dont la valeur vaut 10 ». La seconde ligne
signifie : « Créer une variable de type pointeur dont la valeur vaut l'adresse de la variable age». La
seconde ligne fait donc deux choses à la fois. pour ne pas tout mélanger, sachez qu'on peut la
découper en deux temps :
Vous remarquerez qu'il n'y a pas de type « pointeur » comme il y a un type int et un type double. On
n'écrit donc pas : pointeur pointeurSurAge; Vocabulaire : on dit que le pointeur pointeurSurAge pointe
sur la variable age.
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

Créer un pointeur :

Il n’y a pas de type « pointeur » comme il y a un type int et un type double. On n'écrit donc pas :
pointeur pointeurSurAge;

Au lieu de ça, on utilise le symbole *, mais on continue à écrire int. Qu'est-ce que ça signifie ?
En fait, 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 int), alors mon pointeur doit être de type int* ! Si ma variable age avait été de type
double, alors j'aurais dû écrire double *monPointeur.
Vocabulaire : on dit que le pointeur pointeurSurAge pointe sur la variable age.
La fig. suivante résume ce qu'il s'est passé dans la mémoire.
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

Créer un pointeur :

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). 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, est le secret absolu de tout programme écrit en
langage C. Nous venons de rentrer dans le monde
merveilleux des pointeurs ! Si On demande la valeur
de pointeurSurAge, sa valeur c'est l'adresse de la
variable age(177450).
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs

En plaçant le symbole *devant le nom du pointeur,


on accède à la valeur de la variable age.
Si au contraire on avait utilisé le symbole & devant le
nom du pointeur, on aurait obtenu l'adresse à laquelle
se trouve le pointeur (ici, c'est 3). Qu'est-ce qu'on y gagne ?
On a simplement réussi à compliquer les choses ici. On n'avait pas besoin d'un pointeur pour
afficher la valeur de la variable age! Actuellement l'intérêt n'est pas évident, mais petit à petit,
tout au long de la suite, vous comprendrez que tout cela n'a pas été inventé par pur plaisir de
compliquer les choses. Si vous avez compris le principe, c'est l'essentiel. Les choses s'éclairciront
d'elles-mêmes par la suite.
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

maintenant, on a un pointeurSurAge qui contient l'adresse de la variable age. Essayons de voir ce


que contient le pointeur à l'aide d'un printf :
int age = 10;
int *pointeurSurAge = &age;
printf("%d", pointeurSurAge);
Code : Console
177450
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 placer le symbole * devant le nom du pointeur :
int age = 10;
int *pointeurSurAge = &age;
printf("%d", *pointeurSurAge);
En plaçant le symbole * devant le nom du pointeur, on accède à la valeur de la variable age.
Si au contraire on avait utilisé le symbole & devant le nom du pointeur, on aurait obtenu l'adresse à
laquelle se trouve le pointeur
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
 À retenir absolument
Voici ce qu'il faut avoir compris et ce qu'il faut retenir pour
la suite :
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
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 : int *pointeurSurAge;.
En revanche, lorsqu'ensuite vous utilisez votre pointeur en écrivant printf("%d",
*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 connaître cela par coeur et surtout le comprendre. N'hésitez pas à lire et relire ce
qu'on vient d'apprendre. si vous n'avez pas compris du premier coup ce n'est pas une honte, d'ailleurs. Il faut en général
quelques jours pour bien comprendre et souvent quelques mois pour en saisir toutes les subtilités. Si vous vous sentez un
peu perdus, pensez à ces gens qui sont aujourd'hui de 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, il faudra vraiment
nous la présenter.
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs

Envoyer un pointeur à une fonction


Le gros intérêt des pointeurs (mais ce n'est pas le seul) est qu'on peut les envoyer à des
fonctions pour qu’elles 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 :
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

La fonction triplePointeur prend un paramètre de type int* (c'est-à-dire un pointeur sur int). 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. Ça, 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. À 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, on 'aurait pu faire un simple return comme on a appris à le faire. 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 »). Nous ne sommes plus limités à une seule valeur !
Partie 2 : Techniques « avancées » du langage C : 2.Les pointeurs

Une autre façon d’envoyer un pointeur à une Ce qui compte, c'est d'envoyer l'adresse de la variable
fonction. Comparez bien ce code source avec nombre à la fonction. Or, pointeur vaut
le précédent. Il y a de subtiles différences et l'adresse de la variable nombre! On le fait seulement
pourtant le résultat est strictement le même : 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, on affiche le contenu de la variable
nombre en tapant *pointeur. Notez qu'à la place,
on aurait pu écrire nombre : le résultat aurait été
identique car *pointeur et nombre désignent la
même chose dans la mémoire. nous avons utilisé des
pointeurs sans vraiment le 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 entré au clavier
et de renvoyer le résultat. Pour que la fonction puisse
modifier directement le contenu de votre variable afin
d'y placer la valeur tapée au clavier, elle a besoin de
l'adresse de la variable :
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
int nombre = 0;
scanf("%d", &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
int nombre = 0;
int *pointeur = &nombre;
scanf("%d", 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 : or c’est de l'adresse de nombre dont on a besoin.
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs

Si vous avez compris, vous devriez être capables de résoudre le problème


de decoupeminute maintenant. Qu'est-ce que vous en dites ? Essayez !

1. si on envoie 45, la fonction renvoie 0 heure et 45 minutes ;


2. si on envoie 60, la fonction renvoie 1 heure et 0 minutes ;
3. si on envoie 90, la fonction renvoie 1 heure et 30 minutes.
Partie 2 : Techniques « avancées » du
langage C : 2.Les pointeurs
Rien ne devrait vous surprendre dans ce code source. Toutefois, comme on n'est jamais trop prudent, une
fois de plus voilà ce qui se passe dans ce code afin d'être certain que tout le monde suit bien. C'est un
chapitre important, vous devez faire beaucoup d'efforts pour comprendre :!
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à, le nom importe peu. On aurait pu les appeler
h et m, ou même encore heures et minutes. On ne l’a pas fait car il ne faut pas que vous confondiez
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 de minutes.
Si on n'avait pas fait ça, on aurait modifié l'adresse contenue dans les pointeurs, ce qui n'aurait servi… à
rien.
Partie 2 : Techniques « avancées » du langage
C : 2.Les pointeurs
En résumé
❖ Chaque variable est stockée à une adresse précise en mémoire.
❖ Si on place un symbole & devant un nom de variable, on obtient son adresse au lieu de sa
valeur (ex. : &age)..
❖ Si on place un symbole * devant un nom de pointeur, on obtient la valeur de la variable
stockée à l'adresse indiquée par le pointeur
❖ Les pointeurs constituent une notion essentielle du langage C, mais néanmoins un peu
complexe au début. Il faut prendre le temps de bien comprendre comment ils fonctionnent
car beaucoup d'autres notions sont basées dessus.
❖ Les pointeurs sont semblables aux variables, à ceci près qu'au lieu de stocker un nombre ils
stockent l'adresse à laquelle se trouve une variable en mémoire.

Vous aimerez peut-être aussi