Quelles sont les différences entre une variable pointeur et une variable de référence en C ++?

3266

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?

prakash
la source
100
Je pense que le point 2 devrait être "Un pointeur peut être NULL mais pas une référence. Seul un code mal formé peut créer une référence NULL et son comportement n'est pas défini."
Mark Ransom
19
Les pointeurs ne sont qu'un autre type d'objet et, comme tout objet en C ++, ils peuvent être une variable. Par contre, les références ne sont jamais des objets, seulement des variables.
Kerrek SB
19
Cela se compile sans avertissements: int &x = *(int*)0;sur gcc. La référence peut en effet pointer vers NULL.
Calmarius du
20
la référence est un alias variable
Khaled.K
20
J'aime à quel point la toute première phrase est une erreur totale. Les références ont leur propre sémantique.
Courses de légèreté en orbite du

Réponses:

1709
  1. 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;
    
  2. 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);
    
  3. 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);
    
  4. Un pointeur peut être attribué nullptrdirectement, 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
    
  5. Les pointeurs peuvent parcourir un tableau; vous pouvez utiliser ++pour aller à l'élément suivant vers lequel pointe un pointeur et + 4pour aller au 5ème élément. Quelle que soit la taille de l'objet vers lequel pointe le pointeur.

  6. 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 ..

  7. 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)

  8. 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.

Brian R. Bondy
la source
23
... mais le déréférencement de NULL n'est pas défini. Par exemple, vous ne pouvez pas tester si une référence est NULL (par exemple, & ref == NULL).
Pat Notz,
69
Le numéro 2 n'est pas vrai. Une référence n'est pas simplement "un autre nom pour la même variable". Les références peuvent être transmises à des fonctions, stockées dans des classes, etc. d'une manière très similaire aux pointeurs. Ils existent indépendamment des variables qu'ils désignent.
Derek Park, le
31
Brian, la pile n'est pas pertinente. Les références et les pointeurs n'ont pas besoin de prendre de l'espace sur la pile. Ils peuvent tous deux être alloués sur le tas.
Derek Park,
22
Brian, le fait qu'une variable (dans ce cas un pointeur ou une référence) nécessite de l'espace ne signifie pas qu'elle nécessite de l'espace sur la pile. Les pointeurs et les références peuvent non seulement pointer vers le tas, mais ils peuvent en fait être alloués sur le tas.
Derek Park,
38
une autre différence importante: les références ne peuvent pas être insérées dans un tableau
Johannes Schaub - litb
384

Qu'est-ce qu'une référence C ++ ( pour les programmeurs C )

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!

Pourquoi je considère les références C ++ utiles

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.

Christoph
la source
17
@kriss: Non, vous pouvez également obtenir une référence pendante en renvoyant une variable automatique par référence.
Ben Voigt
12
@kriss: Il est pratiquement impossible pour un compilateur de détecter dans le cas général. Considérons une fonction membre qui renvoie une référence à une variable membre de classe: c'est sûr et ne devrait pas être interdit par le compilateur. Ensuite, un appelant qui a une instance automatique de cette classe, appelle cette fonction membre et renvoie la référence. Presto: référence pendante. Et oui, ça va causer des ennuis, @kriss: c'est mon point. Beaucoup de gens affirment qu'un avantage des références sur les pointeurs est que les références sont toujours valides, mais ce n'est tout simplement pas le cas.
Ben Voigt
4
@kriss: Non, une référence à un objet de durée de stockage automatique est très différente d'un objet temporaire. Quoi qu'il en soit, je viens de fournir un contre-exemple à votre déclaration selon laquelle vous ne pouvez obtenir une référence non valide qu'en déréférençant un pointeur non valide. Christoph a raison - les références ne sont pas plus sûres que les pointeurs, un programme qui utilise exclusivement des références peut encore briser la sécurité du type.
Ben Voigt
7
Les références ne sont pas une sorte de pointeur. Ils sont un nouveau nom pour un objet existant.
catphive
18
@catphive: vrai si vous utilisez la sémantique du langage, pas vrai si vous regardez réellement l'implémentation; C ++ est un langage beaucoup plus "magique" que C, et si vous supprimez la magie des références, vous vous retrouvez avec un pointeur
Christoph
191

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, constil 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.

Prix ​​Matt
la source
5
mais quel est le cas d'utilisation pour cela?
Ahmad Mushtaq
20
Eh bien, s3_copy créera un temporaire puis copiera le construira dans s3_copy alors que s3_reference utilise directement le temporaire. Ensuite, pour être vraiment pédant, vous devez regarder l'optimisation de la valeur de retour par laquelle le compilateur est autorisé à élider la construction de la copie dans le premier cas.
Matt Price
6
@digitalSurgeon: La magie y est assez puissante. La durée de vie de l'objet est prolongée par le fait de la 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.
David Rodríguez - dribeas
9
Mise à jour pour C ++ 11: la dernière phrase doit se lire "Vous ne pouvez pas lier une référence lvalue non const à un temporaire" car vous pouvez lier une référence rvalue non const à un temporaire et elle a le même comportement d'extension de durée de vie.
Oktalist
4
@AhmadMushtaq: L'utilisation clé de ceci est des classes dérivées . S'il n'y a pas d'héritage, vous pouvez également utiliser la sémantique de valeur, qui sera bon marché ou gratuite en raison de la construction RVO / move. Mais si vous avez Animal x = fast ? getHare() : getTortoise()alors vous xserez confronté au problème de découpage classique, tandis que Animal& x = ...fonctionnera correctement.
Arthur Tacca
128

Outre le sucre syntaxique, une référence est un constpointeur ( 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.


la source
8
Je pense que c'est de loin la meilleure réponse. D'autres parlent de références et de pointeurs comme s'ils étaient des bêtes différentes et expliquent ensuite en quoi ils diffèrent dans leur comportement. Cela ne facilite pas les choses à mon humble avis. J'ai toujours compris que les références étaient un T* constsucre syntaxique différent (cela élimine beaucoup de * et de votre code).
Carlo Wood
2
"La cible d'un pointeur const peut être remplacée en prenant son adresse et en utilisant un transtypage const." Cela est un comportement indéfini. Voir stackoverflow.com/questions/25209838/… pour plus de détails.
dgnuff
1
Tenter de modifier le référent d'une référence ou la valeur d'un pointeur const (ou tout scalaire const) est une égalité illégale. Ce que vous pouvez faire: supprimer une qualification const ajoutée par conversion implicite: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);c'est OK.
curiousguy
1
La différence ici est UB contre littéralement impossible. Il n'y a pas de syntaxe en C ++ qui vous permettrait de changer à quels points de référence.
Pas impossible, plus difficile, vous pouvez simplement accéder à la zone mémoire du pointeur qui modélise cette référence et modifie son contenu. Cela peut certainement être fait.
Nicolas Bousquet
126

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 ifclause 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.

Mark Ransom
la source
63
Le code en question contient un comportement non défini. Techniquement, vous ne pouvez rien faire avec un pointeur nul, sauf le définir et le comparer. Une fois que votre programme invoque un comportement indéfini, il peut tout faire, y compris sembler fonctionner correctement jusqu'à ce que vous donniez une démo au grand patron.
KeithB
9
la marque a un argument valide. l'argument selon lequel un pointeur pourrait être NULL et que vous devez donc vérifier n'est pas réel non plus: si vous dites qu'une fonction nécessite non NULL, alors l'appelant doit le faire. donc si l'appelant ne le fait pas, il invoque un comportement indéfini. tout comme mark l'a fait avec la mauvaise référence
Johannes Schaub - litb
13
La description est erronée. Ce code peut créer ou non une référence NULL. Son comportement n'est pas défini. Cela pourrait créer une référence parfaitement valide. Il pourrait ne pas créer de référence du tout.
David Schwartz
7
@David Schwartz, si je parlais de la façon dont les choses devaient fonctionner selon la norme, vous auriez raison. Mais ce n'est pas de cela que je parle - je parle du comportement réel observé avec un compilateur très populaire, et extrapolant sur la base de ma connaissance des compilateurs typiques et des architectures CPU à ce qui se produira probablement . Si vous croyez que les références sont supérieures aux pointeurs parce qu'elles sont plus sûres et ne considérez pas que les références peuvent être mauvaises, vous serez un jour confronté à un problème simple tout comme moi.
Mark Ransom
6
Déréférencer un pointeur nul est incorrect. Tout programme qui fait cela, même pour initialiser une référence est faux. Si vous initialisez une référence à partir d'un pointeur, vous devez toujours vérifier que le pointeur est valide. Même si cela réussit, l'objet sous-jacent peut être supprimé à tout moment en laissant la référence pour faire référence à un objet inexistant, non? Ce que vous dites est de bonnes choses. Je pense que le vrai problème ici est que la référence n'a PAS besoin d'être vérifiée pour la «nullité» lorsque vous en voyez une et que le pointeur doit, au minimum, être affirmé.
t0rakka
115

Vous avez oublié la partie la plus importante:

accès membre avec pointeurs utilise ->
accès membre avec références utilise.

foo.barest clairement supérieur à foo->barde la même manière que vi est clairement supérieur à Emacs :-)

Orion Edwards
la source
4
@Orion Edwards> accès aux membres avec utilisations de pointeurs ->> accès aux membres avec utilisations de références. Ce n'est pas vrai à 100%. Vous pouvez avoir une référence à un pointeur. Dans ce cas, vous accéderez aux membres du pointeur dé-référencé en utilisant -> struct Node {Node * next; }; Node * first; // p est une référence à un pointeur void foo (Node * & p) {p-> next = first; } Node * bar = nouveau Node; foo (bar); - OP: Connaissez-vous les concepts de rvalues ​​et lvalues?
3
Les pointeurs intelligents ont les deux. (méthodes sur la classe de pointeur intelligent) et -> (méthodes sur le type sous-jacent).
JBRWilkinson
1
@ user6105 La déclaration d' Orion Edwards est en fait 100% vraie. "accès aux membres [du] pointeur dé-référencé" Un pointeur n'a pas de membres. L'objet auquel le pointeur fait référence a des membres, et l'accès à ceux-ci est exactement ce qui ->fournit des références aux pointeurs, tout comme avec le pointeur lui-même.
Max Truxa
1
pourquoi est-ce .et ->a quelque chose à voir avec vi et emacs :)
artm
10
@artM - c'était une blague, et cela n'a probablement pas de sens pour les anglophones non natifs. Mes excuses. Pour expliquer, si vi est meilleur qu'emacs est entièrement subjectif. Certaines personnes pensent que vi est de loin supérieur et d'autres pensent exactement le contraire. De même, je pense que l'utilisation .est meilleure que l'utilisation ->, mais tout comme vi vs emacs, c'est entièrement subjectif et vous ne pouvez rien prouver
Orion Edwards
74

Les références sont très similaires aux pointeurs, mais elles sont spécialement conçues pour être utiles à l'optimisation des compilateurs.

  • Les références sont conçues de telle sorte qu'il est beaucoup plus facile pour le compilateur de tracer quelles alias de référence quelles variables. Deux caractéristiques majeures sont très importantes: aucune "arithmétique de référence" et aucune réaffectation de références. Ceux-ci permettent au compilateur de déterminer quelles références alias quelles variables au moment de la compilation.
  • Les références sont autorisées à faire référence à des variables qui n'ont pas d'adresses mémoire, telles que celles que le compilateur choisit de mettre dans les registres. Si vous prenez l'adresse d'une variable locale, il est très difficile pour le compilateur de la mettre dans un registre.

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, refC ++ étendra la durée de vie de cet objet temporaire jusqu'à ce qu'il refsoit hors de portée.

Cort Ammon
la source
Certes, le corps doit être visible. Cependant, il est beaucoup plus facile de déterminer qui maybeModifyne prend pas l'adresse de quoi que ce soit en rapport avec la xpreuve que de prouver qu'un tas d'arithmétique de pointeur ne se produit pas.
Cort Ammon
Je crois que l'optimiseur fait déjà qu'un "tas d'arithémétique de pointeur ne se produit pas" vérifie un tas d'autres raisons.
Ben Voigt
"Les références sont très similaires aux pointeurs" - sémantiquement, dans des contextes appropriés - mais en termes de code généré, uniquement dans certaines implémentations et non via une définition / exigence. Je sais que vous l'avez souligné, et je ne suis en désaccord avec aucun de vos messages en termes pratiques, mais nous avons déjà trop de problèmes avec les gens qui lisent trop dans des descriptions abrégées comme `` les références sont comme / généralement mises en œuvre comme pointeurs '' .
underscore_d
J'ai le sentiment que quelqu'un a signalé à tort comme obsolète un commentaire dans le sens de void maybeModify(int& x) { 1[&x]++; }ce que les autres commentaires ci-dessus discutent
Ben Voigt
69

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, aet 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.

Vincent Robert
la source
2
Une référence est une référence à la valeur l, pas nécessairement à une variable. Pour cette raison, il est beaucoup plus proche d'un pointeur que d'un vrai alias (une construction au moment de la compilation). Des exemples d'expressions qui peuvent être référencées sont * p ou même * p ++
5
À droite, je pointais juste le fait qu'une référence ne peut pas toujours pousser une nouvelle variable sur la pile comme le fera un nouveau pointeur.
Vincent Robert
1
@VincentRobert: Il agira de la même manière qu'un pointeur ... si la fonction est en ligne, la référence et le pointeur seront optimisés. S'il y a un appel de fonction, l'adresse de l'objet devra être transmise à la fonction.
Ben Voigt
1
int * p = NULL; int & r = * p; référence pointant vers NULL; if (r) {} -> boOm;)
sree
2
Cette concentration sur l'étape de compilation semble agréable, jusqu'à ce que vous vous souveniez que les références peuvent être transmises à l'exécution, moment auquel l'alias statique sort de la fenêtre. (Et puis, les références sont généralement implémentées en tant que pointeurs, mais la norme ne nécessite pas cette méthode.)
underscore_d
45

Une référence ne peut jamais l'être NULL.

Cole Johnson
la source
10
Voir la réponse de Mark Ransom pour un contre-exemple. C'est le mythe le plus souvent affirmé sur les références, mais c'est un mythe. La seule garantie que vous avez par rapport à la norme est que vous avez immédiatement UB lorsque vous avez une référence NULL. Mais cela revient à dire "Cette voiture est sûre, elle ne peut jamais sortir de la route. (Nous ne prenons aucune responsabilité pour ce qui peut arriver si vous la conduisez hors de la route de toute façon. Elle pourrait juste exploser.)"
cmaster - rétablir monica
17
@cmaster: dans un programme valide , une référence ne peut pas être nulle. Mais un pointeur peut. Ce n'est pas un mythe, c'est un fait.
user541686
8
@Mehrdad Oui, les programmes valides restent sur la route. Mais il n'y a pas de barrière de circulation pour faire respecter ce que fait réellement votre programme. De grandes parties de la route sont en fait sans marquage. Il est donc extrêmement facile de quitter la route la nuit. Et il est crucial pour le débogage de ces bogues que vous sachiez que cela peut se produire: la référence null peut se propager avant de planter votre programme, tout comme un pointeur null. Et quand c'est le cas, vous avez un code comme 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.
cmaster - réintègre monica
4
int * p = NULL; int & r = * p; référence pointant vers NULL; if (r) {} -> boOm;) -
sree
10
@sree 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 .
cdhowie
35

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.

Kunal Vyas
la source
"une référence fait toujours référence à un objet" est tout simplement faux
Ben Voigt
32

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.

Courses de légèreté en orbite
la source
27

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. 
fatma.ekici
la source
20

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.

MSN
la source
1
Vous ne pouvez pas prendre l'adresse d'une référence, mais cela ne signifie pas qu'ils ne prennent pas physiquement de place. Sauf optimisations, ils le peuvent très certainement.
Courses de légèreté en orbite le
2
Nonobstant l'impact, «Une référence sur la pile ne prend aucun espace» est manifestement fausse.
Courses de légèreté en orbite le
1
@Tomalak, eh bien, cela dépend aussi du compilateur. Mais oui, dire que c'est un peu déroutant. Je suppose qu'il serait moins déroutant de simplement supprimer cela.
MSN
1
Dans un cas particulier donné, cela peut ou non. Donc "ce n'est pas le cas" car une affirmation catégorique est fausse. C'est ce que je dis. :) [Je ne me souviens pas de ce que dit la norme sur la question; les règles des membres de référence peuvent donner une règle générale de "les références peuvent prendre de la place", mais je n'ai pas ma copie de la norme avec moi ici sur la plage: D]
Courses de légèreté en orbite
20

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_Tompeut ê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.

