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




valeur reference (25)

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: étendre 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. Considérant que s3_reference devient essentiellement l'objet temporaire. C'est en réalité une référence à un objet temporaire qui a maintenant la même durée de vie que la référence.

Si vous essayez ceci sans le const compilation ne devrait pas aboutir. Vous ne pouvez pas lier une référence non-const à un objet temporaire, ni prendre son adresse pour cette question.

Je sais que les références sont du sucre syntaxique. Le code est donc plus facile à lire et à écrire.

Mais quelles sont les différences?

Résumé des réponses et des liens ci-dessous:

  1. Un pointeur peut être réaffecté un nombre quelconque de fois, tandis qu'une référence ne peut pas être réaffectée après la liaison.
  2. Les pointeurs peuvent pointer nulle part ( NULL ), alors qu'une référence fait toujours référence à un objet.
  3. Vous ne pouvez pas prendre l'adresse d'une référence comme vous pouvez le faire avec des pointeurs.
  4. Il n'y a pas d '"arithmétique de référence" (mais vous pouvez prendre l'adresse d'un objet pointé par une référence et faire de l'arithmétique de pointeur dessus comme dans &obj + 5 ).

Pour clarifier une idée fausse:

La norme C ++ veille à ne pas indiquer comment un compilateur peut implémenter des références, mais chaque compilateur C ++ implémente des références en tant que pointeurs. C'est-à-dire une déclaration telle que:

int &ri = i;

s'il n'est pas optimisé complètement , alloue la même quantité de mémoire qu'un pointeur et place l'adresse de i dans cette mémoire.

Ainsi, un pointeur et une référence utilisent la même quantité de mémoire.

En règle générale,

  • Utilisez des références dans les paramètres de fonction et les types de retour pour fournir des interfaces utiles et auto-documentées.
  • Utilisez des pointeurs pour implémenter des algorithmes et des structures de données.

Lecture intéressante:


J'ai l'impression qu'il y a encore un autre point qui n'a pas été couvert 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 pouvant être appliquée à un objet fonctionne pour une référence, et avec la même syntaxe (l'exception étant bien sûr l'initialisation).

Bien que cela puisse paraître 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 de modèle sont typés à la manière d'un canard, les propriétés syntaxiques d'un type sont tout ce qui compte. Par conséquent, le même modèle peut souvent être utilisé avec Tet T&.
    (ou std::reference_wrapper<T>qui repose encore sur une conversion implicite vers T&) Des
    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 c-strings ( char* str). Renvoyer le caractère par référence permet aux classes définies par l'utilisateur d'avoir la même notation.

  • Copier les constructeurs . Syntaxiquement, il est logique de passer des objets à des constructeurs de copie, et non des pointeurs sur des objets. Mais il n’existe 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.

  • Surcharge de l'opérateur . Avec les références, il est possible d'introduire l'indirection vers un appel opérateur - par exemple, operator+(const T& a, const T& b)tout en conservant la même notation infixe. Cela fonctionne également pour les fonctions surchargées habituelles.

Ces points habilitent une part considérable du C ++ et de la bibliothèque standard, ce qui en fait une propriété très importante des références.


Une autre utilisation intéressante des références est de fournir un argument par défaut d'un type défini par l'utilisateur:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

La saveur par défaut utilise l'aspect 'bind const reference to' temporaire 'des références.


Une référence à un pointeur est possible en C ++, mais l'inverse n'est pas possible signifie qu'un pointeur sur une référence n'est pas possible. Une référence à un pointeur fournit une syntaxe de nettoyage permettant de 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érons la version C du programme ci-dessus. En C, vous devez utiliser un pointeur à l'autre (indirection multiple), ce qui crée de la confusion et rend le programme complexe.

#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;
}

Consultez la section suivante pour plus d'informations sur la référence au pointeur:

Comme je l'ai dit, un pointeur sur une référence n'est pas possible. Essayez le programme suivant:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

Il existe une différence sémantique qui peut sembler ésotérique si vous n'êtes pas habitué à étudier les 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 inquiéter à ce sujet: vous êtes censé les considérer comme "juste un autre nom" pour un objet existant et la syntaxe en tient compte. 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 pendante que lorsque vous êtes sur le point de créer un pointeur suspendu.

Au-delà, 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 "repositionner" les références, avoir des références au néant ou des pointeurs vers des références.


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 sur une valeur constante!) Avec une indirection automatique, c'est-à-dire que le compilateur appliquera l'opérateur * à votre place.

Toutes les références doivent être initialisées avec une valeur non nulle, sinon la compilation échouera. Il n'est pas non plus possible d'obtenir l'adresse d'une référence. L'opérateur d'adresse renverra l'adresse de la valeur référencée. Il n'est pas non plus possible de faire de l'arithmétique sur des références.

Les programmeurs C peuvent ne pas aimer les références C ++ car cela ne sera plus évident en cas d'indirection ou si un argument est passé par valeur ou par pointeur sans examiner les signatures des fonctions.

Les programmeurs C ++ peuvent ne pas aimer utiliser les pointeurs car ils sont considérés comme peu sûrs - 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 la déclaration 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, ne considérez pas une référence comme un pointeur amusant sur un objet. Une référence est l'objet. Ce n'est pas un pointeur sur l'objet, ni une copie de l'objet. C'est l'objet.

Mais si une référence était vraiment l'objet, comment pourrait-il y avoir des références en suspens? Dans les langues non gérées, il est impossible pour les références d'être «plus sûres» que les pointeurs - il n'y a généralement pas de moyen de créer des alias fiables entre les limites de la portée!

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

Venant d’un contexte C, les références C ++ peuvent sembler un concept quelque peu idiot, mais vous devriez quand même les utiliser à la place des pointeurs: l’indirection automatique est pratique, et les références deviennent particulièrement utiles lorsqu’il s’agit de RAII - mais pas pour des raisons de sécurité. avantage, mais plutôt parce qu’ils rendent l’écriture de code idiomatique moins compliquée.

RAII est l'un des concepts centraux du C ++, mais il interagit de manière non triviale avec la sémantique de la copie. Passer des objets par référence évite ces problèmes car aucune copie n'est impliquée. Si les références ne sont pas présentes dans la langue, vous devrez utiliser des pointeurs, qui sont plus encombrants à utiliser, enfreignant ainsi le principe de conception de langue selon lequel la solution de meilleure pratique devrait être plus facile que les alternatives.


Les références et les pointeurs peuvent être utilisés pour modifier les variables locales d'une fonction dans une autre fonction. Les deux peuvent également être utilisés pour sauvegarder la copie d'objets volumineux lorsqu'ils sont transmis en tant qu'arguments à des fonctions ou renvoyés de fonctions pour obtenir un gain d'efficacité. Malgré les similitudes ci-dessus, il existe des différences entre les références et les pointeurs.

Les références sont moins puissantes que les pointeurs

1) Une fois la référence créée, vous ne pouvez plus faire référence à un autre objet. il ne peut pas être remis en place. Cela se fait souvent avec des pointeurs.

2) Les références ne peuvent pas être NULL. Les pointeurs sont souvent définis comme NULL pour indiquer qu'ils ne pointent vers aucune chose valide.

3) Une référence doit être initialisée lors de la déclaration. Il n'y a pas de telle restriction avec des pointeurs

En raison des limitations ci-dessus, les références en C ++ ne peuvent pas être utilisées pour l'implémentation de structures de données telles que la liste liée, l'arbre, etc. En Java, les références n'ont pas les restrictions précédentes et peuvent être utilisées pour implémenter toutes les structures de données. Les références étant plus puissantes en Java, c'est la raison principale pour laquelle Java n'a pas besoin de pointeurs.

Les références sont plus sûres et plus faciles à utiliser:

1) Plus sûr: comme les références doivent être initialisées, il est peu probable qu’elles existent, telles que les pointeurs sauvages. Il est toujours possible d'avoir des références qui ne font pas référence à un emplacement valide

2) Plus facile à utiliser: les références n'ont pas besoin d'opérateur de déréférencement pour accéder à la valeur. Ils peuvent être utilisés comme des variables normales. L'opérateur '&' n'est nécessaire qu'au moment de la déclaration. De plus, les membres d'une référence d'objet sont accessibles avec l'opérateur de point ('.'), Contrairement aux pointeurs dans lesquels l'opérateur de flèche (->) est nécessaire pour accéder aux membres.

Avec les raisons ci-dessus, il existe peu d'endroits comme l'argument constructeur de copie où le pointeur ne peut pas être utilisé. Référence doit être utilisé pour passer l'argument dans le constructeur de copie. De même, les références doivent être utilisées pour surcharger certains opérateurs tels que ++ .


Peu importe l'espace occupé, vous ne pouvez voir aucun effet secondaire (sans exécuter de code) de l'espace qu'il prendrait.

D'autre part, une différence majeure entre les références et les pointeurs réside dans le fait que les temporelles assignées aux références const vivent jusqu'à ce que la référence const sorte de la 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!

C'est le mécanisme de langage qui permet à ScopeGuard de fonctionner.


Une référence est un alias pour une autre variable alors 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 pour que l'objet transmis ne soit 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. 

Au risque d’ajouter à la confusion, je voudrais ajouter quelques éléments; cela dépend certainement de la manière 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 vraiment correct, prenons 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;
}

Ce qui sort 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 du 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).*/
}

Ce qui sort ceci:

THIS IS A STRING

Par conséquent, une référence est un pointeur sous le capot, ils sont tous deux en train de stocker une adresse mémoire, l'adresse indiquée étant sans importance, que pensez-vous qu'il se passerait si j'appelais std :: cout << str_ref; APRES avoir appelé delete & str_ref? Évidemment, cela compile bien, mais provoque une erreur de segmentation au moment de l’exécution, car elle 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 qui est inutile.

En d’autres termes, une référence n’est rien d’autre qu’un pointeur dont la mécanique est abstraite, ce qui la rend plus sûre et plus facile à utiliser (pas de maths accidentel, pas de confusion entre '.' Et '->', etc.) n'essayez pas un non-sens comme mes exemples ci-dessus;)

Maintenant, quelle que soit la façon dont un compilateur traite 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 que celle-ci fonctionne comme prévu, il n’ya pas moyen de contourner 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 qui le contient soit il est trop tard pour le définir).

N'oubliez pas que 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 bien utiliser une référence, il y a déjà beaucoup de réponses sur ce qui a frappé le clou sur la tête


Une autre différence est que vous pouvez avoir des pointeurs sur un type de vide (et cela signifie un pointeur sur n'importe quoi) mais les références à vide sont interdites.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

Je ne peux pas dire que je suis vraiment heureux avec cette différence particulière. Je préférerais de beaucoup que cela soit autorisé avec la signification du mot référence à tout ce qui a une adresse et sinon le même comportement pour les références. Cela permettrait de définir certains équivalents des fonctions de la bibliothèque C telles que memcpy en utilisant des références.


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

  • Une référence nécessite de spécifier une fenêtre réelle.
  • Un pointeur nécessite l'emplacement d'un espace sur l'écran qui, à son avis, ne contiendra aucune ou plusieurs instances de ce type de fenêtre.

Vous avez oublié la partie la plus importante:

membre-access avec des 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 :-)


Ceci est basé sur le tutorial . Ce qui est écrit le rend plus clair:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

Simplement pour 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 faire référence à presque tous les didacticiels de pointeur, un pointeur est un objet pris en charge par l'arithmétique de 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 correct d'oublier que la terminologie d'une telle déclaration est de créer une référence de Tom.


Différence entre pointeur et référence

Un pointeur peut être initialisé à 0 et une référence non. En fait, une référence doit é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 aussiint& p=5 ; .

En fait, pour le faire correctement, nous devons d'abord avoir déclaré et défini un objet, puis nous pouvons faire référence à cet objet, de sorte que la bonne implémentation 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 cela ne peut pas ê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, donc nous vérifions généralement si le pointeur indique réellement quelque chose ou non. En cas de référence, aucune vérification de ce type n'est nécessaire car nous savons déjà que le référencement d'un objet lors de la déclaration est obligatoire.

Une autre différence est que le pointeur peut pointer sur un autre objet, bien que référence fasse 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

Autre point: lorsque nous avons un modèle comme un modèle STL, un tel type de modèle de classe renvoie toujours une référence, et non 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 "="

  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 assigné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), alors 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 peut indiquer sur la pile ou le tas. Idem une référence. Mon affirmation 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 que l'adresse est la même que la variable à laquelle elle fait 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. Le pointeur peut être affecté directement à nullptr , alors que la référence ne le peut pas. Si vous essayez assez fort et que vous savez comment faire, vous pouvez créer l’adresse d’une référence nullptr . De même, si vous essayez suffisamment, 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 pointé par un pointeur et + 4 pour aller au 5ème élément. Quelle que soit la taille de l'objet visé par le pointeur.

  6. Un pointeur doit être déréférencé avec * pour accéder à l'emplacement de la mémoire pointé, alors qu'une référence peut être utilisée directement. Un pointeur sur une classe / structure utilise -> pour accéder à ses membres alors qu'une référence utilise a . .

  7. Un pointeur est une variable qui contient une adresse mémoire. Quelle que soit la manière dont une référence est mise en œuvre, une référence a la même adresse mémoire que l'élément qu'elle référence.

  8. Les références ne peuvent pas être insérées dans un tableau, alors que les pointeurs peuvent être (Mentionné par l'utilisateur @litb)

  9. Les références de const peuvent être liées à des temporaires. Les pointeurs ne peuvent pas (pas sans quelque indirection):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    Cela rend const& safe pour une utilisation dans les listes d'arguments, etc.


En réalité, une référence ne ressemble pas vraiment à un pointeur.

Un compilateur conserve des "références" à des variables, associant un nom à une adresse mémoire; c'est son travail de traduire n'importe quel nom de variable 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 ne contient qu'une variable à laquelle elle fait référence.

Maintenant quelques explications sur le code réel:

int a = 0;
int& b = a;

Ici, vous ne créez pas une autre variable qui pointe vers a ; vous ajoutez simplement un autre nom au contenu de la mémoire contenant la valeur de a . Cette mémoire a maintenant deux noms, a et b , et il est possible de s’adresser à l’un ou l’autre

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 en tant que référence indique simplement au compilateur d'utiliser l'espace mémoire de la variable d'entrée au lieu d'allouer un nouvel espace mémoire lors de l'appel de la méthode. Cela peut sembler étrange de dire que votre fonction manipulera directement une variable déclarée dans la portée de l'appel, mais souvenez-vous que lors de l'exécution du code compilé, il n'y a plus de portée; il y a simplement une mémoire plate et votre code de fonction peut manipuler toutes les variables.

Dans certains cas, votre compilateur pourrait 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, cela ne sera probablement pas implémenté avec un pointeur.


En outre, une référence qui est un paramètre pour 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 insérant la version du pointeur obligent en réalité 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 en ligne, le pointeur et la référence génèrent le même code et il est toujours préférable de transmettre les éléments intrinsèques par valeur plutôt que par référence s'ils ne sont pas modifiés et retournés par la fonction.


Contrairement aux idées reçues, il est possible d’avoir une référence NULL.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

Certes, il est beaucoup plus difficile de faire avec une référence - mais si vous y parvenez, vous allez vous arracher les cheveux en essayant de le 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 null. C ++ ne prend pas en charge les références nulles en tant que concept, contrairement à d'autres langages. Il existe également d'autres types de références non valides. Toute référence non valide soulève le spectre d' un comportement indéfini , comme le ferait un pointeur non valide.

L'erreur réelle réside dans le déréférencement du pointeur NULL, avant l'affectation à une référence. Mais je ne suis au courant d'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érenciez un pointeur NULL, vous bloquez à cet endroit et le débogage n'est pas très long à comprendre.

Mon exemple ci-dessus est court et artificiel. Voici un exemple plus réaliste.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Je tiens à répéter que le seul moyen d'obtenir une référence null consiste à utiliser du code mal formé. Une fois que vous l'avez obtenue, vous obtenez un comportement indéfini. Il n’a jamais de sens de rechercher une référence nulle; Par exemple, vous pouvez essayer if(&bar==NULL)... mais le compilateur peut optimiser l’instruction hors existence! Une référence valide ne peut jamais être NULL. Par conséquent, du point de vue du compilateur, la comparaison est toujours fausse et il est libre d'éliminer la clause if tant que code mort. Il s'agit de l'essence du comportement indé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é pour y parvenir.

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 Références nulles de Jim Hyslop et Herb Sutter.

Pour un autre exemple des dangers liés à la déréférence d'un pointeur null, voir Exposer un comportement indéfini lors d'une tentative de transfert de code sur une autre plate-forme de Raymond Chen.


J'utilise des références sauf si j'ai besoin de l'un ou l'autre de ces éléments:

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

  • Vous pouvez faire de l'arithmétique sur un pointeur. Par exemple,p += offset;


La différence est que la variable de pointeur non constante (à ne pas confondre avec un pointeur sur constante) peut être modifiée à un moment donné au cours de l'exécution du programme, nécessite l'utilisation d'opérateurs de la sémantique du pointeur (&, *), tandis que les références peuvent être définies lors de l'initialisation only (c’est pourquoi vous pouvez les définir uniquement dans la liste d’initialisation du constructeur, mais pas autrement) et utiliser une valeur ordinaire pour accéder à la sémantique. Des références ont été introduites pour permettre la surcharge des opérateurs, comme je l'avais déjà lu dans un livre très ancien. Comme quelqu'un l'a dit dans ce fil - le pointeur peut être mis à 0 ou à la valeur que vous voulez. 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 réalité, le pointeur peut contenir une valeur qui ne pointe pas vers un emplacement de 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 d'initialiser la variable de référence sur un mauvais emplacement mémoire, il est préférable de ne pas creuser ce détail en profondeur. 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, il y a le sucre syntaxique. Les références rvalue diffèrent de celles-ci - ce sont naturellement des objets pile / tas.Bien qu'il existe de nombreuses façons d'initialiser la variable de référence sur un mauvais emplacement mémoire, il est préférable de ne pas creuser ce détail en profondeur. 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, il y a le sucre syntaxique. Les références rvalue diffèrent de celles-ci - ce sont naturellement des objets pile / tas.Bien qu'il existe de nombreuses façons d'initialiser la variable de référence sur un mauvais emplacement mémoire, il est préférable de ne pas creuser ce détail en profondeur. 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, il y a le sucre syntaxique. Les références rvalue diffèrent de celles-ci - ce sont naturellement des objets pile / tas.


Il y a une différence fondamentale entre les pointeurs et les références que je n'ai vu personne avoir mentionné: les références permettent la sémantique passage par référence dans les arguments de fonction. Les pointeurs, bien qu'ils ne soient pas visibles au début, ne le sont pas: ils fournissent uniquement une sémantique de passage par valeur. Ceci a été très bien décrit dans cet article .

Cordialement, & rzej


Une référence n'est pas un autre nom donné à une mémoire. C'est un pointeur immuable qui est automatiquement dé-référencé lors de l'utilisation. En gros, cela revient à:

int& j = i;

Il devient intérieurement

int* const j = &i;

Les références sont très similaires aux pointeurs, mais elles sont spécialement conçues pour aider à optimiser les compilateurs.

  • Les références sont conçues de telle sorte qu'il est beaucoup plus facile pour le compilateur de tracer quels alias de référence quelles variables. Deux caractéristiques principales sont très importantes: aucune "arithmétique de référence" et aucune réaffectation de références. Celles-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'adresse mémoire, telles que celles que le compilateur choisit de mettre dans des 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 optimiseur peut se rendre compte que nous accédons à un assez grand nombre de [0] et de [1]. Nous aimerions 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 faut prouver que rien ne peut changer de tableau [1] pendant l'appel. C'est plutôt facile à faire. i n'est jamais inférieur à 2, donc array [i] ne peut jamais faire référence à array [1]. Il est donné a0 comme référence à peut-être peut-êtreModify () (tableau d'aliasing [0]). Puisqu'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 qu'il a prouvé que rien ne change de tableau [1].

