Big Tuto : Apprenez le C++

Chapitre 4 : Pointeurs et références

Tutoriel présenté par : Robert Gillard (Gondulzak)
Date d'écriture : 27 août 2015
Date de révision : 31 octobre 2015

      Préliminaires 

   Toute déclaration, que ce soit d'une variable, d'un pointeur ou d'une référence, utilise un mot quelconque du vocabulaire courant et le plus souvent avec une signification précisant l'objet à déclarer.

   Il se fait que le langage C++ (tout comme le langage C, d'ailleurs wink), se réserve de nombreux mots, appelés aussi mots-clé que seuls les compilateurs ont le droit d'utiliser.

   Voici dans le tableau suivant, les mots réservés par C++. Les noms introduits par la nouvelle norme C++ 11 sont affichés en caractères gras.

 

 C++ Keywords
alignas
alignof 
asm 
auto 
bool 
break 
case 
catch 
char 
char16_t 
char32_t 
class 
const 
constexpr 
const_cast 
 continue 
decltype 
default 
delete 
do 
double 
dynamic_cast 
else 
enum 
explicit 
export 
extern 
false 
final 
float 
for 
friend 
goto
if 
inline 
int 
long 
mutable 
namespace 
new 
noexcept 
nullptr 
operator 
override 
private 
protected 
public 
register  
reinterpret_cast 
return 
short 
signed 
sizeof 
static 
static_assert 
static_cast 
struct 
switch 
template 
this 
thread_local 
throw 
true
try 
typedef
typeid
typename 
union
unsigned 
using 
virtual 
void 
volatile 
wchar_t 
while 
 
 
 
 

 

         1 – Les pointeurs

   Comme définition d'un pointeur, le langage C parlait généralement d'une variable contenant l'adresse d'une autre variable. Le langage C++ actuel fait plutôt référence à un objet qui pointe vers une adresse de la mémoire pour en extraire un objet situé à cette adresse. wink

   Quoi qu'il en soit, les pointeurs sont des types d'objets indirectement utilisés pour avoir accès à des emplacements mémoire contenant des objets de même type. Le contenu d'un pointeur n'est donc pas une valeur mais bien une adresse mémoire.

 

   Vous suivez toujours ? indecision

   Je ne voudrais pas minimiser une certaine difficulté à la bonne compréhension des pointeurs car ceux-ci sont d'une grande importance pour toute étude sérieuse d'un langage tel que le C++, mais nous allons aborder ce chapitre petit à petit et je vous assure qu'au terme de celui-ci, vous comprendrez déjà une bonne partie de tout ce qu'il y a à savoir sur les pointeurs. angel

   Et s'il y en a parmi vous quelques-uns qui n'ont déjà plus rien à apprendre de ceux-ci, je leur proposerai de passer directement... au chapitre suivant ! wink

   Je me permettrai cependant une dernière remarque : le C++ est un langage difficile à maîtriser dans son entièreté tant sa richesse est grande et son contenu toujours évolutif. Cela, même au terme de plusieurs années d'études. Quand vous pensez avoir fini, il y a toujours d'autres concepts ou nouvelles fonctionnalités à découvrir et je crois que c'est ce qui fait l'extrême richesse mais aussi la difficulté de ce langage. cool


      1.1 - Déclaration d'un pointeur

   On commence simplement :

int nombre = 10;
int *ptr = &nombre;

   ...signifie que l'on définit un pointeur sur un entier (ici 10) et que ptr est destiné à recevoir l'adresse mémoire où trouver cet entier.

   Ici, le pointeur est ptr, qui est une variable de type int, et donc le compilateur réservera 4 bytes pour le stockage de cet entier. wink

 

   L'étoile placée devant le pointeur ''*'' est un opérateur appelé opérateur d'indirection ou de déréférencement. Cela veut dire que si vous entrez :

cout << *ptr;

   ...le résultat sur la console donnera la valeur de la variable ''nombre'', c'est-à-dire 10.

 

   Bien, je vous ai dit que cette valeur était stockée à une adresse, et nous allons voir maintenant comment récupérer la valeur d'une adresse.

   Le pointeur contenant l'adresse d'un objet, nous obtenons cette adresse en utilisant l'opérateur d'adressage (&). Soit :

int i = 10;
int *ptr2 = &i;

   ...veut dire que ptr2 va contenir l'adresse où est stockée la valeur 10 (donc l'adresse de la variable i, cette adresse dépendant bien entendu de chaque ordinateur wink) et que le pointeur déréférencé, c'est à dire ptr2, précédé de l'opérateur d'indirection ''*'' (*ptr2), contiendra, nous l'avons vu, la valeur 10.


   Donc les instructions :

cout << i;
      et
cout << *ptr2;

   ...donneront chacune la valeur 10.


   et :
cout << &i;

   donnera l'adresse pointée par ptr2 et dans laquelle est stocké l'entier i (10).

 

   Nous verrons des exemples de tout ceci un peu plus loin mais en attendant nous avons encore un peu de théorie à expliquer. smiley


   Vous voyez que pour les exemples j'utilise le mot ''ptr'' pour identifier un pointeur, c'est pour que vous saisissiez bien qu'il s'agit d'un pointeur. wink Bien sûr, tout comme les variables, un pointeur peut avoir n'importe quelle dénomination pour autant que cela ne soit pas un des mots réservés du tableau ci-dessus. cheeky


   Ainsi vous pourriez nommer un pointeur du genre *le_gobelin_de_la_caverne_aux_rats, mais je ne vous le conseille pas. indecision Tout comme avec les variables, utilisez des noms qui vous rappellent ce que vous êtes en train de faire. Pour la suite de notre étude des pointeurs, j'utiliserai les termes ptr, ou ptr1, ptr2.... (c'est un bon moyen de voir directement que l'on a affaire à un pointeur wink).


      1.2 - Pointeurs et variables doivent avoir des types compatibles

   En effet, considérons les déclarations suivantes :

double valeur = 125266;
int *ptr = &valeur;

   Erreur ! angry Le compilateur va vous signaler ''types différents''.

double valeur = 125266;
double *ptr = &valeur;

      ou

int valeur = 125266;
int *ptr = &valeur;

   ...sont bien entendu des définitions valables. wink


      1.3 - Autre manière d'initialiser un pointeur

int nombre = 67;
int *ptr;
ptr = &nombre;

   est bien entendu identique à :

int nombre = 67;
int *ptr = &nombre


      1.4 Un pointeur doit toujours être initialisé

   Il serait dangereux de déclarer un pointeur et de ne pas l'initialiser. devil Une déclaration du genre :

int *ptr;

   ...et ne plus s'en occuper par la suite est donc à proscrire car un pointeur non initialisé est appelé un ''pointeur fou'' (car pouvant pointer n'importe où dans la mémoire ! surprise). Il risquerait donc de causer pas mal de dégâts dans un programme... crying

 

   Voici 3 manières d'initialiser un pointeur à 0 :

int *ptr = 0;
int *ptr2 = nullptr;
int *ptr3 = NULL;

 

   La nouvelle norme C++ 11 a introduit un nouveau mot réservé pour initialiser un pointeur à 0. Il s'agit de nullptr.

   D'anciens programmes C utilisent une variable du pré-processeur, il s'agit de NULL. Cette façon d'initialiser un pointeur à 0 n'est plus très compatible avec les standards actuels et d'autre part, l'initialisation avec la valeur 0 qui est une valeur entière ne conduit qu'à une approximation pouvant avoir certains effets négatifs. Dans tous les cas, il est recommandé d'utiliser nullptrwink

 

Retrouvez les projets complets de ce chapitre :

 

   En attendant voici un programme reprenant les exemples de ce que nous avons appris jusqu'à présent

   Projet 021 - Pointeurs1 :

//Projet 021Pointeurs1
#include <iostream>
 
using namespace std;
using ushort = unsigned short int;
 
 
int main()
{
// Ce pointeur est réservé pour être utilisé plus tard
ushort *ptr3 = nullptr;
 
int nombre(10);
// ptr pointe sur l'adresse ou est stockée la variable nombre.
int *ptr = &nombre;
// On affiche la valeur de la variable nombre
cout << "Valeur de nombre = " << nombre << endl;
// On affiche le pointeur déréférencé (affiche 10)
cout << "Valeur de *ptr = " << *ptr << endl << endl;
 
 
// On initialise une seconde variable entière
int nombre2 = 125266;
// ptr2 pointe sur l'adresse ou est stockée la variable nombre2.
int *ptr2 = &nombre2;
cout << "Valeur de nombre2 = " << nombre2 << endl;
cout << "Valeur de *ptr2 = " << *ptr2 << endl;
// On obtient l'adresse de ptr2 à l'aide de l'opérateur d'adresse &
cout << "L'adresse pointee par ptr2 est " << &nombre2 << endl << endl;
 
// On met les pointeurs ptr et ptr2 a 0 en utilisant nullptr
ptr = nullptr;
ptr2 = nullptr;
 
cout << "Apres mise a zero des pointeurs ptr et ptr2" << endl;
cout << "Valeur de ptr = " << ptr << endl;
cout << "Valeur de ptr2 = " << ptr2 << endl;
cout << "Valeur de nombre2 = " << nombre2 << endl << endl;
 
 
cout << Nous allons maintenant utiliser le pointeur ptr3 reserve au debut du
programme" << endl;
ushort nombre3(25); // Même type que le pointeur ptr3 !
ptr3 = &nombre3;
cout << "Valeur de nombre3 = " << nombre3 << endl;
cout << "Valeur de *ptr3 = " << *ptr3 << endl;
cout << "Adresse pointee par ptr3 = " << ptr3 << endl << endl;
 
 
// Autre façon d'initialiser un pointeur
int nombre4(67);
int *ptr4;
ptr4 = &nombre4;
cout << "Valeur de nombre4 = " << nombre4 << endl;
cout << "Valeur de *ptr4 = " << *ptr4 << endl;
cout << "Adresse pointee par ptr4 = " << ptr4 << endl;
cout << "C'est a dire &nombre4 = " << &nombre4 << endl << endl;
// On met le pointeur à 0 en utilisant nullptr
ptr4 = nullptr;
// Le pointeur est bien supprimé
cout << "ptr4 a ete mis a 0 avec nullptr" << endl;
cout << "ptr4 = " << ptr4 << endl;
 
 
cin.get();
return 0;
}

 

   Et tout ce qu'il y a à dire sur ce programme se trouve dans ses commentaires ou dans la théorie expliquée ci-dessus.

   N'ayez néanmoins pas peur d'intervenir dans le forum pour l'un ou l'autre point qui ne serait pas bien compris. Il est impératif de bien tout comprendre pour entrer dans l'étude des chapitres suivants. wink
 

   Et ce programme affiche dans la console :

 

 

   Les adresses des variables de ce programme seront bien entendu différentes selon les PC de chacun. wink


      1.5 Pointeurs void

   Nous avons vu qu'un pointeur d'un certain type devait pointer sur une variable du même type. Il existe cependant un pointeur qui peut-être utilisé pour pointer sur des pointeurs de tout type, il s'agit du pointeur void.

   Ce pointeur, de type non défini ne peut-être initialisé que d'une seule manière, en le faisant pointer sur un autre pointeur de n'importe quel type. On affectera sa valeur à un pointeur d'un type conventionnel et on opérera ensuite l'indirection.

   Voyons ceci dans un petit exemple (Projet 022 - Pointeurs2) : 

//Projet 022Pointeurs2
//Utilisation d'un pointeur void
#include <iostream>
 
using namespace std;
using ushort = unsigned short int;
 
 
int main()
{
// On déclare un entier court non signé et 2 pointeurs de même type
ushort nombre(10), *ptr1, *ptr2;
// On déclare un pointeur d'un type indéfini
void *vptr;
// On fait pointer ptr1 sur l'adresse de nombre
ptr1 = &nombre;
// On fait pointer le pointeur void sur ptr1 de type ushort
vptr = ptr1;
// Valeur de *ptr1
cout << "Valeur de *ptr1 = " << *ptr1 << endl;
cout << "Adresse de ptr1 = " << vptr << endl;
cout << "Adresse de vptr = " << vptr << endl << endl;
 
// On fait pointer ptr2 sur vptr
ptr2 = (ushort *)vptr;
//Et on opère l'indirection sur ptr2
cout << "Valeur de *ptr2 = " << *ptr2 << endl;
cout << "Adresse de ptr2 = " << ptr2 << endl;
 
cin.get();
return 0;
 
}

 

   Nous voyons que nous devons ''forcer'' l'affectation de l'adresse du pointeur vptr vers ptr2 en effectuant un cast (voir Chapitre 2 – Conversion forcée de type) sur le type du pointeur vptr. ptr2 contient alors l'adresse de ptr1 par pointeur void interposé et l'indirection peut se faire sur ptr2 (on affiche alors le pointeur déréférencé *ptr2 qui est à son tour égal à 10).

   Ce qui donne dans la console :
 

 

      1.6 Pointeurs et tableaux d'entiers

   Le nom d'un tableau est toujours un pointeur sur le premier élément du tableau. wink

   Donc dans la lecture d'un tableau d'entiers nous pouvons remplacer les indices entre crochets par un pointeur.

   Soit l'exemple suivant (Projet 023 - Pointeurs3) :

//Projet 023Pointeurs3
//Pointeurs sur un tableau d'entiers
#include <iostream>
 
using namespace std;
using uint = unsigned int;
 
 
int main()
{
 
// On définit un tableau de 10 entiers non signés
uint tab[] = { 9, 2, 5, 11, 7, 18, 3, 16, 4, 23 };
// On définit un pointeur qui pointe sur le 1er élément du tableau
uint *ptr = tab;
 
cout << "Valeurs du tableau :" << endl;
 
for (int i = 0; i < 10; i++)
cout << *ptr++ << " ";
 
cout << endl;
 
cin.get();
return 0;
 
}

 

Ce qui donne dans la console :

 

 

   Nous reviendrons plus en profondeur sur les pointeurs et les tableaux quand nous aborderons les chaînes de caractères. wink


      1.7 Pointeurs de pointeurs

   Un pointeur étant situé en mémoire, il a donc, comme n'importe quel objet, une adresse. Nous pouvons donc charger l'adresse d'un pointeur dans un autre pointeur. cheeky

   int nombre = 128;
   int *iptr = &nombre;
   int **iptr2 = &iptr;

 

   Dans cet exemple, *iptr pointe sur l'adresse de la variable nombre. Ce pointeur ayant lui-même une adresse qui est pointée par **iptr2 qui est un pointeur sur un pointeur.

   Mais voyons ceci par un court exemple (Projet 024 - Pointeurs4) : 

//Projet 024Pointeurs4
//Un pointeur sur un pointeur
#include <iostream>
 
using namespace std;
using uint = unsigned int;
 
 
int main()
{
 
// On déclare un entier non signé
uint nombre = 128;
// Un pointeur sur ce nombre
uint *iptr = &nombre;
// Et un second pointeur sur le premier pointeur
uint **iptr2 = &iptr;
 
// On affiche les adresses et les pointeurs déréférencés
cout << "Valeur de nombre = " << nombre << endl;
cout << "Adresse pointee par iptr = " << iptr << endl;
cout << "Adresse pointee par iptr2 = " << iptr2 << endl << endl;
 
cout << "*iptr = " << *iptr << endl;
cout << "**iptr2 = " << **iptr2 << endl;
 
cin.get();
return 0;
 
}

 

   Ce qui donne dans la console (de mon PC bien entendu wink) :

 

 

   L'adresse 005CFE88 est l'adresse pointée par iptr2. Cette adresse contient le pointeur iptr qui lui-même pointe vers l'adresse de la variable nombre.
 
   Donc pour obtenir la valeur 128 de la variable nombre, il faut déréférencer le pointeur iptr une fois tandis que le pointeur iptr2 sera déréférencé deux fois pour obtenir cette valeur.
 

      1.8 Les pointeurs et le qualificateur const

 

   Le qualificateur const peut être utilisé avec les pointeurs et son emplacement (avant ou après le type pointé) va déterminer son action. 
 
