web-dev-qa-db-fra.com

Renvoi de pointeurs / références C ++

J'ai une assez bonne compréhension de l'opérateur de déréférencement, de l'adresse de l'opérateur et des pointeurs en général.

Je suis cependant confus quand je vois des choses comme celle-ci:

int* returnA() {
    int *j = &a;
    return j;
}

int* returnB() {
    return &b;
}

int& returnC() {
    return c;
}

int& returnC2() {
    int *d = &c;
    return *d;
}
  1. Dans returnA() je demande de retourner un pointeur; juste pour clarifier cela fonctionne parce que j est un pointeur?
  2. Dans returnB() je demande de retourner un pointeur; puisqu'un pointeur pointe vers une adresse, la raison pour laquelle returnB() fonctionne est parce que je retourne &b?
  3. Dans returnC() je demande une adresse de int à renvoyer. Lorsque je renvoie c, l'opérateur & Est-il automatiquement "ajouté" c?
  4. Dans returnC2() je demande à nouveau une adresse de int à renvoyer. *d Fonctionne-t-il parce que les pointeurs pointent vers une adresse?

Supposons que a, b, c soient initialisés sous forme d'entiers comme Global.

Est-ce que quelqu'un peut valider si j'ai raison avec mes quatre questions?

45
anon235370

En retourA () je demande de retourner un pointeur; juste pour clarifier cela fonctionne parce que j est un pointeur?

Oui, int *j = &a initialise j pour pointer vers a. Vous retournez ensuite la valeur de j, c'est-à-dire l'adresse de a.

Dans returnB (), je demande de retourner un pointeur; puisqu'un pointeur pointe vers une adresse, la raison pour laquelle returnB () fonctionne parce que je retourne & b?

Oui. Ici, la même chose se produit que ci-dessus, juste en une seule étape. &b donne l'adresse de b.

Dans returnC () je demande une adresse int à renvoyer. Quand je reviens c est-ce que l'opérateur & est automatiquement ajouté?

Non, c'est une référence à un int qui est retourné. Une référence n'est pas une adresse de la même manière qu'un pointeur - c'est juste un autre nom pour une variable. Vous n'avez donc pas besoin d'appliquer le & opérateur pour obtenir la référence d'une variable.

Dans returnC2 (), je demande à nouveau une adresse int à renvoyer. * D fonctionne-t-il parce que les pointeurs pointent vers une adresse?

Encore une fois, c'est une référence à un int qui est retourné. *d fait référence à la variable d'origine c (quelle qu'elle soit), pointée par c. Et cela peut implicitement être transformé en référence, tout comme dans returnC.

Les pointeurs ne pointent généralement pas vers une adresse (bien qu'ils puissent - par exemple int** est un pointeur vers un pointeur vers int). Pointeurs sont une adresse de quelque chose. Lorsque vous déclarez le pointeur comme something*, que something est la chose vers laquelle pointe votre pointeur. Donc, dans mon exemple ci-dessus, int** déclare un pointeur sur un int*, qui se trouve être un pointeur lui-même.

28
Péter Török

Bien que Peter ait répondu à votre question, une chose qui vous perturbe clairement est les symboles * et &. Le plus difficile à comprendre est qu'ils ont tous deux deux significations différentes liées à l'indirection (même en excluant les troisièmes significations de * pour la multiplication et & pour les bits et).

  • *, lorsqu'il est utilisé dans le cadre d'un type indique que le type est un pointeur: int est un type, donc int* est un type pointeur vers int et int** est un type pointeur vers pointeur vers int.

  • & lorsqu'il est utilisé dans le cadre d'un type indique que le type est une référence. int est un type, donc int& est une référence à int (il n'y a pas de référence à référence). Les références et les pointeurs sont utilisés pour des choses similaires, mais ils sont assez différents et non interchangeables. Il est préférable de considérer une référence comme un alias ou un autre nom pour une variable existante. Si x est un int, vous pouvez simplement affecter int& y = x pour créer un nouveau nom y pour x. Afterwords, x et y peuvent être utilisés de manière interchangeable pour désigner le même entier. Les deux principales implications de ceci sont que les références ne peuvent pas être NULL (car il doit y avoir une variable d'origine à référencer), et que vous n'avez pas besoin d'utiliser un opérateur spécial pour obtenir la valeur d'origine (car il s'agit simplement d'un autre nom, pas un pointeur). Les références ne peuvent pas non plus être réaffectées.

  • * lorsqu'il est utilisé comme opérateur unaire effectue une opération appelée déréférence (qui n'a rien à voir avec la référence types!). Cette opération n'a de sens que sur les pointeurs. Lorsque vous déréférencer un pointeur, vous récupérez ce qu'il pointe. Donc, si p est un pointeur vers int, *p est le int pointé.

  • & lorsqu'il est utilisé comme opérateur unaire effectue une opération appelée adresse-de. C'est assez explicite; si x est une variable, alors &x est l'adresse de x. L'adresse d'une variable peut être affectée à un pointeur sur le type de cette variable. Donc, si x est un int, alors &x peut être affecté à un pointeur de type int*, et ce pointeur pointera sur x. Par exemple. si vous attribuez int* p = &x, puis *p peut être utilisé pour récupérer la valeur de x.

N'oubliez pas, le suffixe de type & est pour les références, et n'a rien à voir avec l'opératoire unaire &, qui consiste à obtenir des adresses à utiliser avec des pointeurs. Les deux utilisations sont totalement indépendantes. Et * en tant que suffixe de type déclare un pointeur, tandis que * en tant qu'un opérateur unaire effectue une action sur les pointeurs.

74
Tyler McHenry

Tyler, c'était une explication très utile, j'ai fait une expérience en utilisant le débogueur Visual Studio pour clarifier encore plus cette différence: -

int sample = 90;
int& alias = sample;
int* pointerToSample  = &sample;

Name                  Address                        Type
&alias                0x0112fc1c {90}                int *
&sample               0x0112fc1c {90}                int *
pointerToSample       0x0112fc1c {90}                int *
*pointerToSample    90                       int
alias   90                                       int &
&pointerToSample      0x0112fc04 {0x0112fc1c {90}}   int * *

Disposition de la mémoire

PointerToSample       Sample/alias
_______________......____________________
0x0112fc1c |          |   90   |
___________|___.....__|________|_______...

[0x0112fc04]  ...      [0x0112fc1c
5
Berhe Abrha

Dans returnC () et returnC2 (), vous ne demandez pas de renvoyer l'adresse.

Ces deux fonctions renvoient des références aux objets.
. dans le registre)).