La vie
la source
1
Et si une classe a une variable de référence, elle doit être initialisée avec un nullptr ou un objet valide dans la liste d'initialisation.
Misgevolution
1
Le libellé de cette réponse est trop déroutant pour qu'elle soit d'une réelle utilité. Aussi, @Misgevolution, recommandez-vous sérieusement aux lecteurs d'initialiser une référence avec un nullptr? Avez-vous réellement lu une autre partie de ce fil, ou ...?
underscore_d
1
Mon mauvais, désolé pour cette chose stupide que j'ai dit. Je dois avoir été privé de sommeil à ce moment-là. 'initialiser avec nullptr' est totalement faux.
Misgevolution
19

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;
tanweer alam
la source
13
Ce n'est pas ce que dit la norme C ++, et il n'est pas nécessaire que le compilateur implémente des références de la manière décrite par votre réponse.
jogojapan
@jogojapan: Toute méthode valide pour un compilateur C ++ pour implémenter une référence est également une manière valide pour implémenter un constpointeur. Cette flexibilité ne prouve pas qu'il existe une différence entre une référence et un pointeur.
Ben Voigt du
2
@BenVoigt Il peut être vrai que toute implémentation valide de l'un est également une implémentation valide de l'autre, mais cela ne découle pas de manière évidente des définitions de ces deux concepts. Une bonne réponse aurait commencé par les définitions et démontré pourquoi l'affirmation selon laquelle les deux sont finalement les mêmes est vraie. Cette réponse semble être une sorte de commentaire sur certaines des autres réponses.
jogojapan
Une référence est un autre nom donné à un objet. Le compilateur est autorisé à avoir n'importe quel type d'implémentation, tant que vous ne pouvez pas faire la différence, c'est ce que l'on appelle la règle "comme si". La partie importante ici est que vous ne pouvez pas faire la différence. Si vous pouvez découvrir qu'un pointeur n'a pas de stockage, le compilateur est en erreur. Si vous pouvez découvrir qu'une référence n'a pas de stockage, le compilateur est toujours conforme.
sp2danny
18

La réponse directe

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:

  • Les types d'objets peuvent avoir des cvqualificatifs de niveau supérieur . Les références ne le peuvent pas.
  • La variable des types d'objets occupe le stockage selon la sémantique abstraite de la machine . La référence n'occupe pas nécessairement le stockage (voir la section sur les idées fausses ci-dessous pour plus de détails).
  • ...

Quelques règles supplémentaires sur les références:

  • Les déclarants composés sont plus restrictifs sur les références.
  • Les références peuvent s'effondrer .
    • Des règles spéciales sur les &&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.
  • Les références ont des règles spéciales lors de l'initialisation. La durée de vie de la variable déclarée comme type de référence peut être différente de celle des objets ordinaires via l'extension.
    • BTW, quelques autres contextes comme l'initialisation impliquant std::initializer_listsuit des règles similaires d'extension de durée de vie de référence. C'est une autre boîte de vers.
  • ...

Les idées fausses

Sucre syntaxique

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+ .

Espace de rangement

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 .

Pragmatique

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.

  • Parfois, les règles de langage nécessitent explicitement l'utilisation de types spécifiques. Si vous souhaitez utiliser ces fonctionnalités, respectez les règles.
    • Les constructeurs de copie nécessitent des types spécifiques de cv - &type de référence du 1er type de paramètre. (Et généralement, il doit être constqualifié.)
    • Constructeurs Déplacer nécessitent des types spécifiques de cv - &&type de référence du 1er type de paramètre. (Et généralement, il ne devrait pas y avoir de qualificatifs.)
    • Les surcharges spécifiques des opérateurs nécessitent des types de référence ou non de référence. Par exemple:
      • Surchargé en 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.
      • Postfix ++nécessite un mannequin int.
      • ...
  • Si vous savez que la valeur de passage (c'est-à-dire l'utilisation de types non référentiels) est suffisante, utilisez-la directement, en particulier lorsque vous utilisez une implémentation prenant en charge l'élision de copie mandatée C ++ 17. ( Attention : Cependant, raisonner de manière exhaustive sur la nécessité peut être très compliqué .)
  • Si vous souhaitez utiliser certaines poignées avec propriété, utilisez des pointeurs intelligents comme unique_ptret 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.
  • Si vous effectuez des itérations sur une plage, utilisez des itérateurs (ou certaines plages qui ne sont pas encore fournies par la bibliothèque standard), plutôt que des pointeurs bruts, sauf si vous êtes convaincu que les pointeurs bruts feront mieux (par exemple pour moins de dépendances d'en-tête) dans des domaines très spécifiques. cas.
  • Si vous savez que le passage par valeur est suffisant et que vous souhaitez une sémantique nullable explicite, utilisez un wrapper comme std::optional, plutôt que des pointeurs bruts.
  • Si vous savez que la valeur de passage n'est pas idéale pour les raisons ci-dessus et que vous ne voulez pas de sémantique nullable, utilisez les références {lvalue, rvalue, forwarding}.
  • Même lorsque vous voulez une sémantique comme un pointeur traditionnel, il y a souvent quelque chose de plus approprié, comme observer_ptrdans Library Fundamental TS.

Les seules exceptions ne peuvent pas être contournées dans la langue actuelle:

  • Lorsque vous implémentez des pointeurs intelligents ci-dessus, vous devrez peut-être gérer des pointeurs bruts.
  • Des routines d'interopération de langage spécifiques nécessitent des pointeurs, comme 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.)
  • Les pointeurs de fonction peuvent être convertis à partir d'expressions lambda sans captures, contrairement aux références de fonction. Vous devez utiliser des pointeurs de fonction dans du code non générique pour de tels cas, même si vous ne voulez pas délibérément de valeurs nullables.

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:

  • Vous devez servir aux anciennes API (C).
  • Vous devez répondre aux exigences ABI des implémentations C ++ spécifiques.
  • Vous devez interagir au moment de l'exécution avec différentes implémentations de langage (y compris divers assemblys, runtime de langue et FFI de certains langages clients de haut niveau) en fonction des hypothèses d'implémentations spécifiques.
  • Vous devez améliorer l'efficacité de la traduction (compilation et liaison) dans certains cas extrêmes.
  • Vous devez éviter les ballonnements de symboles dans certains cas extrêmes.

Mises en garde concernant la neutralité linguistique

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. )