const int *ptr1 est un pointeur sur un entier constant et la valeur sur laquelle il pointe ne peut pas être modifiée.
int *const ptr2 ptr2 est un pointeur constant sur un entier. Cet entier peut être modifié mais le pointeur sur lequel il pointe ne peut pas pointer sur une autre adresse.
const int *const ptr3 ptr3 est un pointeur constant sur un entier constant. L'entier ne peut-être modifié ni le pointeur pointer vers une autre adresse.


   Exemples :

const double PI = 3.14159           PI est bien entendu une valeur constante
double *ptr = &PI                         Erreur !! ptr ne pointe pas sur l'adresse d'une constante.
const double *ptr = &PI               Ok, ptr peut pointer sur une valeur double constante.
 
   Si on veut un pointeur constant sur PI nous écrirons :
 
const double *const ptr = &PI

 

      1.9 Arithmétique des pointeurs

   Dans le projet 023 - Pointeurs 3, nous avons vu que l'on incrémentait un pointeur *ptr afin de lire le contenu d'un tableau d'entiers.

   Un pointeur peut-être soit incrémenté soit décrémenté ce qui veut dire que seules les opérations d'addition et de soustraction peuvent être effectuées sur les pointeurs. wink

   Notez que quand on incrémente ou que l'on décrémente un pointeur, on déplace (avance ou recule) le pointeur d'un nombre d'emplacements mémoire égal au type de l'objet sur lequel le pointeur pointe.

 

Remarque : Il y a une différence entre incrémenter ou décrémenter une adresse sur laquelle un pointeur pointe et incrémenter ou décrémenter le pointeur lui-même.

   Dans notre exemple de lecture de tableau d'entiers parcouru par un pointeur, nous utilisions l'instruction :

cout << *ptr++;

   ce qui avait pour effet d'afficher la valeur contenue à l'adresse pointée par ptr. Mais considérons les deux instructions suivantes :

cout << ++ *ptr;

   Cette instruction a pour effet d'incrémenter la valeur stockée à l'emplacement pointé par ptr et de l'afficher.

   Tandis que :

cout << * ++ptr;

   ...aura pour effet d'incrémenter l'adresse sur laquelle pointe ptr, et d'afficher la valeur contenue à l'adresse où pointe ptr maintenant.

 

   Mais voyons tout ceci dans un programme (Projet 025 - Pointeurs 5) :  

//Projet 025Pointeurs5
//Arithmétique des pointeurs
#include <iostream>
 
using namespace std;
using uint = unsigned int;
using ullong = unsigned long long;
 
 
int main()
{
 
// Nous déclarons deux tableaux de types différents
uint tab[] = { 9, 2, 5, 11, 7, 18, 3, 16, 4, 23 };
ullong tab2[] = { 9, 2, 5, 11, 7, 18, 3, 16, 4, 23 };
 
// Et un pointeur sur le premier élément de chaque tableau
uint *uptr = tab;
ullong *lptr = tab2;
 
cout << "Adresses des valeurs du tableau uint:" << endl;
for (int i = 0; i < 10; i++)
cout << (int)uptr++ << " ";
cout << endl << endl;
 
 
cout << "Adresses des valeurs du tableau ulong:" << endl;
for (int i = 0; i < 10; i++)
cout << (int)lptr++ << " ";
cout << endl << endl;
 
 
// On fait pointer un second pointeur uint sur de début du tableau tab
uint *uptr2 = tab;
// On utilise la première forme d'instruction cout << ++ *ptr
cout << "On pointe de nouveau sur tab et on utilise l'instruction 'cout << ++ *ptr'"
<< endl;
for (int i = 0; i < 10; i++)
cout << ++ *uptr2 << " ";
cout << endl << endl;
 
cout << "Meme chose avec l'instruction 'cout << * ++ptr'" << endl;
uint *uptr3 = tab;
for (int i = 0; i < 10; i++)
cout << * ++uptr3 << " ";
cout << endl;
 
cin.get();
return 0;
 
}

 

   Ce qui donne dans la console :

 

 

   Nous avons besoin de quelques explications wink :

   Tout d'abord, vous voyez que j'ai transformé les valeurs hexadécimales des adresses en valeurs entières pour que nous nous rendions compte plus rapidement des déplacements des pointeurs.

   Nous voyons qu'un uint est stocké sur 4 bytes (Rappel : cela varie selon les PCs). Le pointeur uptr qui, au départ, pointe sur l'adresse 5766220, pointe ensuite 4 bytes plus loin sur l'adresse 5766224 et ainsi de suite jusqu'à la fin du tableau. wink

   Nous appliquerons le même raisonnement en ce qui concerne le pointeur lptr (8 bytes pour un unsigned long long).

 

   Par la suite, l'instruction cout << ++ *ptr affichera toutes les valeurs successives au-delà de 9 puisque cette instruction incrémente les valeurs stockées à l'emplacement pointé par uptr2.

   Et nous voyons pour terminer que uptr3 incrémente d'abord l'adresse sur laquelle il pointe et affiche sa valeur. C'est pourquoi, la 10ème valeur affichée est hors tableau, il s'agit de la valeur située à l'adresse suivante de la dernière adresse de notre tableau.

   Il faut donc faire très attention quand vous faites des opérations d'incrémentation ou de décrémentation de pointeurs car on peut facilement se ''mélanger les pinceaux'' ! indecision

 

   2 – Les références

      2.1 Une référence est un alias

   Contrairement aux pointeurs qui sont considérés comme des objets, les références représentent simplement un autre nom d'un objet déjà existant. On les appelle également ''variables d'adresses''.

 

   Soit ''nombre'' une variable entière déclarée et définie comme suit:

int nombre = 128;

   Nous pouvons créer une référence (un alias) à cette variable en écrivant :

int &ref = nombre;

 

   Quand on initialise une variable avec une certaine valeur, la valeur de celle-ci est immédiatement copiée dans celle-là. Quand on initialise une référence (ref dans ce cas), au lieu d'y copier la valeur, la référence est ''liée'' à l'objet initial, ce qui veut dire que les deux (la variable et sa référence) sont identiques et que si l'on change la valeur de la variable, la référence prendra aussitôt la même valeur. wink

 

   Une référence n'étant pas un objet, elle doit être initialisée par un objet dès sa déclaration, jamais par un littéral.

int &refNombre;                      Erreur ! La référence doit être initialisée.

Int &refNombre = 10;             Erreur ! La référence doit être initialisée par un objet.
int &refNombre = nombre;     Ok, et c'est la seule façon d'initialiser une référence.


   Voyons quelques exemples dans un petit programme (Projet 026 - References 1) :    

//Projet 026References1
//Création d'alias
#include <iostream>
 
using namespace std;
using uint = unsigned int;
 
 
int main()
{
 
// On déclare un entier non signé
uint nombre = 128;
// Une référence à cet entier
uint &refNb = nombre;
 
// On affiche le nombre
cout << "Valeur du nombre = " << nombre << endl;
cout << "valeur de sa reference refNb = " << refNb << endl << endl;
 
 
cout << "On enleve 10 a la valeur du nombre" << endl;
nombre -= 10; //identique à nombre = nombre - 10
cout << "Valeur du nombre = " << nombre << endl;
cout << "valeur de sa reference refNb = " << refNb << endl << endl;
 
cout << "On ajoute 10 a la reference du nombre" << endl;
refNb += 10; //identique à refNb = refNb + 10
cout << "Valeur du nombre = " << nombre << endl;
cout << "valeur de sa reference refNb = " << refNb << endl << endl;
 
// On déclare une seconde référence qu'on initialise avec la valeur de refNb
uint &ref2Nb = refNb;
cout << "On declare une seconde reference qu'on initialise avec la valeur de refNb"
<< endl;
cout << "Valeur de ref2Nb = " << ref2Nb << endl;
cout << "Valeur de refNb = " << refNb << endl;
cout << "Valeur de nombre = " << nombre << endl;
 
 
cin.get();
return 0;
 
}

 

   Ce qui donne dans la console :

 

 

 

   Vous voyez le lien qui existe entre une variable et sa (ses) référence(s), si on modifie l'une, l'autre est aussitôt modifiée. wink

   Mais à quoi cela peut-il servir, me demanderez-vous ? surprise

   Et bien les références sont bien plus faciles à appréhender que les pointeurs ! angel Là où vous pensez qu'un pointeur est nécessaire, vous pouvez utiliser une référence à la place. Vous pouvez utiliser des références sur des variables, sur des pointeurs (comme nous allons le voir) ou des tableaux. Elles sont surtout utilisées dans des fonctions où la manipulation de pointeurs pourrait facilement conduire à provoquer l'une ou l'autre erreur. Nous verrons ceci dans un prochain chapitre sur une étude plus approfondie des fonctions. wink


      2.2 Adresse d'une référence

   Une référence étant un alias d'une variable d'un certain type, l'adresse d'une référence est l'adresse de la variable à laquelle la référence se réfère.

   Exemple (Projet 027 - References 2) : 

