web-dev-qa-db-fra.com

Quand une variable de référence est-elle appropriée et pourquoi? Pouvez-vous expliquer la syntaxe et le placement réels?

Je suis nouveau en C++. Nous avons récemment commencé à explorer les variables de référence en classe, et je suis très confus à leur sujet. Pas nécessairement comment les faire, car je comprends qu'ils changent de valeurs de variables, mais plutôt dans le sens de POURQUOI un développeur voudrait-il faire une telle chose? Que font-ils? Est-ce qu'ils économisent de la mémoire? Évitent-ils d'avoir à renvoyer des informations?

Voici une partie du projet sur lequel nous travaillons. Nous devons inclure au moins une variable de référence. Je peux voir comment j'écrirais le programme sans la variable de référence, mais je ne vois pas où une variable de référence serait utile ou nécessaire.

"L'utilisateur peut souhaiter obtenir une estimation d'une à plusieurs pièces. Les tarifs sont basés sur la superficie en pieds carrés des murs et/ou du plafond. La société estime qu'il faut 2,5 heures pour peindre 200 FS d'espace mur et 3,2 heures pour Peignez la même zone sur un plafond.Le taux de travail est de 40 $ l’heure.Si la tâche de peindre WALLS totalise plus de 1400 FS d’espace, le client reçoit un rabais de 15% pour toute la superficie au-dessus de 1400 pieds carrés. réduction pour les plafonds de peinture.

Le programme imprime un rapport final des coûts estimés dans un format professionnel.

Le programme demande à l'utilisateur s'il souhaite effectuer d'autres calculs avant de quitter. "

Je ne cherche pas vos gars à faire mes devoirs pour moi, et pour référence, nous venons juste de finir avec des fonctions d'apprentissage. Je suis assez bon, mais il y a BEAUCOUP de choses qui ne sont pas comprises dans ces sites.

Et, essentiellement, studentID serait réglé sur 21654. Ai-je bien compris?

Essayons encore ceci:

J'ai examiné cette duplication suggérée. Bien qu’il couvre les bases du pour et du contre de l’utilisation de variables de référence au lieu de pointeurs et aborde une multitude de raisons pour utiliser les deux, je remets toujours en question l’idée de base de quand (quand est approprié ou non nécessaire) et pourquoi (pourquoi est approprié dans certaines circonstances, quels avantages donne-t-il au programme?)

Je devrais utiliser de telles variables ainsi que comment (la syntaxe et le placement réels). Presque tout le monde ici a été formidable et j'ai beaucoup appris sur le sujet grâce à mes interactions avec vous. Même si cela est répétitif et irritant pour les codeurs chevronnés, tout est nouveau pour moi et je devais participer à la conversation autant que j'avais besoin d'informations. J'ai utilisé Stack Overflow pour de nombreux projets, par exemple à propos de newString.equalsIgnoreCase () de Java, et j'admire vos connaissances. Je peux seulement vous dire la vérité, si cela ne suffit pas, alors c'est ce que c'est.

Bon, laissez-moi passer en revue ma compréhension jusqu'à présent:

Les variables de référence ont tendance à réduire les modifications non désirées de variables dans une fonction et/ou un programme.

Les variables de référence sont utilisées pour modifier les variables existantes dans les fonctions

Ceci est utile car il "déplace" les valeurs tout en minimisant la copie de ces valeurs.

Les variables de référence modifient les variables existantes dans les fonctions/programmes

Je ne sais pas si vous pouvez quand même lire ceci ou non, car cela a été signalé comme un doublon. J'ai joué avec quelques-uns des mini-programmes que vous m'avez donnés, relu certaines parties de mon livre, fait d'autres recherches, etc., et je pense je comprends à un niveau rudimentaire. Ces variables de référence vous permettent de modifier et/ou d’utiliser d’autres variables dans votre code sans les extraire directement dans votre code. Je ne me souviens plus quel utilisateur utilisait l'exemple foo (hubble, bubble), mais c'est son code qui l'a finalement fait cliquer. Au lieu d'utiliser simplement la valeur, vous utilisez et/ou réaffectez la variable.

16
Eryn

Vous mélangez deux choses complètement distinctes ici. Trois exemples pour montrer comment les deux choses fonctionnent, individuellement puis ensemble ...

  1. Une fonction peut prendre un paramètre passé par valeur et renvoyer une valeur.

    double foo (double y)
    {
        y = y + 200.0;
        return y;
    }
    
    void main(void)
    {
        double hubble = 50.0;
        double bubble = 100.0;
    
        hubble = foo(bubble);
    
        std::cout << "hubble=" << hubble << ", bubble=" << bubble << std::endl;
    }
    

    Notez que parce que ceci est passé par valeur, même si foo () change y, bubble ne change pas. hubble est défini sur la valeur renvoyée par foo ().

    Alors vous obtenez

    hubble=300, bubble=100
    
  2. Une fonction peut prendre un paramètre passé par référence et modifier ce paramètre.

    void foo (double& y)
    {
        y = y + 200.0;
    }
    
    void main(void)
    {
        double hubble = 50.0;
        double bubble = 100.0;
    
        foo(bubble);
    
        std::cout << "hubble=" << hubble << ", bubble=" << bubble << std::endl;
    }
    

    Alors vous obtenez

    hubble=50, bubble=300
    

    Bien sûr, hubble n'a pas changé. Mais comme bubble a été passé par référence, la modification de y à l'intérieur de foo () change bubble, car cette modification a lieu sur la variable passée et non sur une valeur copiée.

    Notez que vous avez pas une déclaration "return" ici. La fonction ne renvoie rien - elle modifie simplement la variable qui lui est transmise.

  3. Et bien sûr, vous pouvez utiliser les deux ensemble.

    double foo (double& y)
    {
        y = y + 200.0;
        return y + 400.0;
    }
    
    void main(void)
    {
        double hubble = 50.0;
        double bubble = 100.0;
    
        hubble = foo(bubble);
    
        std::cout << "hubble=" << hubble << ", bubble=" << bubble << std::endl;
    }
    

    Alors vous obtenez

    hubble=700, bubble=300
    

    Comme précédemment, changer y dans foo () change bubble. Mais maintenant, la fonction renvoie également une valeur, qui définit hubble.

Pourquoi choisiriez-vous de renvoyer une valeur, de modifier la valeur transmise ou de faire les deux? Cela dépend entièrement de la façon dont vous écrivez votre code.

Je conviens avec vous qu'il n'est pas nécessaire d'utiliser un mot de passe par référence ici. Moi-même, je retournerais probablement juste une valeur. Mais il s’agit d’un exercice d’apprentissage, et on vous a dit de le faire de cette façon. Supposons que votre référence par référence est la réduction? Ainsi, une fonction "réduction de vide (double et valeur)" prend la valeur transmise et la multiplie par 0,85. C'est un peu artificiel, mais cela démontrerait le principe.

6
Graham

Une variable de référence n'est rien mais un nom d'alias de la variable. Vous l'utiliseriez si vous vouliez simplement transmettre la valeur au lieu de copier la même variable en mémoire à un emplacement différent. Donc, en utilisant référence, la copie peut être évitée, ce qui économise la mémoire.

Selon de FAQ de Bjarne Stroustrup :

C++ a hérité des pointeurs de C, je ne pouvais donc pas les supprimer sans causant de graves problèmes de compatibilité. Les références sont utiles pour plusieurs choses, mais la raison directe pour laquelle je les ai introduites en C++ était: soutenir l'opérateur en surcharge. Par exemple:

void f1(const complex* x, const complex* y)    // without references
    {
        complex z = *x+*y;    // ugly
        // ...
    }

    void f2(const complex& x, const complex& y)    // with references
    {
        complex z = x+y;    // better
        // ...
    }

Plus généralement, si vous voulez avoir à la fois la fonctionnalité de pointeurs et la fonctionnalité des références, vous avez besoin soit de deux .__ différents. types (comme en C++) ou deux jeux d'opérations différents sur un même fichier type. Par exemple, avec un seul type, vous avez besoin d’une opération pour assigner à l'objet référencé et une opération à assigner à référence/pointeur. Cela peut être fait en utilisant des opérateurs séparés (comme dans Simula). Par exemple:

Ref<My_type> r :- new My_type;
r := 7;            // assign to object
r :- new My_type;    // assign to reference

Vous pouvez également utiliser la vérification de type (surcharge). Pour Exemple:

Ref<My_type> r = new My_type;
r = 7;            // assign to object
r = new My_type;    // assign to reference

Lisez aussi cette question de débordement de pile sur les différences entre une variable de pointeur et une variable de référence.

20
M.S Chaudhari

Je vais donner trois raisons, mais il y en a beaucoup plus.

  1. Éviter les copies inutiles.

    Supposons que vous écriviez une fonction comme ceci:

    double price(std::vector<Room> rooms)
    {
           ...
    }
    

    Maintenant, chaque fois que vous l'appelez, le vecteur de Room sera copié. Si vous ne calculez que le prix de quelques pièces, c'est bien, mais si vous souhaitez calculer le coût de la peinture de l'ensemble des bureaux de l'Empire State Building, vous commencerez à copier des objets énormes, ce qui prend du temps.

    Dans ce cas, il vaut mieux utiliser une référence constante qui fournit un accès en lecture seule aux données:

    double price(const std::vector<Room>& rooms) { ... }
    
  2. Utilisation de polymorphisme

    Supposons que vous ayez maintenant différents types de pièces, peut-être une CubicRoom et une CylindricalRoom, qui héritent toutes deux de la même classe de base, Room.

    Il n'est pas possible d'écrire:

    double price(Room room) { ... }
    

    et ensuite appeler

    price(CylindricalRoom());
    //or
    price(CubicRoom());
    

    mais vous pouvez si vous définissez price comme suit:

    double price(Room& room);
    

    Tout fonctionne alors comme si vous passiez par valeur.

  3. Éviter les retours

    Supposons que chaque fois que vous calculez un prix, vous souhaitez ajouter un devis mis en forme à un rapport. En C++, vous ne pouvez retourner qu'un seul objet à partir d'une fonction, vous ne pouvez donc pas écrire:

    return price, fmtQuote
    

    Cependant, vous pouvez faire:

    double price(Room room, std::vector<std::string>& quotes)
    {
        ...
        quotes.Push_back(fmtQuote);
        return price
    }
    

    De toute évidence, vous pouvez renvoyer une paire d'objets std::pair<double, std::string>, mais cela signifie que l'appelant doit décompresser le résultat. Si vous avez l’intention d’appeler souvent la fonction ci-dessus, elle deviendra rapidement laide. Dans ce cas, cela rejoint le premier point: le journal de tous les guillemets augmentera et vous voudrez le {pas} _ le copier pour chaque appel.

    Il s'agit d'un modèle d'accès typique pour les partagées ressources: vous souhaitez que quelques fonctions/objets obtiennent un descripteur sur une ressource, et non un copie de cette ressource.

10
Urukann

Les variables de référence sont une alternative plus sûre aux pointeurs. Habituellement, lorsque vous utilisez des pointeurs, vous vous souciez peu du pointeur (ptr) que de ce à quoi il pointe (*ptr); Et pourtant, tous les programmeurs gachent et manipulent ptr au lieu de *ptr et ainsi de suite. Considérons ce code:

void zeroize_by_pointer(int* p)
{
    p = 0; // this compiles, but doesn't do what you want
}

Comparer à la version de référence,

void zeroize_by_reference(int& p)
{
    p = 0; // works fine
}

Il y a beaucoup d'autres raisons pour lesquelles les références sont une bonne idée, mais pour quelqu'un débutant en C++, je vous conseillerais de vous concentrer sur celui-ci: il est légèrement plus difficile de se tirer une balle dans le pied. Chaque fois que vous traitez avec des pointeurs, vous allez traiter à un certain niveau le modèle de mémoire de la machine, et c'est une bonne chose à éviter lorsque cela est possible.

5
Rob Hansen

Les références ont été introduites principalement pour prendre en charge la surcharge des opérateurs. Utiliser des pointeurs pour "passer par référence" vous donnerait une syntaxe inacceptable selon Bjarne Stroustrup. Ils permettent également le crénelage.

De plus, ils permettent une programmation orientée objet avec une syntaxe plus agréable que l'utilisation explicite d'un pointeur. Si vous utilisez des classes, vous devez indiquer des références pour éviter le découpage d'objet .

En résumé, vous devriez toujours préférer utiliser des références aux pointeurs nus.

4
Rayniery

Il existe un autre avantage plus général des références que les pointeurs ne fournissent pas. Les références, de par leur nature même, vous permettent d’exprimer par le biais de la signature de la fonction que l’objet auquel il est fait référencedoit exister au moment où la fonction est appeléeAucune valeur autorisée. 

L'appelant ne peut pas raisonnablement s'attendre à une fonction qui prend une référence pour vérifier la validité de cette référence. 

Les pointeurs, par contre, peuvent valablement être nuls. Si j'écris une fonction qui accepte un pointeur ...

void increment(int* val)
{ 
    (*val)++;
}

... et l'appelant fournit null, mon programme va probablement planter. Je peux écrire toute la documentation voulue en précisant que le pointeur ne doit pas être nul, mais le fait est que c'est assez facile pour quelqu'un de la transmettre accidentellement. Donc, si je veux être en sécurité, je dois vérifier.

Mais écrivez cette fonction avec une référence et l'intention est claire. Aucune null n'est autorisée.

4
Joe

Il existe également différents types de références. Nous avons les références lvalue et rvalue, désignées respectivement par & et &&. Généralement, une référence nous dit quelque chose sur la durée de vie de l'objet qu'elle référence, pas un pointeur. Comparer

void foo(int* i);
void foo(int& i);
void foo(int&& i);

Dans le premier cas, i peut désigner un objet que nous pouvons affecter, mais plus important encore, il peut également s'agir d'une nullptr ou d'un point à la fin du tableau. Ainsi, le déréférencement peut conduire à un comportement indéfini. Vérifier une nullptr est assez facile, l'autre vérification ne l'est pas.

Les deuxième et troisième cas, i, doivent toujours faire référence à une int valide que nous pouvons également affecter.

La différence entre les références rvalue et lvalue réside dans le fait que les références rvalue/&& donnent à entendre que la valeur référencée n’est utile à personne d’autre et qu’elles permettent donc des optimisations. Lisez sur std::move et déplacez les constructeurs pour voir ce que je veux dire.


Pour résumer: les références nous disent quelque chose sur la durée de vie de l'objet. Certes, cela pourrait être indiqué dans la documentation, mais avec des indicateurs, les violations de ce contrat pourraient être difficiles à détecter. Les références appliquent le contrat (dans une large mesure) au moment de la compilation et fournissent de ce fait une documentation implicite au code. Cela permet certaines optimisations rapides et simples en utilisant par exemple déplacez les constructeurs ou perfectionnez la transmission dans certains cas.

1
WorldSEnder

Vous pouvez presque toujours utiliser des variables de référence (au lieu de toujours passer par valeur): par exemple ...

// this function creates an estimate
// input parameter is the Rooms to be painted
// passed as a const reference because this function doesn't modify the rooms
// return value is the estimated monetary cost
Money createEstimate(const Rooms& rooms)
{
  ...
}

// this function adds Paint to the rooms
// input parameter is the Rooms to be painted
// passed as a non-const reference because this function modifies the rooms
void paintRooms(Rooms& rooms)
{
  ...
}

Lorsque vous transmettez valeur par valeur au lieu de passer par référence, vous créez et transmettez implicitement une copie de la chose ...

// creates and passes a copy of the Rooms to the createEstimate function
Money createEstimate(Rooms rooms)
{
  ...
}

... qui (créer une copie) est (souvent, légèrement) plus lent que de passer par référence (en outre, créer une copie peut avoir des effets secondaires).

En tant que possible légère optimisation des performances, et par convention (parce que les gens ne s'en soucient pas), il est courant de passer par valeur au lieu de passer-être-référence lorsque le type est petit et simple (aka type "primitif") par exemple:

// passes a copy of the x and y values
// returns the sum
int add(int x, int y)
{
  ...
}

... au lieu de ...

// passes a reference to x and y
// returns the sum
int add(const int& x, const int& y)
{
  ...
}

Voir aussi Passage d'un paramètre modifiable à la fonction c ++ ainsi que Pourquoi avoir des paramètres de pointeur?

1
ChrisW

Les arguments de référence sont plus utilisés lorsque vous passez un objet en argument. De cette façon, vous ne copiez pas la variable entière; ils viennent généralement avec un modificateur const comme:

void printDescription(const Person& person) { ... }

De cette façon, vous ne copiez pas l'objet.

Parfois, le type de retour est également défini comme référence. De cette façon, vous retournez le même objet (et non une copie de celui-ci). Regardez l'opérateur << de ostream. ostream& operator<< (streambuf* sb );.

Avec les variables, vous pouvez penser au cas où vous pouvez échanger des valeurs.

void swap(int & a, int & b) {
    int aux = a;
    int a = b;
    int b = aux;
}

Ce cas en Java, par exemple, doit être traité de manière plus complexe.