Quelle est la différence entre un pointeur vers une référence, une référence vers un pointeur et un pointeur vers un pointeur en C++?
Où devrait-on préférer l'un à l'autre?
Tout d'abord, une référence à un pointeur est comme une référence à toute autre variable:
void fun(int*& ref_to_ptr)
{
ref_to_ptr = 0; // set the "passed" pointer to 0
// if the pointer is not passed by ref,
// then only the copy(parameter) you received is set to 0,
// but the original pointer(outside the function) is not affected.
}
Un pointeur vers une référence est illégal en C++, car - contrairement à un pointeur - une référence est juste un concept qui permet au programmeur de créer des alias d'autre chose. Un pointeur est un place en mémoire qui a l'adresse de quelque chose d'autre, mais une référence n'est PAS.
Maintenant, le dernier point pourrait ne pas être clair si vous insistez pour traiter les références comme des pointeurs. par exemple.:
int x;
int& rx = x; // from now on, rx is just like x.
// Unlike pointers, refs are not real objects in memory.
int* p = &x; // Ok
int* pr = ℞ // OK! but remember that rx is just x!
// i.e. rx is not something that exists alone, it has to refer to something else.
if( p == pr ) // true!
{ ... }
Comme vous pouvez le voir dans le code ci-dessus, lorsque nous utilisons la référence, nous ne traitons pas de quelque chose de séparé de ce à quoi elle fait référence. Ainsi, l'adresse d'une référence n'est que l'adresse de ce à quoi elle fait référence. C'est pourquoi il n'existe pas une telle chose appelée l'adresse de la référence en termes de ce dont vous parlez.
Un pointeur en C++ est juste une valeur qui stocke un emplacement mémoire (généralement sous la forme d'une valeur 32 bits).
Supposons que vous ayez une valeur entière d'entrée utilisateur (78
== 0x4E
en hexadécimal).
Il serait stocké en mémoire de la même manière (je simplifie volontairement les choses pour cet exemple):
Memory address Value
0x12345678 0x0000004E
Si vous vouliez créer un "pointeur" vers cette valeur, cela ressemblerait à ceci en mémoire:
Memory address Value
0x22334455 0x12345678
À l'adresse mémoire 0x22334455
vous avez maintenant un "pointeur" dont la valeur est 0x12345678
, ou l'adresse mémoire de l'endroit où la valeur entière saisie par l'utilisateur (0x4E
) est stocké.
Supposons que vous vouliez créer un "pointeur" vers cette valeur de pointeur. Cela ressemblerait à ceci:
Memory address Value
0x11335577 0x22334455
Vous avez maintenant une nouvelle valeur de "pointeur" en mémoire qui stocke l'adresse mémoire de la valeur de pointeur précédemment définie.
Les pointeurs peuvent être créés comme ceci indéfiniment - la clé se souvient qu'un pointeur n'est qu'une autre valeur que le compilateur interprète comme un emplacement mémoire (et il fournit diverses sémantiques d'accès telles que *
et ->
qui sont spéciaux pour les types "pointeur").
Un référence peut être considéré comme une vue, ou un alias, sur un autre objet réel. Lorsque vous créez une référence à un pointeur appelé myReference
, vous définissez simplement un nouveau nom appelé myReference
qui peut être utilisé pour accéder au pointeur que vous avez précédemment défini en mémoire.
En interne, les références sont implémentées à l'aide de pointeurs, mais cela dépasse le cadre de votre question.
Les références ont des restrictions sur les autres types en C++ - par exemple, vous devez toujours initialiser une référence pour "faire référence" à un objet réel lorsque vous le créez, tandis que un pointeur peut pointer vers une mémoire invalide ou non initialisée.
Cela n'existe pas. Comme indiqué précédemment, une référence est simplement un alias vers un autre objet. Vous ne pouvez pas "pointer" vers une référence, car ce n'est pas un objet en soi mais simplement un autre nom pour un objet réel.
Bien sûr, vous pouvez avoir un pointeur vers l'objet auquel une référence fait référence. Mais maintenant, nous sommes de retour dans le territoire du pointeur Vanilla.
Lorsque vous passez un paramètre par valeur à une méthode ou une routine, vous passez essentiellement une "copie" de l'objet à la méthode. Toutes les modifications que vous apportez à la valeur dans la routine seront perdues lors du retour de la routine, car le paramètre sera traité comme une variable locale dans le contexte de la routine.
Si vous souhaitez modifier un paramètre qui est passé afin que le code client (appelant) puisse accéder à la modification, vous devez passer le paramètre par pointeur ou par référence.
Par exemple:
void myMethod(int myValue)
{
// NOTE: This change will be lost to the caller!
myValue = 5;
}
void myMethod2(int* myValue)
{
// Correct way of modifying pointer parameter value
*myValue = 5;
}
void myMethod3(int& myValue)
{
// Correct way of modifying reference parameter value
myValue = 5;
}
Disons maintenant que votre méthode souhaite allouer de la mémoire pour un pointeur. Vous pourriez être tenté de faire ceci:
void myMethod4(int* myValue)
{
// Warning: You will lose the address of the allocated
// memory when you return!
myValue = new int[5];
}
Mais rappelez-vous que vous modifiez la copie de la valeur du pointeur ici, pas le réel valeur du pointeur. Puisque vous souhaitez modifier le pointeur dans cette routine, et non le valeur vers lequel le pointeur "pointe", vous devez le passer en tant que "pointeur vers un pointeur "ou une" référence à un pointeur ":
void myMethod5(int** myValue)
{
// Correct way of allocating memory in a method
// via pointer-to-pointer
*myValue = new int[5];
}
void myMethod6(int*& myValue)
{
// Correct way of allocating memory in a method
// via reference-to-pointer
myValue = new int[5];
}
Dans ces deux derniers exemples, le code qui appelle myMethod5
et myMethod6
obtiendra correctement l'adresse mémoire de la mémoire nouvellement allouée via le pointeur ou la référence de paramètre myValue
.
Il n'existe pas de pointeur vers une référence.
Une référence est une abstraction loin des pointeurs. Les références sont un peu plus difficiles à bousiller, en particulier pour les novices, et sont un peu plus de haut niveau.
Vous n'avez pas besoin de références. Vous pouvez toujours utiliser des pointeurs. Cependant, le code peut parfois être plus facile à lire avec eux.
Un exemple typique de débutant est une liste chaînée. Imaginez que vous ayez une variable appelée "liste" qui contient un pointeur vers la première. Si vous voulez ajouter quelque chose à la tête, vous devez donner à votre add () un double pointeur, car il doit être capable de modifier "head". Cependant, vous pouvez utiliser une référence à un pointeur à la place. Ici, nous voulons utiliser des pointeurs dans la liste elle-même puisque nous les muterons, mais la fonction add () sera plus claire si nous passons une référence à la tête de la liste au lieu d'un double pointeur.
Ils sont simplement un choix de style. Si vous travaillez sur un projet plus important, vous devez choisir le style du projet. Sinon, vous pouvez utiliser ce que vous jugez préférable. Vous devriez, cependant, être à l'aise avec tous les styles si vous espérez être un programmeur C++ moyennement performant.
Il est également intéressant que vous ne puissiez pas avoir de pointeur vers une référence. C'est parce que les références ne sont en réalité qu'un autre nom pour une autre variable, qui peut être dans une autre portée. Avoir un pointeur vers une référence n'a pas de sens. Ce que vous voulez vraiment, c'est juste un pointeur sur les données d'origine, aucune référence impliquée.
Il est important de noter que même si une référence n'est pas un objet et n'a donc pas d'adresse accessible, une référence peut être contenue dans un objet et l'objet contenant a une adresse.
struct contains_ref
{
int& ref;
contains_ref(int& target) : ref(target) {}
};
L'explication "la référence est un alias" n'est pas incorrecte, mais est souvent accompagnée d'allégations trompeuses. Une référence n'est pas équivalente à l'objet d'origine. Il a sa propre durée de vie, déterminée par la portée ou l'objet qui le contient, et non par l'objet auquel il fait référence. Et une référence peut survivre à un objet et être utilisée pour faire référence à un nouvel objet créé à la même adresse.
Traitez une référence comme ce qu'elle est vraiment - une abstraction autour d'un pointeur qui exclut null comme une valeur valide et empêche la remise en place1 - et pas quelque chose de magique. La seule propriété inhabituelle d'une référence qui n'est pas dérivée de sa nature de pointeur est l'extension de la durée de vie des temporaires.
1En fait, c'est une conséquence du fait que C++ ne fournit aucune syntaxe pour faire référence à la référence elle-même plutôt qu'à sa cible. Tous les opérateurs, y compris l'opérateur d'affectation, sont simplement appliqués à la cible.
Essayez simplement de voir par vous-même ce que chaque chose contient. L'exemple de programme imprime simplement la valeur d'un int et les adresses des différentes entités:
#include<stdio.h>
int main(){
int myInt ;
int *ptr_to_myInt = &myInt;
int *ptr_to_myInt_ref = ptr_to_myInt;
myInt = 42;
printf("myInt is %d\n",myInt);
printf("ptr_to_myInt is %x\n",ptr_to_myInt);
printf("ptr_to_myInt_ref is %x\n",ptr_to_myInt_ref);
printf("&ptr_to_myInt is %x\n",&ptr_to_myInt);
return 0;
}
Production:
myInt is 42
ptr_to_myInt is bffff858
ptr_to_myInt_ref is bffff858
&ptr_to_myInt is bffff854
Ainsi, le pointeur vers l'int et le pointeur vers la référence de l'int sont exactement la même chose. Ceci est évident à partir du code, car le pointeur vers une référence est simplement une autre façon d'aliaser un pointeur (il dit "tenez l'adresse suivante pour moi").
Désormais, le pointeur a également besoin d'espace en mémoire, et si vous imprimez la référence à ce pointeur (la dernière instruction printf), il indique simplement l'emplacement en mémoire où réside le pointeur.