//Projet 027References2
//Adresse d'une référence
#include <iostream>
 
using namespace std;
using ushort = unsigned short int;
 
 
 
int main()
{
 
// On déclare un entier court non signé
ushort nombre = 255;
// Une référence à cet entier court
ushort &ref = nombre;
 
// On affiche les adresses
cout << " Adrese de la variable 'nombre' = " << &nombre << endl;
cout << " Adresee de sa reference 'ref' = " << &ref << endl;
 
cin.get();
return 0;
 
}

 

Ce qui donne dans la console :

 

 

 

   Nous voyons que le symbole & (opérateur d'adresse) sert à la fois pour extraire l'adresse d'une variable et pour initialiser une référence. wink

 

      2.3 Les références et le qualificateur const

   Comme avec les pointeurs ou autres objets, on peut lier une référence à un objet de type constant. Cependant, contrairement à une référence ordinaire, une référence sur un objet constant ne peut pas être utilisée pour changer l'objet à laquelle la référence est liée.

const int nombre = 128;

const int &refNb = nombre;
const int &refNb2 = nombre;         // une seconde référence est liée à ''nombre''.
const int &refNb3 = refNb * 2;      // refNb3 est une référence à un const.


refNb = 64;                                     // Erreur ! Nombre est un entier constant.
int refNb4 = refNb * 2;                  // Erreur ! refNb4 n'est pas une référence constante.

 

      2.4 Références sur un pointeur

   Une référence n'étant pas un objet, on ne peut pas avoir un pointeur sur une référence tandis qu'un pointeur étant un objet, on peut définir une référence sur un pointeur.

unsigned int nombre = 64;    // Déclaration d'un unsigned int.
unsigned int *ptr;                  // Déclaration d'un pointeur.

 

unsigned int &ref = *ptr;      //ref est une référence sur le pointeur ptr.
ref = &nombre;

 

   ref est une référence sur un pointeur. Assigner l'adresse de nombre à ref fait que ptr pointera sur la variable nombre.

   Montrons ceci dans un petit exemple (Projet 028 - References 3) :

//Projet 028References3
//Référence sur un pointeur
#include <iostream>
 
using namespace std;
using uint = unsigned int;
 
 
 
int main()
{
// On déclare un entier non signé
uint nombre = 64;
// Déclaration d'un pointeur
uint *ptr;
 
// On dérlare une référence sur le pointeur ptr
uint *&ref = ptr;
// Assigner l'adresse de la variable à la reférence fera pointer
// le pointeur sur cette variable
ref = &nombre;
 
cout << "ptr pointe maintenant sur nombre" << endl;
cout << "Valeur de nombre = " << nombre << endl;
cout << "Valeur du pointeur *ptr = " << *ptr << endl;
 
cin.get();
return 0;
}

 

  Ce qui donne dans la console :

 

   Voilà, ce chapitre est enfin terminé et j'espère ne pas vous avoir donné de maux de tête. indecision

   Notre étude des pointeurs et des références n'en est pas terminée pour autant car dans un autre chapitre, nous aborderons l''allocation dynamique de pointeurs'' ainsi que l'utilisation des pointeurs et des références dans les fonctions. Comme expliqué lors du premier chapitre, je reviendrai quelquefois sur des sujets déjà vus tout en approfondissant ceux-ci. 

     @ bientôt pour le chapitre 5 : Chaînes de caractères - Introduction aux Itérateurs angel

Gondulzak.
   

 

Connexion

CoalaWeb Traffic

Today122
Yesterday232
This week1148
This month3441
Total1742648

20/04/24