Il doit également prouver qu’il est impossible pour un futur appel de lire / écrire un [0] tant que nous en avons une copie dans le registre temporaire en a0. Cela 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 un tableau [1], car nous lui avons déjà donné un pointeur; le chat est sorti du sac. Il doit maintenant faire la preuve beaucoup plus difficile: une analyse statique de peut-modifier pour prouver qu’elle n’écrit jamais dans & x + 1. Elle doit également prouver qu’elle ne sauvegarde jamais un pointeur pouvant faire référence à aussi délicat.

Les compilateurs modernes s'améliorent de mieux en mieux en analyse statique, mais il est toujours agréable de les aider et d'utiliser des références.

Bien entendu, à moins d'optimisations aussi astucieuses, les compilateurs transformeront les références en indicateurs si nécessaire.

EDIT: Cinq ans après la publication de cette réponse, j’ai trouvé une différence technique réelle dans laquelle les références diffèrent d’une manière différente de voir le même concept d’adressage. Les références peuvent modifier la durée de vie des objets temporaires de la même manière que les pointeurs.

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

Normalement, les objets temporaires tels que celui créé par l'appel à createF(5) sont détruits à la fin de l'expression. Cependant, en liant cet objet à une référence ref , C ++ étendra la durée de vie de cet objet temporaire jusqu'à ce que ref disparaisse de sa portée.


C et C++ obéissent à la règle du "maximum". De la même manière, a --- b est traduit en (a--) - b , dans votre cas, x-->0 traduit par (x--)>0 .

La règle dit essentiellement que, de gauche à droite, les expressions sont formées en prenant le maximum de caractères qui formeront une expression valide.





c++ pointers reference c++-faq