Tout ce que vous savez, c'est qu'une référence pointe vers un objet spécifique.
Alors qu'une référence elle-même n'est pas un objet juste un nom alternatif.

3
Martin York

Tous vos exemples produisent un comportement d'exécution indéfini. Vous renvoyez des pointeurs ou des références à des éléments qui disparaissent après l'exécution quitte la fonction.

Permettez-moi de clarifier:

int * returnA()
{
  static int a;  // The static keyword keeps the variable from disappearing. 
  int * j = 0;   // Declare a pointer to an int and initialize to location 0.
  j = &a;        // j now points to a.
  return j;      // return the location of the static variable (evil).
}

Dans votre fonction, la variable j est affectée pour pointer vers l'emplacement temporaire de a. A la sortie de votre fonction, la variable a disparaît, mais son ancien emplacement est retourné via j. Étant donné que a n'existe plus à l'emplacement indiqué par j, un comportement indéfini se produira lors de l'accès à *j.

Les variables à l'intérieur des fonctions ne doivent pas être modifiées via une référence ou un pointeur par un autre code. Cela peut arriver même si cela produit un comportement indéfini.

Étant pédant, les pointeurs renvoyés doivent être déclarés comme pointant vers des données constantes. Les références retournées doivent être const:

const char * Hello()
{
  static const char text[] = "Hello";
  return text;
}

La fonction ci-dessus renvoie un pointeur sur des données constantes. Un autre code peut accéder (lire) aux données statiques mais ne peut pas être modifié.

const unsigned int& Counter()
{
  static unsigned int value = 0;
  value = value + 1;
  return value;
}

Dans la fonction ci-dessus, le value est initialisé à zéro sur la première entrée. Toutes les prochaines exécutions de cette fonction entraînent l'incrémentation de value d'une unité. La fonction renvoie une référence à une valeur constante. Cela signifie que d'autres fonctions peuvent utiliser la valeur (de loin) comme s'il s'agissait d'une variable (sans avoir à déréférencer un pointeur).

À mon avis, un pointeur est utilisé pour un paramètre ou un objet facultatif. Une référence est transmise lorsque l'objet doit exister. À l'intérieur de la fonction, un paramètre référencé signifie que la valeur existe, cependant un pointeur doit être vérifié pour null avant de le déréférencer. De plus, avec une référence, il y a plus de garantie que l'objet cible est valide. Un pointeur peut pointer vers une adresse non valide (non nulle) et provoquer un comportement indéfini.

2
Thomas Matthews

Sémantiquement, les références agissent comme des adresses. Cependant, syntaxiquement, ils sont le travail du compilateur, pas le vôtre, et vous pouvez traiter une référence comme s'il s'agissait de l'objet d'origine vers lequel elle pointe, y compris en y liant d'autres références et en les faisant également référence à l'objet d'origine. Dites adieu à l'arithmétique des pointeurs dans ce cas.

L'inconvénient est que vous ne pouvez pas modifier ce à quoi ils se réfèrent - ils sont liés au moment de la construction.

1
Puppy