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?
int &x = *(int*)0;
sur gcc. La référence peut en effet pointer vers NULL.
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?
int &x = *(int*)0;
sur gcc. La référence peut en effet pointer vers NULL.
Réponses:
Un pointeur peut être réaffecté:
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 pas et doit être affectée à l'initialisation:
int x = 5;
int y = 6;
int &r = x;
Un pointeur a sa propre adresse mémoire et sa propre taille sur la pile (4 octets sur x86), tandis qu'une référence partage la même adresse mémoire (avec la variable d'origine) mais prend également un peu d'espace sur la pile. Puisqu'une référence a la même adresse que la variable d'origine elle-même, il est prudent de penser à une référence comme un autre nom pour la même variable. Remarque: ce qu'un pointeur pointe vers peut être sur la pile ou le tas. Idem une référence. Ma réclamation dans cette déclaration n'est pas qu'un pointeur doit pointer vers la pile. Un pointeur est juste une variable qui contient une adresse mémoire. Cette variable est sur la pile. Puisqu'une référence a son propre espace sur la pile, et puisque l'adresse est la même que la variable qu'elle référence. Plus sur pile vs tas. Cela implique qu'il existe une adresse réelle d'une référence que le compilateur ne vous dira pas.
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
Vous pouvez avoir des pointeurs vers 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'indirection.
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);
Un pointeur peut être attribué nullptr
directement, contrairement à la référence. Si vous essayez assez fort et que vous savez comment faire, vous pouvez faire l'adresse d'une référence nullptr
. De même, si vous essayez assez fort, vous pouvez avoir une référence à un pointeur, puis cette référence peut contenir nullptr
.
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 parcourir un tableau; vous pouvez utiliser ++
pour aller à l'élément suivant vers lequel pointe un pointeur et + 4
pour aller au 5ème élément. Quelle que soit la taille de l'objet vers lequel pointe le pointeur.
Un pointeur doit être déréférencé avec *
pour accéder à l'emplacement de mémoire vers lequel il pointe, tandis qu'une référence peut être utilisée directement. Un pointeur vers une classe / structure utilise ->
pour accéder à ses membres tandis qu'une référence utilise un .
.
Les références ne peuvent pas être insérées dans un tableau, tandis que les pointeurs peuvent l'être (mentionné par l'utilisateur @litb)
Les références const peuvent être liées à des temporaires. Les pointeurs ne peuvent pas (non sans indirection):
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
Cela rend l' const&
utilisation des listes d'arguments plus sûre, etc.
Une référence peut être considérée comme un pointeur constant (à ne pas confondre avec un pointeur vers une valeur constante!) Avec indirection 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 d'obtenir l'adresse d'une référence - l'opérateur d'adresse retournera plutôt l'adresse de la valeur référencée - ni de faire de l'arithmétique sur les références.
Les programmeurs C peuvent détester les références C ++ car cela ne sera plus évident lorsque l'indirection se produit ou si un argument est passé par valeur ou par pointeur sans regarder les signatures de fonction.
Les programmeurs C ++ pourraient ne pas aimer utiliser les pointeurs car ils sont considérés comme dangereux - bien que les références ne soient pas vraiment plus sûres que les pointeurs constants, sauf dans les cas les plus triviaux - manquent de la commodité de l'indirection automatique et portent une connotation sémantique différente.
Considérez l'instruction suivante de la FAQ C ++ :
Même si une référence est souvent implémentée à l'aide d'une adresse dans le langage d'assemblage sous-jacent, veuillez ne pas considérer une référence comme un pointeur à la recherche d'un objet. Une référence est l'objet. Ce n'est pas un pointeur sur l'objet, ni une copie de l'objet. Il est l'objet.
Mais si une référence était vraiment l'objet, comment pourrait-il y avoir des références pendantes? Dans les langages non gérés, il est impossible que les références soient plus sûres que les pointeurs - il n'y a généralement pas de moyen d'alias de manière fiable à travers les limites de la portée!
Venant d'un arrière-plan C, les références C ++ peuvent ressembler à un concept quelque peu idiot, mais il faut quand même les utiliser au lieu de pointeurs lorsque cela est possible: l'indirection automatique est pratique, et les références deviennent particulièrement utiles lorsqu'il s'agit de RAII - mais pas à cause d'une sécurité perçue avantage, mais plutôt parce qu'ils rendent l'écriture de code idiomatique moins gênante.
RAII est l'un des concepts centraux du C ++, mais il interagit de manière non triviale avec la copie de la sémantique. Le passage d'objets par référence évite ces problèmes car aucune copie n'est impliquée. Si les références n'étaient pas présentes dans la langue, vous devriez utiliser des pointeurs à la place, qui sont plus encombrants à utiliser, violant ainsi le principe de conception de la langue selon lequel la meilleure solution devrait être plus facile que les alternatives.
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 résultant de la concaténation. Alors 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, 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 d'ailleurs.
const &
liaison, et uniquement lorsque la référence sort du domaine d'application, le destructeur du type référencé réel (par rapport au type de référence, qui pourrait être une base) est appelé. Puisqu'il s'agit d'une référence, aucun découpage n'aura lieu entre les deux.
Animal x = fast ? getHare() : getTortoise()
alors vous x
serez confronté au problème de découpage classique, tandis que Animal& x = ...
fonctionnera correctement.
Outre le sucre syntaxique, une référence est un const
pointeur ( pas un pointeur sur a const
). Vous devez établir à quoi il fait référence lorsque vous déclarez la variable de référence et vous ne pouvez pas la modifier ultérieurement.
Mise à jour: maintenant que j'y pense un peu plus, il y a une différence importante.
La cible d'un pointeur const peut être remplacée en prenant son adresse et en utilisant un transtypage const.
La cible d'une référence ne peut en aucun cas être remplacée par UB.
Cela devrait permettre au compilateur de faire plus d'optimisation sur une référence.
T* const
sucre syntaxique différent (cela élimine beaucoup de * et de votre code).
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
c'est OK.
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)
Certes, c'est beaucoup plus difficile à faire avec une référence - mais si vous y parvenez, vous vous arracherez les cheveux en essayant de la trouver. Les références ne sont pas intrinsèquement sûres en C ++!
Techniquement, il s'agit d'une référence non valide , pas d'une référence nulle. C ++ ne prend pas en charge les références nulles en tant que concept, comme vous pouvez le trouver dans d'autres langages. Il existe également d'autres types de références invalides. Toute référence invalide soulève le spectre d' un comportement indéfini , tout comme le ferait un pointeur invalide.
L'erreur réelle se trouve dans le déréférencement du pointeur NULL, avant l'affectation à une référence. Mais je ne connais aucun compilateur qui générera des erreurs à 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érencez un pointeur NULL, vous vous plantez juste à cet endroit et cela ne prend pas beaucoup de débogage pour le comprendre.
Mon exemple ci-dessus est court et artificiel. Voici un exemple plus réel.
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 le seul moyen d'obtenir une référence nulle est d'utiliser du code mal formé et une fois que vous l'avez, vous obtenez un comportement indéfini. Il n'a jamais de sens de vérifier une référence nulle; par exemple, vous pouvez essayer, if(&bar==NULL)...
mais le compilateur peut optimiser l'instruction hors de l'existence! Une référence valide ne peut jamais être NULL, donc du point de vue du compilateur, la comparaison est toujours fausse, et il est libre d'éliminer la if
clause en tant que code mort - c'est l'essence même d'un comportement non défini.
La bonne façon d'éviter les ennuis est d'éviter de déréférencer un pointeur NULL pour créer une référence. Voici un moyen automatisé d'accomplir cela.
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 regard plus ancien sur ce problème d'une personne ayant de meilleures compétences en écriture, voir les références nulles de Jim Hyslop et Herb Sutter.
Pour un autre exemple des dangers de déréférencer un pointeur nul, voir Exposer un comportement non défini lors de la tentative de portage de code vers une autre plate-forme par Raymond Chen.
Vous avez oublié la partie la plus importante:
accès membre avec pointeurs utilise ->
accès membre 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 :-)
->
fournit des références aux pointeurs, tout comme avec le pointeur lui-même.
.
et ->
a quelque chose à voir avec vi et emacs :)
.
est meilleure que l'utilisation ->
, mais tout comme vi vs emacs, c'est entièrement subjectif et vous ne pouvez rien prouver
Les références sont très similaires aux pointeurs, mais elles sont spécialement conçues pour être utiles à l'optimisation des compilateurs.
Par 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 se rendre compte que nous accédons à un [0] et un [1] tout un tas. Il aimerait optimiser l'algorithme pour:
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 effectuer une telle optimisation, il doit prouver que rien ne peut changer le tableau [1] pendant l'appel. C'est assez facile à faire. i n'est jamais inférieur à 2, donc le tableau [i] ne peut jamais faire référence au tableau [1]. peut-êtreModify () reçoit a0 comme référence (tableau d'alias [0]). Comme il n'y a pas d'arithmétique "de référence", le compilateur doit simplement prouver que peut-êtreModify n'obtient jamais l'adresse de x, et il a prouvé que rien ne change de tableau [1].
Cela doit également prouver qu'il n'y a aucun moyen qu'un futur appel puisse lire / écrire un [0] alors que nous en avons une copie de registre temporaire dans a0. C'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, faites la même chose avec les 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 peut-êtreModify ne modifie jamais le tableau [1], car nous lui 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 peut-êtreModifier pour prouver qu'il n'écrit jamais dans & x + 1. Il doit également prouver qu'il ne sauve jamais un pointeur qui peut se référer au tableau [0], qui est juste aussi délicat.
Les compilateurs modernes s'améliorent de plus en plus en analyse statique, mais il est toujours agréable de les aider et d'utiliser des références.
Bien sûr, à moins de telles optimisations intelligentes, les compilateurs transformeront en effet les références en pointeurs en cas de besoin.
EDIT: cinq ans après avoir posté cette réponse, j'ai trouvé une réelle différence technique où les références sont différentes d'une simple façon de voir 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;
};
Les objets normalement 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 ++ étendra la durée de vie de cet objet temporaire jusqu'à ce qu'il ref
soit hors de portée.
maybeModify
ne prend pas l'adresse de quoi que ce soit en rapport avec la x
preuve que de prouver qu'un tas d'arithmétique de pointeur ne se produit pas.
void maybeModify(int& x) { 1[&x]++; }
ce que les autres commentaires ci-dessus discutent
En fait, une référence n'est pas vraiment comme un pointeur.
Un compilateur conserve des "références" aux variables, associant un nom à une adresse mémoire; c'est son travail de traduire tout nom de variable en une adresse mémoire lors de la compilation.
Lorsque vous créez une référence, vous indiquez uniquement au compilateur que vous attribuez un autre nom à la variable de pointeur; c'est pourquoi les références ne peuvent pas "pointer vers null", car une variable ne peut pas être, et ne pas être.
Les pointeurs sont des variables; ils contiennent l'adresse d'une autre variable ou peuvent être nuls. L'important est qu'un pointeur ait une valeur, alors qu'une référence n'a qu'une variable qu'il référence.
Maintenant, quelques explications sur le vrai code:
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 a maintenant deux noms, a
et b
, et elle peut être adressée 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 généralement des espaces mémoire pour les arguments à copier. La signature de fonction définit les espaces à créer et donne le nom à utiliser pour ces espaces. La déclaration d'un paramètre comme référence indique simplement au compilateur d'utiliser l'espace mémoire variable d'entrée au lieu d'allouer un nouvel espace mémoire pendant 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 appelante, mais n'oubliez pas que lors de l'exécution de 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 peut ne pas être en mesure de 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 non être implémentée en tant que pointeur dans le code sous-jacent. Mais dans les exemples que je vous ai donnés, il ne sera probablement pas implémenté avec un pointeur.
Une référence ne peut jamais l'être NULL
.
void Foo::bar() { virtual_baz(); }
celui-ci. Si vous n'êtes pas conscient que les références peuvent être nulles, vous ne pouvez pas retracer le null jusqu'à son origine.
int &r=*p;
est un comportement non défini. À ce stade, vous n'avez pas de «référence pointant vers NULL», vous avez un programme qui ne peut plus être raisonné du tout .
Bien que les références et les pointeurs soient utilisés pour accéder indirectement à une autre valeur, il existe 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 d'affectation est la deuxième différence importante: l'affectation à une référence modifie l'objet auquel la référence est liée; il ne lie pas la référence à un autre objet. Une fois initialisée, une référence fait toujours référence au même objet sous-jacent.
Considérez ces deux fragments de programme. Dans le premier, nous attribuons un pointeur à un autre:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
Après l'affectation, ival, l'objet adressé par pi reste inchangé. L'affectation modifie la valeur de pi, la faisant pointer vers un autre objet. Considérons maintenant un programme similaire qui attribue deux références:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
Cette affectation change ival, la valeur référencée par ri et non la référence elle-même. Après l'affectation, les deux références se réfèrent toujours à leurs objets d'origine, et la valeur de ces objets est désormais la même.
Il existe une différence sémantique qui peut apparaître ésotérique si vous n'êtes pas familier avec l'étude des langages informatiques de manière 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 soucier: vous êtes censé les considérer comme "juste un autre nom" pour un objet existant et la syntaxe le reflète. Ils sont plus stricts que les pointeurs, de sorte que votre compilateur peut vous avertir de manière plus fiable lorsque vous êtes sur le point de créer une référence suspendue que lorsque vous êtes sur le point de créer un pointeur suspendu.
Au-delà de cela, il existe bien sûr des 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 "réinstaller" les 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 qu'un pointeur contient 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 transmis 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, car vous ne pouvez réellement voir aucun effet secondaire (sans exécuter de code) de l'espace qu'il prendrait.
D'un autre côté, une différence majeure entre les références et les pointeurs est que les temporaires affectés aux références const vivent jusqu'à ce que la référence const soit hors de portée.
Par exemple:
class scope_test
{
public:
~scope_test() { printf("scope_test done!\n"); }
};
...
{
const scope_test &test= scope_test();
printf("in scope\n");
}
imprimera:
in scope
scope_test done!
Il s'agit du 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)
Tout simplement pour s'en souvenir,
>>> 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 de pointeur, un pointeur est un objet pris en charge par l'arithmétique du pointeur qui rend le pointeur similaire à un tableau.
Regardez la déclaration suivante,
int Tom(0);
int & alias_Tom = Tom;
alias_Tom
peut être compris comme un alias of a variable
(différent avec typedef
, ce qui est alias of a type
) Tom
. Il est également bon d'oublier que la terminologie d'une telle déclaration est de créer une référence de Tom
.
nullptr
? Avez-vous réellement lu une autre partie de ce fil, ou ...?
Une référence n'est pas un autre nom donné à une mémoire. Il s'agit d'un pointeur immuable qui est automatiquement déréférencé lors de l'utilisation. Fondamentalement, cela se résume à:
int& j = i;
Il devient en interne
int* const j = &i;
const
pointeur. Cette flexibilité ne prouve pas qu'il existe une différence entre une référence et un pointeur.
Qu'est-ce qu'une référence en C ++? Une instance spécifique de type qui n'est pas un type d'objet .
Qu'est-ce qu'un pointeur en C ++? Une instance spécifique de type qui est un type d'objet .
De la définition ISO C ++ du type d'objet :
Un type d' objet est un type (éventuellement qualifié par cv ) qui n'est ni un type de fonction, ni un type de référence, ni un cv void.
Il peut être important de savoir que le type d'objet est une catégorie de niveau supérieur de l'univers de type en C ++. La référence est également une catégorie de premier niveau. Mais le pointeur ne l'est pas.
Les pointeurs et les références sont mentionnés ensemble dans le contexte du type composé . Cela est essentiellement dû à la nature de la syntaxe du déclarant héritée de (et étendu) C, qui n'a pas de références. (De plus, il y a plus d'un type de déclarant de références depuis C ++ 11, alors que les pointeurs sont toujours "unitped": &
+ &&
vs. *
) Donc rédiger un langage spécifique par "extension" avec un style similaire de C dans ce contexte est quelque peu raisonnable . (Je soutiendrai toujours que la syntaxe des déclarants gaspille beaucoup l' expressivité syntaxique , rend les utilisateurs humains et les implémentations frustrants. Ainsi, tous ne sont pas qualifiés pour être intégrésdans un nouveau design de langage. C'est un sujet totalement différent sur la conception PL, cependant.)
Sinon, il est insignifiant que les pointeurs puissent être qualifiés de types spécifiques de types avec des références ensemble. Ils partagent simplement trop peu de propriétés communes en plus de la similitude de syntaxe, il n'est donc pas nécessaire de les assembler dans la plupart des cas.
Notez que les instructions ci-dessus mentionnent uniquement les "pointeurs" et les "références" comme types. Il y a des questions intéressantes sur leurs instances (comme les variables). Il y a aussi trop d'idées fausses.
Les différences des catégories de niveau supérieur peuvent déjà révéler de nombreuses différences concrètes qui ne sont pas directement liées aux pointeurs:
cv
qualificatifs de niveau supérieur . Les références ne le peuvent pas.Quelques règles supplémentaires sur les références:
&&
paramètres (comme les "références de transfert") basées sur l'effondrement des références pendant la déduction des paramètres du modèle permettent un "transfert parfait" des paramètres.std::initializer_list
suit des règles similaires d'extension de durée de vie de référence. C'est une autre boîte de vers.Je sais que les références sont du sucre syntaxique, donc le code est plus facile à lire et à écrire.
Techniquement, c'est tout à fait faux. Les références ne sont pas le sucre syntaxique des autres fonctionnalités de C ++, car elles ne peuvent pas être remplacées exactement par d'autres fonctionnalités sans aucune différence sémantique.
(De même, les expressions lambda ne sont pas du sucre syntaxique d'aucune autre fonctionnalité en C ++ car elles ne peuvent pas être simulées avec précision avec des propriétés "non spécifiées" comme l'ordre de déclaration des variables capturées , ce qui peut être important car l'ordre d'initialisation de ces variables peut être important.)
C ++ n'a que quelques types de sucres syntaxiques au sens strict. Une instance est (héritée de C) l'opérateur intégré (non surchargé) []
, qui est défini exactement avec les mêmes propriétés sémantiques de formes spécifiques de combinaison que l'opérateur intégré unaire *
et binaire+
.
Ainsi, un pointeur et une référence utilisent tous deux la même quantité de mémoire.
La déclaration ci-dessus est tout simplement fausse. Pour éviter de telles idées fausses, regardez plutôt les règles ISO C ++:
Depuis [intro.object] / 1 :
... Un objet occupe une région de stockage dans sa période de construction, tout au long de sa vie et dans sa période de destruction. ...
De [dcl.ref] / 4 :
Il n'est pas précisé si une référence nécessite ou non un stockage.
Notez que ce sont des propriétés sémantiques .
Même si les pointeurs ne sont pas suffisamment qualifiés pour être associés à des références au sens de la conception du langage, il existe encore certains arguments qui rendent discutable le choix entre eux dans certains autres contextes, par exemple, lors du choix des types de paramètres.
Mais ce n'est pas toute l'histoire. Je veux dire, il y a plus de choses que de pointeurs vs références que vous devez considérer.
Si vous n'avez pas à vous en tenir à de tels choix trop spécifiques, dans la plupart des cas, la réponse est courte: vous n'avez pas la nécessité d'utiliser des pointeurs, donc vous ne l'avez pas . Les pointeurs sont généralement assez mauvais car ils impliquent trop de choses auxquelles vous ne vous attendez pas et ils s'appuieront sur trop d'hypothèses implicites compromettant la maintenabilité et (même) la portabilité du code. Compter inutilement sur des pointeurs est définitivement un mauvais style et cela devrait être évité dans le sens du C ++ moderne. Reconsidérez votre objectif et vous constaterez enfin que le pointeur est la caractéristique des dernières sortes dans la plupart des cas.
&
type de référence du 1er type de paramètre. (Et généralement, il doit être const
qualifié.)&&
type de référence du 1er type de paramètre. (Et généralement, il ne devrait pas y avoir de qualificatifs.)operator=
tant que fonctions membres spéciales, il faut des types de référence similaires au 1er paramètre des constructeurs copier / déplacer.++
nécessite un mannequin int
.unique_ptr
et shared_ptr
(ou même avec des homebrews par vous-même si vous souhaitez qu'ils soient opaques ), plutôt que des pointeurs bruts.std::optional
, plutôt que des pointeurs bruts.observer_ptr
dans Library Fundamental TS.Les seules exceptions ne peuvent pas être contournées dans la langue actuelle:
operator new
. (Cependant, cv - void*
est toujours assez différent et plus sûr par rapport aux pointeurs d'objets ordinaires car il exclut l'arithmétique des pointeurs inattendus, sauf si vous comptez sur une extension non conforme void*
comme GNU.)Donc, dans la pratique, la réponse est si évidente: en cas de doute, évitez les pointeurs . Vous devez utiliser des pointeurs uniquement lorsqu'il existe des raisons très explicites pour lesquelles rien d'autre n'est plus approprié. À l'exception de quelques cas exceptionnels mentionnés ci-dessus, ces choix ne sont presque toujours pas uniquement spécifiques à C ++ (mais susceptibles d'être spécifiques à l'implémentation du langage). De telles instances peuvent être:
Si vous venez de voir la question via un résultat de recherche Google (non spécifique à C ++) , il est fort probable que ce soit le mauvais endroit.
Références en C ++ est tout à fait « bizarre », car il est essentiellement pas de première classe: ils seront traités comme les objets ou les fonctions étant appelées donc ils ont aucune chance de soutenir certaines opérations de première classe comme étant l'opérande gauche de la opérateur d'accès membre indépendamment du type de l'objet référencé. D'autres langues peuvent ou non avoir des restrictions similaires sur leurs références.
Les références en C ++ ne conserveront probablement pas la signification dans différents langages. Par exemple, les références en général n'impliquent pas de propriétés non nulles sur des valeurs comme celles du C ++, donc de telles hypothèses peuvent ne pas fonctionner dans d'autres langages (et vous trouverez assez facilement des contre-exemples, par exemple Java, C #, ...).
Il peut toujours y avoir des propriétés communes parmi les références dans différents langages de programmation en général, mais laissons cela pour d'autres questions dans SO.
(Remarque: la question peut être significative plus tôt que tous les langages de type "C", comme ALGOL 68 vs PL / I. )
Une référence à un pointeur est possible en C ++, mais l'inverse n'est pas possible signifie qu'un pointeur vers une référence n'est pas possible. Une référence à un pointeur fournit une syntaxe plus propre 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érez la version C du programme ci-dessus. En C, vous devez utiliser pointeur à pointeur (indirection multiple), et cela 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;
}
Visitez les sites suivants pour plus d'informations sur la référence au pointeur:
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;
}
J'utilise des références sauf si j'ai besoin de l'une ou l'autre:
Les pointeurs nuls peuvent être utilisés comme valeur sentinelle, souvent un moyen peu coûteux d'éviter la surcharge de fonctions ou l'utilisation d'un booléen.
Vous pouvez faire de l'arithmétique sur un pointeur. Par exemple,p += offset;
&r + offset
où a r
été déclaré comme référence
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 activent la sémantique passe-par-référence dans les arguments de fonction. Les pointeurs, bien qu'ils ne soient pas visibles au premier abord, ne le font pas: ils fournissent uniquement une sémantique de passage par valeur. Cela a été très bien décrit dans cet article .
Cordialement, & rzej
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 que sur une variable de la pile n'est pas réellement correct, prenez ceci 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 pointe avec succès vers une variable sur le tas! Maintenant, si vous voulez vraiment devenir 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
Par conséquent, une référence EST un pointeur sous le capot, ils stockent simplement une adresse mémoire, où l'adresse pointe n'est pas pertinente, que pensez-vous qu'il se passerait si j'appelais std :: cout << str_ref; APRÈS avoir appelé delete & str_ref? Eh bien, évidemment, il compile bien, mais provoque une erreur de segmentation au moment de l'exécution car il ne pointe plus sur une variable valide, nous avons essentiellement une référence cassée qui existe toujours (jusqu'à ce qu'elle tombe hors de portée), mais 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 la rend plus sûre et plus facile à utiliser (pas de calcul de pointeur accidentel, pas de confusion entre '.' Et '->', etc.), en supposant que vous n'essayez pas de bêtises comme mes exemples ci-dessus;)
Maintenant, quelle que soit la façon dont un compilateur gère les références, il aura toujours une sorte de pointeur sous le capot, car une référence doit faire référence à une variable spécifique à une adresse mémoire spécifique pour qu'elle fonctionne comme prévu, il n'y a pas moyen de contourner cela (d'où le terme «référence»).
La seule règle importante à retenir avec les références est qu'elles doivent être définies au moment de la déclaration (à l'exception d'une référence dans un en-tête, dans ce cas, elle doit être définie dans le constructeur, après que l'objet dans lequel il est contenu soit construit il est trop tard pour le définir).
Rappelez-vous, mes exemples ci-dessus ne sont que des exemples démontrant ce qu'est une référence, vous ne voudriez jamais utiliser une référence de cette manière! Pour une utilisation correcte d'une référence, il y a déjà beaucoup de réponses ici qui ont frappé le clou sur la tête
Une autre différence est que vous pouvez avoir des pointeurs vers un type void (et cela signifie un pointeur vers n'importe quoi) mais les références à void sont interdites.
int a;
void * p = &a; // ok
void & p = a; // forbidden
Je ne peux pas dire que je suis vraiment satisfait de cette différence particulière. Je préférerais de beaucoup qu'il soit autorisé avec la référence de sens à tout ce qui a une adresse et sinon le même comportement pour les références. Cela permettrait de définir certains équivalents de fonctions de bibliothèque C comme memcpy en utilisant des références.
En outre, une référence qui est un paramètre d'une fonction en ligne peut être gérée 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);
}
De nombreux compilateurs en insérant la version du pointeur forceront en fait une écriture en mémoire (nous prenons l'adresse explicitement). Cependant, ils laisseront la référence dans un registre plus optimal.
Bien sûr, pour les fonctions qui ne sont pas insérées, le pointeur et la référence génèrent le même code et il est toujours préférable de transmettre les valeurs intrinsèques par valeur que par référence si elles ne sont pas modifiées et renvoyées par la fonction.
Une autre utilisation intéressante des références consiste à 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;
}
La saveur par défaut utilise la référence «bind const à un aspect temporaire» des références.
Ce programme pourrait aider à comprendre la réponse à la question. Il s'agit d'un simple programme d'une référence "j" et d'un pointeur "ptr" pointant sur 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();
}
Exécutez le programme et regardez la sortie et vous comprendrez.
Aussi, épargnez 10 minutes et regardez cette vidéo: https://www.youtube.com/watch?v=rlJrrGV0iOg
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 équivalentes à l'objet auquel elles se réfèrent, c'est-à-dire que 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 pense que cette propriété est cruciale pour un certain nombre de fonctionnalités C ++, par exemple:
Modèles . Étant donné que les paramètres du modèle sont de type canard, les propriétés syntaxiques d'un type sont tout ce qui compte, si souvent le même modèle peut être utilisé avec les deux T
et T&
.
(ou std::reference_wrapper<T>
qui repose toujours sur une conversion implicite en T&
)
Modèles qui couvrent les deux T&
et T&&
sont encore plus courants.
Lvalues . Considérez la déclaration str[0] = 'X';
Sans références, cela ne fonctionnerait que pour les chaînes c ( char* str
). Le renvoi du caractère par référence permet aux classes définies par l'utilisateur d'avoir la même notation.
Copiez les constructeurs . Syntaxiquement, il est logique de passer des objets à des constructeurs de copie et non des pointeurs vers des objets. Mais il n'y a tout simplement aucun moyen pour un constructeur de copie de prendre un objet par valeur - cela entraînerait un appel récursif au même constructeur de copie. Cela laisse les références comme seule option ici.
Surcharges de l'opérateur . Avec des références, il est possible d'introduire une indirection vers un appel opérateur - disons, operator+(const T& a, const T& b)
tout en conservant la même notation infixe. Cela fonctionne également pour les fonctions régulièrement surchargées.
Ces points renforcent une partie considérable de C ++ et de la bibliothèque standard, c'est donc une propriété de référence assez importante.
Il existe 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 valeur, il ne peut donc certainement pas être modifié x
; pour modifier un argument, vous devez passer un pointeur fn(&x)
. Donc, si un argument n'était pas précédé d'un &
vous saviez qu'il ne serait pas modifié. (L'inverse, &
signifie modifié, n'était pas vrai car il fallait parfois passer de grandes structures en lecture seule par const
pointeur.)
Certains soutiennent qu'il s'agit d'une fonctionnalité tellement utile lors de la lecture de code, que les paramètres de pointeur doivent toujours être utilisés pour des paramètres modifiables plutôt que pour des non- const
références, même si la fonction n'attend jamais a nullptr
. Autrement dit, ces personnes soutiennent que les signatures de fonction comme fn3()
ci-dessus ne devraient pas être autorisées. Les directives de style C ++ de Google en sont un exemple.
Peut-être que certaines métaphores vous aideront; Dans le cadre de votre écran de bureau -
Un pointeur peut être initialisé à 0 et pas une référence. En fait, une référence doit également faire référence à 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 au premier, puis nous pouvons faire référence à cet objet, donc l'implémentation correcte 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 mais rien de tel ne peut être fait en cas de référence qui doit toujours faire référence à une variable ou à un objet. Cependant, une telle utilisation d'un pointeur est risquée, nous vérifions donc généralement si le pointeur pointe réellement vers quelque chose ou non. Dans le cas d'une référence, une telle vérification n'est pas nécessaire, car nous savons déjà que la référence à 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 fait toujours référence 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: lorsque nous avons un modèle comme un modèle STL, ce type de modèle de classe renvoie toujours une référence, pas un pointeur, pour faciliter la lecture ou l'attribution d'une nouvelle valeur à l'aide de 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 "="
const int& i = 0
.
La différence est que la variable de pointeur non constant (à ne pas confondre avec un pointeur sur constant) peut être modifiée à un moment donné pendant l'exécution du programme, nécessite que la sémantique du pointeur soit utilisée (&, *), tandis que les références peuvent être définies lors de l'initialisation uniquement (c'est pourquoi vous pouvez les définir uniquement dans la liste d'initialisation du constructeur, mais pas d'une autre manière) et utiliser une valeur ordinaire pour accéder à la sémantique. Fondamentalement, des références ont été introduites pour permettre la prise en charge de la surcharge des opérateurs, comme je l'avais lu dans un livre très ancien. Comme quelqu'un l'a dit dans ce fil - le pointeur peut être réglé sur 0 ou sur la valeur souhaitée. 0 (NULL, nullptr) signifie que le pointeur est initialisé avec rien. C'est une erreur de déréférencer le pointeur nul. Mais en fait, le pointeur peut contenir une valeur qui ne pointe pas vers un emplacement mémoire correct. Les références à leur tour essaient de ne pas permettre à un utilisateur d'initialiser une référence à quelque chose qui ne peut pas être référencé car vous lui fournissez toujours une valeur de type correct. Bien qu'il existe de nombreuses façons de faire initialiser une variable de référence à un mauvais emplacement de mémoire - il est préférable pour vous de ne pas creuser trop profondément dans les détails. Au niveau de la machine, le pointeur et la référence fonctionnent de manière uniforme - via des pointeurs. Disons que dans les références essentielles se trouve le sucre syntaxique. Les références rvalue sont différentes de cela - ce sont naturellement des objets pile / tas. Bien qu'il existe de nombreuses façons de faire initialiser une variable de référence à un mauvais emplacement de mémoire - il est préférable pour vous de ne pas creuser trop profondément dans les détails. Au niveau de la machine, le pointeur et la référence fonctionnent de manière uniforme - via des pointeurs. Disons que dans les références essentielles se trouve le sucre syntaxique. Les références rvalue sont différentes de cela - ce sont naturellement des objets pile / tas. Bien qu'il existe de nombreuses façons de faire initialiser une variable de référence à un mauvais emplacement de mémoire - il est préférable pour vous de ne pas creuser trop profondément dans les détails. Au niveau de la machine, le pointeur et la référence fonctionnent de manière uniforme - via des pointeurs. Disons que dans les références essentielles se trouve le sucre syntaxique. Les références rvalue sont différentes de cela - ce sont naturellement des objets pile / tas.
en termes simples, on peut dire qu'une référence est un nom alternatif pour une variable alors qu'un pointeur est une variable qui contient l'adresse d'une autre variable. par exemple
int a = 20;
int &r = a;
r = 40; /* now the value of a is changed to 40 */
int b =20;
int *ptr;
ptr = &b; /*assigns address of b to ptr not the value */