PortailAccueilFAQRechercherCalendrierS'enregistrerMembresGroupesConnexion

Partagez | 
 

 Chapitre4.Les pointeurs et références (1)

Voir le sujet précédent Voir le sujet suivant Aller en bas 
AuteurMessage
didouqen
Admin
avatar

Masculin
Nombre de messages : 40
Age : 32
Localisation : Marrakech
Date d'inscription : 07/09/2006

MessageSujet: Chapitre4.Les pointeurs et références (1)   Mar 25 Sep - 7:18

Les pointeurs sont des variables très utilisées en C et en C++. Ils doivent être considérés comme des variables, il n'y a rien de sorcier derrière les pointeurs. Cependant, les pointeurs ont un domaine d'application très vaste.
Les références sont des identificateurs synonymes d'autres identificateurs, qui permettent de manipuler certaines notions introduites avec les pointeurs plus souplement. Elles n'existent qu'en C++.
4.1. Notion d'adresse
Tout objet manipulé par l'ordinateur est stocké dans sa mémoire. On peut considérer que cette mémoire est constituée d'une série de « cases », cases dans lesquelles sont stockées les valeurs des variables ou les instructions du programme. Pour pouvoir accéder à un objet (la valeur d'une variable ou les instructions à exécuter par exemple), c'est-à-dire au contenu de la case mémoire dans laquelle cet objet est enregistré, il faut connaître le numéro de cette case. Autrement dit, il faut connaître l'emplacement en mémoire de l'objet à manipuler. Cet emplacement est appelé l'adresse de la case mémoire, et par extension, l'adresse de la variable ou l'adresse de la fonction stockée dans cette case et celles qui la suivent.
Toute case mémoire a une adresse unique. Lorsqu'on utilise une variable ou une fonction, le compilateur manipule l'adresse de cette dernière pour y accéder. C'est lui qui connaît cette adresse, le programmeur n'a pas à s'en soucier.
4.2. Notion de pointeur
Une adresse est une valeur. On peut donc stocker cette valeur dans une variable. Les pointeurs sont justement des variables qui contiennent l'adresse d'autres objets, par exemple l'adresse d'une autre variable. On dit que le pointeur pointe sur la variable pointée. Ici, pointer signifie « faire référence à ». Les adresses sont généralement des valeurs constantes, car en général un objet ne se déplace pas en mémoire. Toutefois, la valeur d'un pointeur peut changer. Cela ne signifie pas que la variable pointée est déplacée en mémoire, mais plutôt que le pointeur pointe sur autre chose.
Afin de savoir ce qui est pointé par un pointeur, les pointeurs disposent d'un type. Ce type est construit à partir du type de l'objet pointé. Cela permet au compilateur de vérifier que les manipulations réalisées en mémoire par l'intermédiaire du pointeur sont valides. Le type des pointeur se lit « pointeur de ... », où les points de suspension représentent le nom du type de l'objet pointé.
Les pointeurs se déclarent en donnant le type de l'objet qu'ils devront pointer, suivi de leur identificateur précédé d'une étoile :
int *pi; // pi est un pointeur d'entier.
Note : Si plusieurs pointeurs doivent être déclarés, l'étoile doit être répétée :
int *pi1, *pi2, j, *pi3;
Ici, pi1, pi2 et pi3 sont des pointeurs d'entiers et j est un entier.
Figure 4-1. Notion de pointeur et d'adresse

Il est possible de faire un pointeur sur une structure dans une structure en indiquant le nom de la structure comme type du pointeur :
typedef struct nom
{
struct nom *pointeur; /* Pointeur sur une structure "nom". */
...
} MaStructure;
Ce type de construction permet de créer des listes de structures, dans lesquelles chaque structure contient l'adresse de la structure suivante dans la liste. Nous verrons plus loin un exemple d'utilisation de ce genre de structure.
Il est également possible de créer des pointeurs sur des fonctions, et d'utiliser ces pointeurs pour paramétrer un algorithme, dont le comportement dépendra des fonctions ainsi pointées. Nous détaillerons plus loin ce type d'utilisation des pointeurs.
4.3. Déréférencement, indirection
Un pointeur ne servirait strictement à rien s'il n'y avait pas de possibilité d'accéder à l'adresse d'une variable ou d'une fonction (on ne pourrait alors pas l'initialiser) ou s'il n'y avait pas moyen d'accéder à l'objet référencé par le pointeur (la variable pointée ne pourrait pas être manipulée ou la fonction pointée ne pourrait pas être appelée).
Ces deux opérations sont respectivement appelées indirection et déréférencement. Il existe deux opérateurs permettant de récupérer l'adresse d'un objet et d'accéder à l'objet pointé. Ces opérateurs sont respectivement & et *.
Il est très important de s'assurer que les pointeurs que l'on manipule sont tous initialisés (c'est-à-dire contiennent l'adresse d'un objet valide, et pas n'importe quoi). En effet, accéder à un pointeur non initialisé revient à lire ou, plus grave encore, à écrire dans la mémoire à un endroit complètement aléatoire (selon la valeur initiale du pointeur lors de sa création). En général, on initialise les pointeurs dès leur création, ou, s'ils doivent être utilisés ultérieurement, on les initialise avec le pointeur nul. Cela permettra de faire ultérieurement des tests sur la validité du pointeur ou au moins de détecter les erreurs. En effet, l'utilisation d'un pointeur initialisé avec le pointeur nul génère souvent une faute de protection du programme, que tout bon débogueur est capable de détecter. Le pointeur nul se note NULL.
Note : NULL est une macro définie dans le fichier d'en-tête stdlib.h. En C, elle représente la valeur d'une adresse invalide. Malheureusement, cette valeur peut ne pas être égale à l'adresse 0 (certains compilateurs utilisent la valeur -1 pour NULL par exemple). C'est pour cela que cette macro a été définie, afin de représenter, selon le compilateur, la bonne valeur. Voir le Chapitre 5 pour plus de détails sur les macros et sur les fichiers d'en-tête.
La norme du C++ fixe la valeur nulle des pointeurs à 0. Par conséquent, les compilateurs C/C++ qui définissent NULL comme étant égal à -1 posent un problème de portabilité certain, puisque un programme C qui utilise NULL n'est plus valide en C++. Par ailleurs, un morceau de programme C++ compilable en C qui utiliserait la valeur 0 ne serait pas correct en C.
Il faut donc faire un choix : soit utiliser NULL en C et 0 en C++, soit utiliser NULL partout, quitte à redéfinir la macro NULL pour les programmes C++ (solution qui me semble plus pratique).
Exemple 4-1. Déclaration de pointeurs
int i=0; /* Déclare une variable entière. */
int *pi; /* Déclare un pointeur sur un entier. */
pi=&i; /* Initialise le pointeur avec l'adresse de cette
variable. */
*pi = *pi+1; /* Effectue un calcul sur la variable pointée par pi,
c'est-à-dire sur i lui-même, puisque pi contient
l'adresse de i. */

/* ہ ce stade, i ne vaut plus 0, mais 1. */
Il est à présent facile de comprendre pourquoi il faut répéter l'étoile dans la déclaration de plusieurs pointeurs :
int *p1, *p2, *p3;
signifie syntaxiquement : p1, p2 et p3 sont des pointeurs d'entiers, mais aussi *p1, *p2 et *p3 sont des entiers.
Si l'on avait écrit :
int *p1, p2, p3;
seul p1 serait un pointeur d'entier. p2 et p3 seraient des entiers.
L'accès aux champs d'une structure par le pointeur sur cette structure se fera avec l'opérateur '->', qui remplace '(*).'.
Exemple 4-2. Utilisation de pointeurs de structures
struct Client
{
int Age;
};

Client structure1;
Client *pstr = &structure1;
pstr->Age = 35; /* On aurait pu écrire (*pstr).Age=35; */
4.4. Notion de référence
En plus des pointeurs, le C++ permet de créer des références. Les références sont des synonymes d'identificateurs. Elles permettent de manipuler une variable sous un autre nom que celui sous laquelle cette dernière a été déclarée.
Note : Les références n'existent qu'en C++. Le C ne permet pas de créer des références.
Par exemple, si « id » est le nom d'une variable, il est possible de créer une référence « ref » de cette variable. Les deux identificateurs id et ref représentent alors la même variable, et celle-ci peut être accédée et modifiée à l'aide de ces deux identificateurs indistinctement.
Toute référence doit se référer à un identificateur : il est donc impossible de déclarer une référence sans l'initialiser. De plus, la déclaration d'une référence ne crée pas un nouvel objet comme c'est le cas pour la déclaration d'une variable par exemple. En effet, les références se rapportent à des identificateurs déjà existants.
La syntaxe de la déclaration d'une référence est la suivante :
type &référence = identificateur;
Après cette déclaration, référence peut être utilisé partout où identificateur peut l'être. Ce sont des synonymes.
Exemple 4-3. Déclaration de références
int i=0;
int &ri=i; // Référence sur la variable i.
ri=ri+i; // Double la valeur de i (et de ri).
Il est possible de faire des références sur des valeurs numériques. Dans ce cas, les références doivent être déclarées comme étant constantes, puisqu'une valeur est une constante :
const int &ri=3; // Référence sur 3.
int &error=4; // Erreur ! La référence n'est pas constante.
4.5. Lien entre les pointeurs et les références
Les références et les pointeurs sont étroitement liés. En effet, une variable et ses différentes références ont la même adresse, puisqu'elles permettent d'accéder à un même objet. Utiliser une référence pour manipuler un objet revient donc exactement au même que de manipuler un pointeur constant contenant l'adresse de cet objet. Les références permettent simplement d'obtenir le même résultat que les pointeurs, mais avec une plus grande facilité d'écriture.
Cette similitude entre les pointeurs et les références se retrouve au niveau syntaxique. Par exemple, considérons le morceau de code suivant :
int i=0;
int *pi=&i;
*pi=*pi+1; // Manipulation de i via pi.
et faisons passer l'opérateur & de la deuxième ligne à gauche de l'opérateur d'affectation :
int i=0;
int &*pi=i; // Cela génère une erreur de syntaxe mais nous
// l'ignorons pour les besoins de l'explication.
*pi=*pi+1;
Maintenant, comparons avec le morceau de code équivalent suivant :
int i=0;
int &ri=i;
ri=ri+1; // Manipulation de i via ri.
Nous constatons que la référence ri peut être identifiée avec l'expression *pi, qui représente bel et bien la variable i. Ainsi, la référence ri encapsule la manipulation de l'adresse de la variable i et s'utilise comme l'expression *pi. Cela permet de comprendre l'origine de la syntaxe de déclaration des références. La différence se trouve ici dans le fait que les références doivent être initialisées d'une part, et que l'on n'a pas à effectuer le déréférencement d'autre part. Les références sont donc beaucoup plus faciles à manipuler que les pointeurs, et permet
Revenir en haut Aller en bas
Voir le profil de l'utilisateur http://didouqen.ousama.free.fr
 
Chapitre4.Les pointeurs et références (1)
Voir le sujet précédent Voir le sujet suivant Revenir en haut 
Page 1 sur 1

Permission de ce forum:Vous ne pouvez pas répondre aux sujets dans ce forum
DESIGN STUDIO FORUM :: programmation :: C,C++-
Sauter vers: