Quelles sont les différences entre une variable indicatrice et une variable de référence en C++?
je sais que les références sont du sucre syntaxique, donc le code est plus facile à lire et à écrire.
Mais quelles sont les différences?
résumé des réponses et des liens ci-dessous:
- un pointeur peut être réattribué n'importe quel nombre de fois alors qu'un renvoi ne peut pas être réattribué après la reliure.
- pointeurs peuvent pointer nulle part (
NULL
), alors qu'une référence se réfère toujours à un objet. - Vous ne pouvez pas prendre l'adresse d'une référence comme vous le pouvez avec des pointeurs.
- il n'y a pas de" arithmétique de référence "(mais vous pouvez prendre l'adresse d'un objet pointé par une référence et faire l'arithmétique pointeur dessus comme dans
&obj + 5
).
pour clarifier une idée fausse:
la norme c++ est très prudente pour éviter de dicter comment un compilateur peut mettre références, mais chaque compilateur C++ implémente références (pointeurs. C'est-à-dire une déclaration telle que:
int &ri = i;
si elle n'est pas entièrement optimisée , répartit le même volume de stockage comme un pointeur, et place l'adresse de
i
dans ce stockage.
So, un pointeur et une référence les deux utilisent la même quantité de mémoire.
en règle générale,
- utiliser des références dans les paramètres de fonction et les types de retour pour fournir des interfaces utiles et auto-documentant.
- utiliser des pointeurs pour mettre en œuvre des algorithmes et des structures de données.
intéressant lire:
- mon préféré de tous les temps C++ FAQ lite .
- Références vs Pointeurs .
- présentation des Références .
- Références et const .
30 réponses
-
un pointeur peut être réattribué:
int x = 5; int y = 6; int *p; p = &x; p = &y; *p = 10; assert(x == 5); assert(y == 10);
Une référence ne peut, et doit être attribuée à l'initialisation:
int x = 5; int y = 6; int &r = x;
-
un pointeur a sa propre adresse mémoire et sa taille sur la pile (4 octets sur x86), alors qu'une référence partage la même adresse mémoire (avec la variable originale) mais prend également un peu d'espace sur la pile. Depuis une référence a la même adresse que l' variable d'origine lui-même, il est sûr de penser à une référence comme un autre nom pour la même variable. Note: Ce qu'un pointeur indique peut être sur la pile ou le tas. Idem une référence. Ma demande dans cette déclaration n'est pas qu'un pointeur doit pointer vers la pile. Un pointeur est une variable qui contient une adresse mémoire. Cette variable est sur la pile. Depuis une référence a son propre espace sur la pile, et depuis l'adresse est la même que la variable de référence. Plus sur pile vs tas . Cela implique qu'il y est une vraie adresse de référence que le compilateur ne vais pas vous dire.
int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2);
-
vous pouvez avoir des pointeurs vers des pointeurs offrant des niveaux supplémentaires d'indirection. Alors que les références n'offrent qu'un seul niveau d'indirectement.
int x = 0; int y = 0; int *p = &x; int *q = &y; int **pp = &p; pp = &q;//*pp = q **pp = 4; assert(y == 4); assert(x == 0);
-
pointeur peut être assigné
nullptr
directement, alors que la référence ne peut pas. Si vous essayez assez dur, et vous savez comment, vous pouvez faire l'adresse d'une référencenullptr
. De même, si vous faites suffisamment d'efforts, vous pouvez avoir une référence à un pointeur, et alors cette référence peut contenirnullptr
.int *p = nullptr; int &r = nullptr; <--- compiling error int &r = *p; <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
-
les pointeurs peuvent itérer sur un tableau, vous pouvez utiliser
++
pour passer à l'élément suivant qu'un pointeur pointe, et+ 4
pour passer au 5e élément. Ce n'est pas une question de quelle taille l'objet est que le pointeur pointe. -
un pointeur doit être déréférencé avec
*
pour accéder à l'emplacement mémoire qu'il pointe, alors qu'une référence peut être utilisée directement. Un pointeur vers une classe / struct utilise->
pour accéder à ses membres alors qu'une référence utilise un.
. -
Un pointeur est une variable qui contient une adresse mémoire. Quelle que soit la façon dont une référence est mise en œuvre, une référence a la même adresse de la mémoire comme élément de référence.
-
les références ne peuvent pas être empilées dans un tableau, alors que les pointeurs peuvent être (mentionnés par l'utilisateur @litb)
-
les références à la Const peuvent être liées à des temporaires. Pointeurs ne peuvent pas (pas sans une certaine indirectement):
const int &x = int(12); //legal C++ int *y = &int(12); //illegal to dereference a temporary.
cela rend
const&
plus sûr à utiliser dans les listes d'arguments et ainsi de suite.
référence C++ ( pour les programmeurs C )
Un de référence peut être considéré comme un pointeur constant (à ne pas confondre avec un pointeur vers une valeur constante!) avec effet indirect automatique, c'est-à-dire que le compilateur appliquera l'opérateur *
pour vous.
toutes les références doivent être initialisées avec une valeur non nulle ou la compilation échouera. Il n'est pas possible de obtenir l'adresse d'une référence - l'opérateur d'adresse retournera l'adresse de la valeur référencée à la place - il n'est pas non plus possible de faire de l'arithmétique sur les références.
les programmeurs C pourraient ne pas aimer les références C++ car il ne sera plus évident quand l'effet indirect se produit ou si un argument est passé par valeur ou par pointeur sans regarder les signatures de fonction.
c++ programmeurs pourraient ne pas aimer utiliser des pointeurs car ils sont considérés comme dangereux-bien que les références ne sont pas vraiment plus sûres que les pointeurs constants, sauf dans les cas les plus triviaux - elles manquent de la commodité de l'approche indirecte automatique et ont une connotation sémantique différente.
Considérer la déclaration suivante à partir de la C++ FAQ :
même si une référence est souvent implémentée en utilisant une adresse sous-jacente de l'assemblée de la langue, s'il vous plaît pas pensez à une référence comme drôle à la recherche de pointeur vers un objet. Une référence est l'objet. Il est pas un pointeur vers l'objet, ni une copie de l'objet. est l' objet.
mais si une référence vraiment était l'objet, comment pourrait-il y avoir des références pendantes? Dans les langues non gérées, il est impossible que les références soient plus "sûres" que les pointeurs - il y a généralement ce n'est tout simplement pas une façon fiable d'alias des valeurs au-delà des limites de la portée!
pourquoi je considère que les références C++ sont utiles
venant d'un arrière-plan C, les références C++ peuvent sembler un concept un peu idiot, mais il faut quand même les utiliser au lieu de pointeurs lorsque c'est possible: l'indication automatique est pratique, et les références deviennent particulièrement utiles quand on traite de RAII - mais pas à cause d'une perception avantage de sécurité, mais plutôt parce qu'ils rendent l'écriture du code idiomatique moins embarrassante.
RAII est l'un des concepts centraux de C++, mais il interagit de façon non-triviale avec la sémantique de copie. Les objets de passage par référence permet d'éviter ces problèmes comme aucune copie est impliqué. Si les références n'étaient pas présentes dans la langue, vous auriez à utiliser des pointeurs à la place, qui sont plus difficiles à utiliser, violant ainsi le principe de conception de la langue que la solution de la meilleure pratique devrait être plus facile que les solutions de rechange.
si vous voulez être vraiment pédant, il y a une chose que vous pouvez faire avec une référence que vous ne pouvez pas faire avec un pointeur: prolonger la durée de vie d'un objet temporaire. En C++ , si vous liez une référence const à un objet temporaire, la durée de vie de cet objet devient la durée de vie de la référence.
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
dans cet exemple, s3_copy copie l'objet temporaire qui résulte de la concaténation. Tandis que s3_reference devient essentiellement l'objet temporaire. C'est vraiment une référence à un objet temporaire qui a maintenant la même durée de vie que la référence.
si vous essayez ceci sans le const
, il ne devrait pas être compilé. Vous ne pouvez pas lier une référence non-const à un objet temporaire, ni prendre son adresse pour cette matière.
Contrairement à l'opinion populaire, il est possible d'avoir une référence NULL.
int * p = NULL;
int & r = *p;
r = 1; // crash! (if you're lucky)
Accordée, il est beaucoup plus difficile à faire avec une référence - mais si vous réussissez, vous allez arracher les cheveux en essayant de les trouver. Les références sont et non intrinsèquement sécuritaires en C++!
techniquement, il s'agit d'une référence non valide , et non d'une référence nulle. C++ ne supporte pas les références nulles en tant que concept comme vous peut trouver dans d'autres langues. Il existe d'autres types de références non valides. tout référence non valide soulève le spectre de comportement non défini , tout comme l'utilisation d'un pointeur non valide.
l'erreur réelle est dans le déréférencement du pointeur nul, avant l'affectation à une référence. Mais je ne suis pas au courant d'aucun compilateur qui va générer des erreurs sur cette condition - l'erreur se propage à un point plus loin dans le code. C'est ce qui rend ce problème si insidieux. La plupart du temps, si vous déréférenciez un pointeur NULL, vous vous écrasez juste à cet endroit et il ne faut pas beaucoup de débogage pour le comprendre.
Mon exemple ci-dessus est court et artificiel. Voici un exemple plus réaliste.
class MyClass
{
...
virtual void DoSomething(int,int,int,int,int);
};
void Foo(const MyClass & bar)
{
...
bar.DoSomething(i1,i2,i3,i4,i5); // crash occurs here due to memory access violation - obvious why?
}
MyClass * GetInstance()
{
if (somecondition)
return NULL;
...
}
MyClass * p = GetInstance();
Foo(*p);
je tiens à répéter que la seule façon d'obtenir une référence nulle est par le code malformé, et une fois que vous l'avez, vous obtenez un comportement non défini. Il jamais fait du sens pour vérifier une référence nulle; par exemple, vous pouvez essayer if(&bar==NULL)...
mais le compilateur pourrait optimiser la déclaration hors de l'existence! Une référence valide ne peut jamais être nulle, donc de la vue du compilateur la comparaison est toujours fausse, et il est libre d'éliminer la clause if
comme code mort - c'est l'essence du comportement non défini.
la bonne façon de rester en dehors des problèmes est d'éviter de déréférencier un pointeur nul à créer une référence. Voici une façon automatisée d'accomplir ceci.
template<typename T>
T& deref(T* p)
{
if (p == NULL)
throw std::invalid_argument(std::string("NULL reference"));
return *p;
}
MyClass * p = GetInstance();
Foo(deref(p));
Pour un ancien de regarder ce problème de quelqu'un avec de meilleures compétences en écriture, voir Null Références de Jim Hyslop et Herb Sutter.
pour un autre exemple des dangers de déréférencement d'un pointeur nul, voir exposant un comportement non défini lors d'une tentative de code de port à une autre plate-forme par Raymond Chen.
vous avez oublié la partie la plus importante:
membre d'accès avec des pointeurs utilise ->
membre-accès avec références utilise .
foo.bar
est clairement supérieur à foo->bar
de la même manière que vi est clairement supérieur à Emacs :-)
en dehors de sucre syntaxique, une référence est un const
pointeur ( pas pointeur vers un const
). Vous devez établir à quoi il se réfère lorsque vous déclarez la variable de référence, et vous ne pouvez pas la changer plus tard.
mise à Jour: maintenant que j'y pense un peu plus, il existe une différence importante.
la cible d'un pointeur de const peut être remplacée en prenant son adresse et en utilisant un jet de const.
La cible d'une référence ne peut être remplacée autrement que par UB.
cela devrait permettre au compilateur de faire plus d'optimisation sur une référence.
en Fait, une référence n'est pas vraiment comme un pointeur.
un compilateur conserve des" références " à des variables, associant un nom à une adresse mémoire; c'est son travail de traduire n'importe quel nom de variable à une adresse mémoire lors de la compilation.
quand vous créez une référence, vous dites seulement au compilateur que vous assignez un autre nom à la variable pointeur; c'est pourquoi les références ne peuvent pas "pointer vers null", parce qu'une variable ne peut pas être, et ne pas être.
Les pointeurssont des variables; ils contiennent l'adresse d'une autre variable, ou peuvent être nuls. L'important est qu'un pointeur a une valeur, alors qu'une référence n'a qu'une variable qu'elle renvoie.
maintenant une explication du code réel:
int a = 0;
int& b = a;
ici, vous ne créez pas une autre variable qui pointe vers a
; vous ajoutez simplement un autre nom au contenu de la mémoire contenant la valeur de a
. Cette mémoire il a maintenant deux noms, a
et b
, et il peut être adressé en utilisant l'un ou l'autre nom.
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
lors de l'appel d'une fonction, le compilateur génère habituellement des espaces de mémoire pour les arguments à copier. La fonction signature définit les espaces qui doivent être créés et donne le nom qui doit être utilisé pour ces espaces. Déclarer un paramètre par référence indique au compilateur d'utiliser la variable d'entrée de l'espace de mémoire au lieu d'allouer une nouvelle mémoire l'espace lors de l'appel de méthode. Il peut sembler étrange de dire que votre fonction manipulera directement une variable déclarée dans la portée d'appel, mais rappelez-vous que lors de l'exécution du code compilé, il n'y a plus de portée; il n'y a que de la mémoire plate, et votre code de fonction pourrait manipuler n'importe quelle variable.
maintenant, il peut y avoir des cas où votre compilateur ne peut pas connaître la référence lors de la compilation, comme lors de l'utilisation d'une variable externe. Ainsi, une référence peut ou ne pas être implémenté comme un pointeur dans le code sous-jacent. Mais dans les exemples que je vous ai donnés, il ne sera probablement pas mis en œuvre avec un pointeur.
sont très similaires aux pointeurs, mais elles sont spécifiquement conçues pour être utiles pour optimiser les compilateurs.
- les références sont conçues de telle sorte qu'il soit beaucoup plus facile pour le compilateur de tracer les alias de référence quelles variables. Deux caractéristiques majeures sont très importantes: Pas de "arithmétique de référence" et pas de réattribution des références. Ceux-ci permettent au compilateur de comprendre quelles références alias quelles variables au moment de la compilation. Les références
- sont autorisées à renvoyer à des variables qui n'ont pas d'adresse mémoire, telles que celles que le compilateur choisit de mettre dans des registres. Si vous prenez l'adresse d'une variable locale, c'est très dur pour le compilateur de le mettre dans un registre.
comme exemple:
void maybeModify(int& x); // may modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// This function is designed to do something particularly troublesome
// for optimizers. It will constantly call maybeModify on array[0] while
// adding array[1] to array[2]..array[size-1]. There's no real reason to
// do this, other than to demonstrate the power of references.
for (int i = 2; i < (int)size; i++) {
maybeModify(array[0]);
array[i] += array[1];
}
}
un compilateur d'optimisation peut réaliser que nous accédons à un[0] et un[1] assez nombreux. Il serait l'amour pour optimiser l'algorithme de:
void hurtTheCompilersOptimizer(short size, int array[])
{
// Do the same thing as above, but instead of accessing array[1]
// all the time, access it once and store the result in a register,
// which is much faster to do arithmetic with.
register int a0 = a[0];
register int a1 = a[1]; // access a[1] once
for (int i = 2; i < (int)size; i++) {
maybeModify(a0); // Give maybeModify a reference to a register
array[i] += a1; // Use the saved register value over and over
}
a[0] = a0; // Store the modified a[0] back into the array
}
pour faire une telle optimisation, il doit prouver que rien ne peut changer le tableau[1] pendant l'appel. C'est assez facile à faire. je n'est jamais inférieur à 2, tableau[i] ne peut jamais se référer à tableau[1]. maybeModify () est donné a0 comme référence (aliasing array[0]). Parce qu'il n'y a pas d'arithmétique" de référence", le compilateur doit juste prouver que maybeModify n'obtient jamais l'adresse de x, et il a prouvé que rien ne change le tableau[1].
Elle aussi doit prouver qu'il n'existe aucun moyen d'un futur appel pouvait lire/écrire un[0], alors que nous avons un registre temporaire copie dans a0. Ceci est souvent trivial à prouver, car dans de nombreux cas il est évident que la référence n'est jamais stockée dans une structure permanente comme une instance de classe.
Maintenant faire la même chose avec des pointeurs
void maybeModify(int* x); // May modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// Same operation, only now with pointers, making the
// optimization trickier.
for (int i = 2; i < (int)size; i++) {
maybeModify(&(array[0]));
array[i] += array[1];
}
}
le comportement est le même; seulement maintenant il est beaucoup plus difficile de prouver que maybeModify ne modifie jamais array[1], parce que nous avons déjà donné un pointeur; le chat est sorti du sac. Maintenant il doit faire la preuve beaucoup plus difficile: une analyse statique de maybeModify pour prouver qu'il n'écrit jamais à &x + 1. Il doit également prouver qu'il n'a jamais enregistre un pointeur qui peut se référer à tableau[0], qui est tout aussi délicate.
compilateurs modernes sont de mieux en mieux à l'analyse statique, mais il est toujours agréable de les aider et d'utiliser des références.
bien sûr, à moins qu'il ne soit aussi intelligent optimisations, compilateurs vont en effet transformer les références en pointeurs si nécessaire.
EDIT: cinq ans après avoir posté cette réponse, j'ai trouvé une différence technique réelle où les références sont différentes que juste une façon différente de regarder le même concept d'adressage. Les références peuvent modifier la durée de vie des objets temporaires d'une manière que les pointeurs ne peuvent pas.
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
normalement des objets temporaires tels que celui créé par l'appel à createF(5)
sont détruits à la fin de l'expression. Cependant, en liant cet objet à une référence, ref
, C++ prolongera la durée de vie de cet objet temporaire jusqu'à ce que ref
sorte du champ d'application.
bien que les références et les pointeurs soient utilisés pour accéder indirectement à une autre valeur, il y a deux différences importantes entre les références et les pointeurs. La première est qu'une référence se réfère toujours à un objet: C'est une erreur de définir une référence sans l'initialiser. Le comportement de l'assignation est la deuxième différence importante: assigner à une référence change l'objet auquel la référence est liée; il ne rebinde pas la référence à un autre objet. Une fois initialisé, un la référence renvoie toujours au même objet sous-jacent.
considérez ces deux fragments de programme. Dans le premier, nous assignons un pointeur à un autre:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
après la cession, ival, l'objet adressé par pi reste inchangé. L'affectation change la valeur de pi, ce qui le fait pointer vers un objet différent. Considérons maintenant un programme similaire qui attribue deux références:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
Cette mission changements ival, l' valeur référencée par ri, et non la référence elle-même. Après l'assignation, les deux références font toujours référence à leurs objets originaux, et la valeur de ces objets est maintenant la même.
il y a une différence sémantique qui peut sembler ésotérique si vous n'êtes pas familier avec l'étude des langages informatiques de façon abstraite ou même académique.
au plus haut niveau, l'idée des références est qu'elles sont des"alias" transparents. Votre ordinateur peut utiliser une adresse pour les faire fonctionner, mais vous n'êtes pas censé vous en inquiéter: vous êtes censé les considérer comme "juste un autre nom" pour un objet existant et la syntaxe reflète cela. Ils sont plus strict que les pointeurs, de sorte que votre compilateur peut vous avertir de façon plus fiable lorsque vous êtes sur le point de créer une référence pendante, que lorsque vous êtes sur le point de créer un pointeur pendant.
au-delà de cela, il y a bien sûr quelques différences pratiques entre les pointeurs et les références. La syntaxe pour les utiliser est évidemment différente, et vous ne pouvez pas "replacer" des références, avoir des références au néant, ou avoir des pointeurs vers des références.
Une référence est un alias pour une autre variable tandis que un pointeur détient l'adresse mémoire d'une variable. Les références sont généralement utilisées comme paramètres de fonction de sorte que l'objet passé n'est pas la copie mais l'objet lui-même.
void fun(int &a, int &b); // A common usage of references.
int a = 0;
int &b = a; // b is an alias for a. Not so common to use.
peu importe l'espace qu'il prend, puisque vous ne pouvez pas voir d'effet secondaire (sans exécuter de code) de l'espace qu'il prendrait.
d'autre part, une différence majeure entre les références et les pointeurs est que les temporairesaffectés aux références const vivent jusqu'à ce que la référence const sorte du champ d'application.
par exemple:
class scope_test
{
public:
~scope_test() { printf("scope_test done!\n"); }
};
...
{
const scope_test &test= scope_test();
printf("in scope\n");
}
affichera:
in scope
scope_test done!
C'est le mécanisme de langage qui permet à ScopeGuard de fonctionner.
Ceci est basé sur le tutoriel . Ce qui est écrit le rend plus clair:
>>> The address that locates a variable within memory is
what we call a reference to that variable. (5th paragraph at page 63)
>>> The variable that stores the reference to another
variable is what we call a pointer. (3rd paragraph at page 64)
simplement pour se rappeler que,
>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)
de plus, comme nous pouvons nous référer à presque n'importe quel tutoriel pointeur, un pointeur est un objet qui est supporté par pointeur arithmétique qui rend pointeur semblable à un tableau.
regardez la déclaration suivante,
int Tom(0);
int & alias_Tom = Tom;
alias_Tom
peut être interprété comme un alias of a variable
(différent de typedef
, qui est alias of a type
) Tom
. Il est également correct d'oublier que la terminologie d'une telle déclaration est de créer une référence de Tom
.
Une référence n'est pas un autre nom donné à la mémoire. C'est un immuable pointeur qui est automatiquement dé-référencé sur l'utilisation. Fondamentalement, il se résume à:
int& j = i;
en interne devient
int* const j = &i;
Une référence à un pointeur est possible en C++, mais l'inverse n'est pas possible signifie un pointeur vers une référence n'est pas possible. Une référence à un pointeur fournit une syntaxe plus claire pour modifier le pointeur. Regardez cet exemple:
#include<iostream>
using namespace std;
void swap(char * &str1, char * &str2)
{
char *temp = str1;
str1 = str2;
str2 = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap(str1, str2);
cout<<"str1 is "<<str1<<endl;
cout<<"str2 is "<<str2<<endl;
return 0;
}
et considérer la version C du programme ci-dessus. En C, vous devez utiliser pointeur de pointeur (plusieurs indirection), et il conduit à la confusion et le programme peut sembler compliqué.
#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
char *temp = *str1_ptr;
*str1_ptr = *str2_ptr;
*str2_ptr = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap1(&str1, &str2);
printf("str1 is %s, str2 is %s", str1, str2);
return 0;
}
Pour de plus amples renseignements sur la référence à pointer:
comme je l'ai dit, un pointeur vers une référence n'est pas possible. Essayez le programme suivant:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
il y a une différence fondamentale entre les pointeurs et les références que je n'ai vu personne mentionner: les références permettent la sémantique pass-by-reference dans les arguments de fonction. Les indicateurs, bien qu'ils ne soient pas visibles au premier abord, ne le font pas: ils fournissent seulement la sémantique de passe-par-valeur. Cela a été très bien décrit dans cet article .
en ce qui Concerne, et rzej
j'utilise des références sauf si j'en ai besoin:
-
pointeurs Null peut être utilisé comme un valeur sentinelle, souvent un moyen bon marché de éviter les surcharges de fonctions ou l'utilisation de un bool.
-
Vous pouvez faire de l'arithmétique sur un pointeur. Par exemple,
p += offset;
au risque d'ajouter à la confusion, je veux ajouter quelques entrées, je suis sûr que cela dépend principalement de la façon dont le compilateur implémente les références, mais dans le cas de gcc l'idée qu'une référence ne peut pointer qu'une variable sur la pile n'est pas vraiment correcte, prenez par exemple:
#include <iostream>
int main(int argc, char** argv) {
// Create a string on the heap
std::string *str_ptr = new std::string("THIS IS A STRING");
// Dereference the string on the heap, and assign it to the reference
std::string &str_ref = *str_ptr;
// Not even a compiler warning! At least with gcc
// Now lets try to print it's value!
std::cout << str_ref << std::endl;
// It works! Now lets print and compare actual memory addresses
std::cout << str_ptr << " : " << &str_ref << std::endl;
// Exactly the same, now remember to free the memory on the heap
delete str_ptr;
}
qui produit ceci:
THIS IS A STRING
0xbb2070 : 0xbb2070
si vous remarquez que même les adresses mémoire sont exactement les mêmes, ce qui signifie que la référence est pointant une variable sur le tas! Maintenant, si vous voulez vraiment obtenir bizarre, cela fonctionne aussi:
int main(int argc, char** argv) {
// In the actual new declaration let immediately de-reference and assign it to the reference
std::string &str_ref = *(new std::string("THIS IS A STRING"));
// Once again, it works! (at least in gcc)
std::cout << str_ref;
// Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
delete &str_ref;
/*And, it works, because we are taking the memory address that the reference is
storing, and deleting it, which is all a pointer is doing, just we have to specify
the address with '&' whereas a pointer does that implicitly, this is sort of like
calling delete &(*str_ptr); (which also compiles and runs fine).*/
}
qui produit ceci:
THIS IS A STRING
donc une référence est un pointeur sous le capot, ils sont tous les deux en train de stocker une adresse mémoire, où l'adresse pointe vers est sans importance, que pensez-vous qu'il se passerait si j'appelais std::cout << str_ref; après avoir appelé delete &str_ref? Évidemment, ça se compile bien, mais ça provoque une segmentation. faute à l'exécution parce qu'il n'est plus pointé vers une variable valide, nous avons essentiellement une référence cassée qui existe encore (jusqu'à ce qu'elle tombe en dehors de la portée), mais qui est inutile.
en d'autres termes, une référence n'est rien d'autre qu'un pointeur dont la mécanique du pointeur est abstraite, ce qui le rend plus sûr et plus facile à utiliser (Pas de calcul de pointeur accidentel, pas de mélange).'et '->', etc.), en supposant que vous ne tentez pas n'importe quel non-sens comme mes exemples ci-dessus;)
maintenant indépendamment de de la façon dont un compilateur gère les références, il aura toujours avoir une sorte de pointeur sous le capot, parce qu'une référence doit se référer à une variable spécifique à une adresse mémoire spécifique pour qu'il fonctionne comme prévu, il n'y a pas de contourner cela (d'où le terme "référence").
la seule règle importante qu'il est important de se rappeler avec des références est qu'elles doivent être définies au moment de déclaration (à l'exception d'une référence dans un en-tête, dans ce cas, il doit être définie dans le constructeur, après que l'objet c'est contenue dans le construit, il est trop tard pour le définir).
rappelez-vous, mes exemples ci-dessus sont juste que, des exemples démontrant ce qu'est une référence, vous ne voudriez jamais utiliser une référence de cette façon! Pour une utilisation correcte d'une référence il y a beaucoup de réponses ici déjà qui a frappé le clou sur la tête
une autre différence est que vous pouvez avoir des pointeurs vers un type de vide (et cela signifie pointer vers n'importe quoi) mais les références au vide sont interdites.
int a;
void * p = &a; // ok
void & p = a; // forbidden
Je ne peux pas dire que je suis vraiment content de cette différence particulière. Je préférerais qu'il soit permis avec la référence de signification à n'importe quoi avec une adresse et autrement le même comportement pour les références. Il permettrait de définir quelques équivalents des fonctions de bibliothèque C comme memcpy en utilisant des références.
les références et les pointeurs peuvent être utilisés pour changer les variables locales d'une fonction à l'intérieur d'une autre fonction. Les deux peuvent également être utilisés pour sauvegarder la copie de gros objets lorsqu'ils sont passés en argument à des fonctions ou retournés à partir de fonctions, pour obtenir un gain d'efficacité. Malgré les similitudes ci-dessus, il y a des différences suivantes entre les références et les pointeurs.
les références sont moins puissantes que les pointeurs
1) Une fois a la référence est créée, elle ne peut pas être faite plus tard à la référence d'un autre objet; elle ne peut pas être reseated. Cela se fait souvent avec des pointeurs.
2) les références ne peuvent être nulles. Les pointeurs sont souvent rendus nuls pour indiquer qu'ils ne pointent pas vers une chose valide.
3) une référence doit être initialisée lorsqu'elle est déclarée. Il n'y a pas de telle restriction avec les pointeurs
en raison des limitations ci-dessus, les références en C++ ne peuvent pas être utilisé pour mettre en œuvre des structures de données comme la liste liée, L'arbre, etc. En Java, les références n'ont pas les restrictions ci-dessus, et peuvent être utilisées pour implémenter toutes les structures de données. Les références étant plus puissantes en Java, C'est la principale raison pour laquelle Java n'a pas besoin de pointeurs.
les références sont plus sûres et plus faciles à utiliser:
1) plus sûr: puisque les références doivent être initialisées, les références sauvages comme les indicateurs sauvages sont peu susceptibles d'exister. Il est encore possible d'avoir des références qui ne se réfèrent pas à un emplacement valide
2) Plus Facile à utiliser: les références n'ont pas besoin d'opérateur dereferencing pour accéder à la valeur. Ils peuvent être utilisés comme des variables normales. ‘&’ opérateur n'est nécessaire qu'au moment de la déclaration. De plus, les membres d'une référence d'objet peuvent être consultés avec l'opérateur dot (‘.’ ), contrairement aux pointeurs où arrow operator ( - > ) est nécessaire pour accéder aux membres.
ensemble avec ce qui précède raisons, il y a peu d'endroits comme argument de constructeur de copie où le pointeur ne peut pas être utilisé. Référence doit être utilisé passer l'argument dans le constructeur de copie. De même, les références doivent être utilisées pour surcharger certains opérateurs comme ++ .
Aussi, une référence qui est un paramètre à une fonction inline peuvent être traités différemment d'un pointeur.
void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
int testptr=0;
increment(&testptr);
}
void increftest()
{
int testref=0;
increment(testref);
}
beaucoup de compilateurs lors de la mise en ligne de la version de pointeur on va en fait forcer une écriture à la mémoire (nous prenons l'adresse explicitement). Cependant, ils quittent la référence dans un registre qui est plus optimal.
bien sûr, pour les fonctions qui ne sont pas insérées le pointeur et la référence générer le même code et il est toujours préférable de passer les intrinsèques par valeur plutôt que par référence s'ils ne sont pas modifiés et retournés par la fonction.
, Ce programme pourrait aider à comprendre la réponse de la question. C'est un programme simple de référence "j" et un "pointeur ptr" pointant vers la variable "x".
#include<iostream>
using namespace std;
int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"
cout << "x=" << x << endl;
cout << "&x=" << &x << endl;
cout << "j=" << j << endl;
cout << "&j=" << &j << endl;
cout << "*ptr=" << *ptr << endl;
cout << "ptr=" << ptr << endl;
cout << "&ptr=" << &ptr << endl;
getch();
}
lancez le programme et regardez la sortie et vous comprendrez.
aussi, 10 minutes de plus et regardez cette vidéo: https://www.youtube.com/watch?v=rlJrrGV0iOg
une autre utilisation intéressante des références est de fournir un argument par défaut d'un type défini par l'utilisateur:
class UDT
{
public:
UDT() : val_d(33) {};
UDT(int val) : val_d(val) {};
virtual ~UDT() {};
private:
int val_d;
};
class UDT_Derived : public UDT
{
public:
UDT_Derived() : UDT() {};
virtual ~UDT_Derived() {};
};
class Behavior
{
public:
Behavior(
const UDT &udt = UDT()
) {};
};
int main()
{
Behavior b; // take default
UDT u(88);
Behavior c(u);
UDT_Derived ud;
Behavior d(ud);
return 1;
}
Le défaut de saveur utilise la "liaison const référence à un temporaire' aspect de références.
j'ai l'impression qu'il y a encore un autre point qui n'a pas été abordé ici.
Contrairement aux pointeurs, les références sont syntaxiquement équivalent à l'objet auquel elles se rapportent, c.-à-d. toute opération qui peut être appliquée à un objet fonctionne pour une référence, et avec la même syntaxe exacte (l'exception est bien sûr l'initialisation).
bien que cela puisse sembler superficiel, je crois que cette propriété est cruciale pour un nombre de fonctionnalités C++, par exemple:
-
modèles . Puisque les paramètres du modèle sont dactylographiés en duck, les propriétés syntaxiques d'un type sont tout ce qui importe, donc souvent le même modèle peut être utilisé à la fois avec
T
etT&
.
(oustd::reference_wrapper<T>
qui repose encore sur une distribution implicite àT&
)
Modèles qui couvrent à la foisT&
etT&&
sont encore plus fréquents. -
Lvalues . Considérez l'énoncé
str[0] = 'X';
sans les références il ne fonctionnerait que pour C-strings (char* str
). Retourner le caractère par référence permet à des classes définies par l'utilisateur d'avoir la même notation. -
Copie constructeurs . Syntaxiquement, il est logique de passer des objets aux constructeurs de copie, et non des pointeurs vers des objets. Mais il n'y a tout simplement aucun moyen pour un constructeur de copie pour prendre un objet par valeur d'un appel récursif de la même constructeur de copie. Cela laisse les références comme seule option ici.
-
la surcharge de l'Opérateur . Avec des références, il est possible d'introduire indirectement à un appel de l'opérateur, par exemple,
operator+(const T& a, const T& b)
, tout en conservant la même notation d'infix. Cela fonctionne également pour les réguliers surchargés fonction.
ces points confèrent une autonomie considérable au C++ et à la bibliothèque standard, ce qui en fait une propriété majeure des références.
peut-être que certaines métaphores aideront; Dans le contexte de votre screenspace de bureau -
- une référence vous demande de spécifier une fenêtre réelle.
- un pointeur exige l'emplacement d'un morceau d'espace sur l'écran que vous assurez qu'il contiendra zéro ou plus instances de ce type de fenêtre.
différence entre pointeur et référence
un pointeur peut être initialisé à 0 et une référence non. En fait, une référence doit aussi se référer à un objet, mais un pointeur peut être le pointeur nul:
int* p = 0;
mais nous ne pouvons pas avoir int& p = 0;
et aussi int& p=5 ;
.
en fait pour le faire correctement, nous devons avoir déclaré et défini un objet à la première puis nous pouvons faire une référence à cet objet, de sorte que la correcte la mise en œuvre du code précédent sera:
Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;
un autre point important est que nous pouvons faire la déclaration du pointeur sans initialisation cependant aucune telle chose ne peut être faite en cas de référence qui doit faire une référence toujours à variable ou objet. Cependant une telle utilisation d'un pointeur est risquée donc généralement nous vérifions si le pointeur est effectivement pointé vers quelque chose ou non. Dans le cas d'une référence, un tel contrôle n'est pas nécessaire, car nous savons déjà le renvoi à un objet lors de la déclaration est obligatoire.
une autre différence est que le pointeur peut pointer vers un autre objet mais la référence se réfère toujours au même objet, prenons cet exemple:
Int a = 6, b = 5;
Int& rf = a;
Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.
rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased
un autre point: quand nous avons un modèle comme un modèle STL ce genre de modèle de classe retournera toujours une référence, pas un pointeur, pour faciliter la lecture ou pour assigner une nouvelle valeur en utilisant l'opérateur []:
Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
la différence est que la variable de pointeur non-constante(à ne pas confondre avec un pointeur vers constant) peut être changée à un certain moment au cours de l'exécution du programme, nécessite la sémantique de pointeur à utiliser(&,*) les opérateurs, tandis que les références peuvent être définies lors de l'initialisation seulement(c'est pourquoi vous pouvez les définir dans la liste d'initialiseur de constructeur seulement, mais pas autrement) et utiliser la sémantique d'accès de valeur ordinaire. Fondamentalement, des références ont été introduites pour permettre le soutien des opérateurs en surcharge comme j'ai eu lu dans un très vieux livre. Comme quelqu'un a déclaré dans ce thread - pointer peut être réglé à 0 ou n'importe quelle valeur que vous voulez. 0 (NULL, nullptr) signifie que le pointeur est initialisé avec rien. C'est une erreur de déréférencement de pointeur null. Mais en fait, le pointeur peut contenir une valeur qui ne pointent pas vers une bonne mémoire. Références à leur tour essayer de ne pas permettre à un utilisateur d'initialiser une référence à quelque chose qui ne peut pas être référencé en raison du fait que vous fournissez toujours valeur de type. Bien qu'il y ait beaucoup de façons de faire variable de référence être initialisé à un mauvais emplacement de mémoire - il est préférable pour vous de ne pas creuser cette profondeur dans les détails. Au niveau de la machine le pointeur et le travail de référence uniformément-par les pointeurs. Disons que les références essentielles sont du sucre syntaxique. les références de valeur sont différentes de ceci - elles sont naturellement des objets empilés/tas.
il y a une différence non-technique très importante entre les pointeurs et les références: un argument passé à une fonction par pointeur est beaucoup plus visible qu'un argument passé à une fonction par référence non-const. Par exemple:
void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);
void bar() {
std::string x;
fn1(x); // Cannot modify x
fn2(x); // Cannot modify x (without const_cast)
fn3(x); // CAN modify x!
fn4(&x); // Can modify x (but is obvious about it)
}
de retour en C, Un appel qui ressemble à fn(x)
ne peut être passé que par la valeur, donc il ne peut certainement pas modifier x
; pour modifier un argument, vous auriez besoin de passer un pointeur fn(&x)
. Donc si un argument elle n'a pas été précédée d'un &
vous saviez qu'il ne serait pas modifié. (L'inverse, &
signifie modifié, n'était pas vrai parce que vous auriez parfois à passer de grandes structures en lecture seule par const
pointeur.)
certains font valoir qu'il s'agit d'une caractéristique tellement utile lors de la lecture de code, que les paramètres de pointeur devraient toujours être utilisés pour les paramètres modifiables plutôt que les références non - const
, même si la fonction ne s'attend jamais à un nullptr
. C'est à dire ceux les gens soutiennent que les signatures de fonction comme fn3()
ci-dessus ne devraient pas être autorisés. les lignes directrices du style C++ de Google en sont un exemple.