FrankHB
la source
17

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;
}
Destructeur
la source
16

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;

Aardvark
la source
5
Vous pouvez écrire &r + offsetoù a rété déclaré comme référence
MM
15

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

Andrzej
la source
1
Les références et les pointeurs sont les deux poignées. Ils vous donnent tous les deux la sémantique où votre objet est passé par référence, mais le handle est copié. Aucune différence. (Il existe également d'autres façons d'avoir des poignées, comme une clé de recherche dans un dictionnaire)
Ben Voigt
Je pensais aussi comme ça. Mais voir l'article lié décrivant pourquoi il n'en est pas ainsi.
Andrzej
2
@Andrzj: C'est juste une version très longue de la phrase unique dans mon commentaire: le handle est copié.
Ben Voigt
J'ai besoin de plus d'explications sur ce "La poignée est copiée". Je comprends une idée de base mais je pense que physiquement la référence et le pointeur pointent tous les deux l'emplacement mémoire de la variable. Est-ce comme si l'alias stocke la variable de valeur et la met à jour lorsque la valeur de la variable est un changement ou autre chose? Je suis novice, et ne le signalez pas comme une question stupide.
Asim
1
@Andrzej False. Dans les deux cas, le passage par valeur se produit. La référence est passée par valeur et le pointeur est passé par valeur. Dire le contraire confond les débutants.
Miles Rout
14

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

tory
la source
14

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.

kriss
la source
13

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.

Adisak
la source
11

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.

Don Wakefield
la source
11

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

Arlene Batada
la source
11

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 Tet 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.

Ap31
la source
" transtypage implicite " un transtypage est une construction syntaxique, il existe dans la grammaire; un casting est toujours explicite
curiousguy
9

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 constpointeur.)

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- constré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.

Arthur Tacca
la source
8

Peut-être que certaines métaphores vous aideront; Dans le cadre de votre écran de bureau -

  • Une référence vous oblige à spécifier une fenêtre réelle.
  • Un pointeur nécessite l'emplacement d'un espace à l'écran que vous vous assurez qu'il contiendra zéro ou plusieurs instances de ce type de fenêtre.
George R
la source
6

Différence entre le pointeur et la référence

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 "="
dhokar.w
la source
1
Nous pouvons encore avoir const int& i = 0.
Revolver_Ocelot
1
Dans ce cas, la référence sera utilisée uniquement en lecture, nous ne pouvons pas modifier cette référence const même en utilisant "const_cast" car "const_cast" n'accepte que le pointeur et non la référence.
dhokar.w
1
const_cast fonctionne assez bien avec les références: coliru.stacked-crooked.com/a/eebb454ab2cfd570
Revolver_Ocelot
1
vous effectuez un transtypage en référence sans transtyper une référence, essayez ceci; const int & i =; const_cast <int> (i); j'essaie de jeter la constance de la référence pour rendre possible l'écriture et l'affectation d'une nouvelle valeur à la référence mais ce n'est pas possible. veuillez vous concentrer !!
dhokar.w
5

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.

Zorgiev
la source
4

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 */
Sadhana Singh
la source