Question Quelle est la différence entre passer par référence et passer par valeur?


Quelle est la différence entre

  1. un paramètre passé par référence
  2. un paramètre passé en valeur?

Pourriez-vous me donner des exemples, s'il vous plaît?


437
2017-12-17 01:49


origine


Réponses:


Quand un paramètre est passé par référence, l'appelant et l'appelé utiliser la même variable pour le paramètre. Si l'appelé modifie la variable de paramètre, l'effet est visible pour la variable de l'appelant.

Quand un paramètre est passé par valeur, l'appelant et appelé ont deux variables indépendantes avec la même valeur. Si l'appelé modifie la variable de paramètre, l'effet n'est pas visible pour l'appelant.

toutefois, si la valeur en question est un objet de type référence mutable ou fait référence indirectement à d’autres valeurs, vous pouvez alors imiter call-by-reference dans un environnement call-by-value: si l'appelé modifie l'objet (ou d'autres valeurs pointées par l'objet), ces modifications sont visible à l'appelant. Mais l'émulation n'est pas exactement la même, puisque seules les modifications objet, pas au variable, sont visibles. Cela conduit à des explications déformées comme "appeler par valeur où la valeur est une référence". Cet état de choses quelque peu confus est le nombre de langages de programmation populaires qui fonctionnent aujourd'hui, et donc les gens confondent souvent les objets mutables par valeur avec appel par référence.

Pour plus d'explications, voir les autres réponses ci-dessous.


REMARQUE: Pendant longtemps, cette réponse disait:

Disons que je veux partager une page Web avec vous. Si je vous dis l'URL, je suis   passant par référence. Vous pouvez utiliser cette URL pour voir la même page Web que je   peut voir. Si cette page est modifiée, nous voyons tous deux les changements. Si vous   supprimer l'URL, tout ce que vous faites détruit votre référence à cette   page - vous ne supprimez pas la page elle-même.

Si j'imprime la page et vous donne l'impression, je passe par   valeur. Votre page est une copie déconnectée de l'original. Vous ne verrez pas   les modifications ultérieures et les modifications que vous apportez (par exemple, le gribouillage   sur votre impression) n'apparaîtra pas sur la page d'origine. Si vous   détruire l'impression, vous avez effectivement détruit votre copie de la   objet - mais la page Web d'origine reste intacte.

C'est une analogie simple, facile à comprendre, qui a eu ce billet des centaines de commentaires. Cependant, l'analogie est erronée: l'appel par référence et l'appel par valeur ne sont pas comme les URL. (UNE Type de référence dans un langage comme C # est comme une URL; voir L'excellent article de Jon Skeet sur les types de références dans .NET pour plus de détails. Mais types de référence ne sont pas la même chose que passer par référence.)

Comme cette analogie n'est pas correcte, elle a été retirée de cette réponse. Voir aussi les commentaires ci-dessous où cela a été discuté.


935
2017-12-17 02:03



C'est un moyen de transmettre des arguments à des fonctions. Passer par référence signifie que le paramètre des fonctions appelées sera le même que l'argument passé par les appelants (pas la valeur, mais l'identité - la variable elle-même). Passer par valeur signifie que le paramètre des fonctions appelées sera une copie de l'argument passé de l'appelant. La valeur sera la même, mais l'identité - la variable - est différente. Ainsi, les modifications apportées à un paramètre par la fonction appelée dans un cas modifient l'argument transmis et dans l'autre cas, la valeur du paramètre dans la fonction appelée (qui n'est qu'une copie) est simplement modifiée. En toute hâte:

  • Java ne supporte que le passage par valeur. Copie toujours les arguments, même si, lors de la copie d'une référence à un objet, le paramètre de la fonction appelée pointe sur le même objet et que les modifications apportées à cet objet seront visibles dans l'appelant. Puisque cela peut être déroutant, ici est ce que Jon Skeet a à dire à ce sujet.
  • C # supporte le passage par valeur et passe par référence (mot clé ref utilisé à l'appelant et la fonction appelée). Jon Skeet a aussi une bonne explication de cette ici.
  • Les supports C ++ passent par valeur et passent par référence (type de paramètre de référence utilisé pour la fonction appelée). Vous trouverez une explication de ceci ci-dessous.

Codes

Puisque ma langue est le C ++, je vais l'utiliser ici

// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
    p = NULL;
}

// passes an integer
void call_by_value(int p) { // :2
    p = 42;
}

// passes an integer by reference
void call_by_reference(int & p) { // :3
    p = 42;
}

// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
    *p = 10; // changes what p points to ("what p references" in java)
    // only changes the value of the parameter, but *not* of 
    // the argument passed by the caller. thus it's pass-by-value:
    p = NULL;
}

int main() {
    int value = 10;
    int * pointer = &value;

    call_by_value(pointer); // :1
    assert(pointer == &value); // pointer was copied

    call_by_value(value); // :2
    assert(value == 10); // value was copied

    call_by_reference(value); // :3
    assert(value == 42); // value was passed by reference

    call_by_value_special(pointer); // :4
    // pointer was copied but what pointer references was changed.
    assert(value == 10 && pointer == &value);
}

Et un exemple en Java ne fera pas de mal:

class Example {
    int value = 0;

    // similar to :4 case in the c++ example
    static void accept_reference(Example e) { // :1
        e.value++; // will change the referenced object
        e = null; // will only change the parameter
    }

    // similar to the :2 case in the c++ example
    static void accept_primitive(int v) { // :2
        v++; // will only change the parameter
    }        

    public static void main(String... args) {
        int value = 0;
        Example ref = new Example(); // reference

        // note what we pass is the reference, not the object. we can't 
        // pass objects. The reference is copied (pass-by-value).
        accept_reference(ref); // :1
        assert ref != null && ref.value == 1;

        // the primitive int variable is copied
        accept_primitive(value); // :2
        assert value == 0;
    }
}

Wikipédia

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference

Ce type le cloue à peu près:

http://javadude.com/articles/passbyvalue.htm


116
2018-01-24 03:38



Voici un exemple:

#include <iostream>

void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }

int main()
{
    int x = 0;
    by_val(x); std::cout << x << std::endl;  // prints 0
    by_ref(x); std::cout << x << std::endl;  // prints 2

    int y = 0;
    by_ref(y); std::cout << y << std::endl;  // prints 2
    by_val(y); std::cout << y << std::endl;  // prints 2
}

49
2018-03-24 19:24



Beaucoup de réponses ici (et en particulier la réponse la plus fortement mise à jour) sont factuellement incorrectes, puisqu'elles se méprennent sur ce que signifie réellement "appel par référence". Voici ma tentative de régler les choses directement.

TL; DR

En termes simples:

  • appel par valeur signifie que vous passez valeurs comme arguments de fonction
  • appel par référence signifie que vous passez variables comme arguments de fonction

En termes métaphoriques:

  • Appel par valeur est ou J'écris quelque chose sur un bout de papier et je te le donne. Peut-être que c'est une URL, peut-être que c'est une copie complète de War and Peace. Peu importe ce que c'est, c'est sur un bout de papier que je vous ai donné, et maintenant c'est effectivement votre morceau de papier. Vous êtes maintenant libre de griffonner sur ce bout de papier ou d'utiliser ce bout de papier pour trouver quelque chose d'autre et le manipuler, peu importe.
  • Appeler par référence est quand Je vous donne mon cahier qui contient quelque chose d'écrit. Vous pouvez griffonner dans mon cahier (peut-être que je le veux, peut-être que je ne le fais pas), et ensuite je garde mon cahier avec tous les gribouillis que vous avez mis là. De plus, si ce que vous ou moi écrivons, il y a des informations sur la façon de trouver quelque chose ailleurs, vous ou moi pouvons aller là-bas et manipuler cette information.

Que "appelez en valeur" et "appel par référence" ne pas signifier

Notez que ces deux concepts sont complètement indépendants et orthogonaux du concept de types de référence (qui en Java est tous les types qui sont des sous-types de Object, et en C # tout class types), ou le concept de types de pointeurs comme en C (qui sont sémantiquement équivalentes aux "types de référence" de Java, simplement avec une syntaxe différente).

La notion de Type de référence correspond à une URL: elle est à la fois une information et c'est un référence (une aiguille, si vous voulez) à d'autres informations. Vous pouvez avoir plusieurs copies d'une URL à différents endroits, et elles ne modifient pas le site Web auquel elles sont toutes associées. Si le site Web est mis à jour, chaque copie d'URL mènera toujours à l'information mise à jour. Inversement, changer l'URL dans un endroit n'affectera aucune autre copie écrite de l'URL.

Notez que C ++ a une notion de "références" (par ex. int&) C'est ne pas comme les "types de référence" Java et C #, mais est comme "appel par référence". Les "types de référence" Java et C #, et tout les types en Python, sont comme quels C et C ++ appellent "types de pointeurs" (par ex. int*).


OK, voici l'explication plus longue et plus formelle.

Terminologie

Pour commencer, je veux mettre en évidence quelques éléments importants de la terminologie, pour aider à clarifier ma réponse et pour m'assurer que nous faisons tous référence aux mêmes idées lorsque nous utilisons des mots. (En pratique, je crois que la grande majorité de la confusion à propos de sujets tels que ceux-ci provient de l'utilisation de mots de manière à ne pas communiquer complètement le sens voulu.)

Pour commencer, voici un exemple dans un langage de type C d'une déclaration de fonction:

void foo(int param) {  // line 1
  param += 1;
}

Et voici un exemple d'appeler cette fonction:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

En utilisant cet exemple, je veux définir quelques éléments importants de la terminologie:

  • foo est un fonction déclaré sur la ligne 1 (Java insiste sur la création de toutes les méthodes de fonctions, mais le concept est le même sans perte de généralité; C et C ++ distinguent la déclaration et la définition que je n'entrerai pas ici)
  • param est un paramètre formel à foo, également déclaré sur la ligne 1
  • arg est un variable, spécifiquement un variable locale de la fonction bar, déclaré et initialisé à la ligne 2
  • arg est aussi un argument à un spécifique invocation de foo sur la ligne 3

Il y a deux ensembles de concepts très importants à distinguer ici. Le premier est valeur contre variable:

  • UNE valeur est le résultat de l'évaluation d'une expression dans la langue Par exemple, dans le bar fonction ci-dessus, après la ligne int arg = 1;, l'expression arg a la valeur  1.
  • UNE variable est un conteneur pour valeurs. Une variable peut être mutable (c'est la valeur par défaut dans la plupart des langages de type C), en lecture seule (par exemple, déclarée à l'aide de Java final ou C #'s readonly) ou profondément immuable (par exemple, en utilisant C ++) const).

L'autre paire importante de concepts à distinguer est paramètre contre argument:

  • UNE paramètre (aussi appelé un paramètre formel) est un variable qui doit être fourni par l'appelant lors de l'appel d'une fonction.
  • Un argument est un valeur qui est fourni par l'appelant d'une fonction pour satisfaire un paramètre formel spécifique de cette fonction

Appel par valeur

Dans appel par valeur, les paramètres formels de la fonction sont des variables nouvellement créées pour l'invocation de la fonction et qui sont initialisées avec le valeurs de leurs arguments.

Cela fonctionne exactement de la même manière que tous les autres types de variables sont initialisés avec des valeurs. Par exemple:

int arg = 1;
int another_variable = arg;

Ici arg et another_variable sont des variables totalement indépendantes - leurs valeurs peuvent changer indépendamment les unes des autres. Cependant, au point où another_variable est déclaré, il est initialisé pour conserver la même valeur que arg détient - qui est 1.

Comme ce sont des variables indépendantes, les changements another_variablen'affecte pas arg:

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

C'est exactement la même chose que la relation entre arg et param dans notre exemple ci-dessus, que je répéterai ici pour la symétrie:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

C'est exactement comme si nous avions écrit le code de cette façon:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

Autrement dit, la caractéristique déterminante de ce appel par valeur signifie que l'appelé (foo dans ce cas) reçoit valeurs comme arguments, mais a sa propre variables pour ces valeurs à partir des variables de l'appelant (bar dans ce cas).

Pour revenir à ma métaphore ci-dessus, si je suis bar et tu es foo, quand je vous appelle, je vous remets un morceau de papier avec un valeur écrit dessus. Vous appelez ce morceau de papier param. Cette valeur est un copie de la valeur que j'ai écrite dans mon carnet (mes variables locales), dans une variable que j'appelle arg.

(En réserve: en fonction du matériel et du système d’exploitation, il existe différents conventions d'appel sur comment vous appelez une fonction d'une autre. La convention d'appel est comme si nous décidions si j'écrivais la valeur sur un morceau de papier et que je vous la donnais, ou si vous avez un bout de papier sur lequel je l'écris ou si je l'écris sur le mur devant nous deux. C'est un sujet intéressant également, mais qui dépasse largement la portée de cette réponse déjà longue.)

Appeler par référence

Dans appel par référence, les paramètres formels de la fonction sont simplement nouveaux noms pour les mêmes variables que l'appelant fournit comme arguments.

Pour revenir à notre exemple ci-dessus, cela équivaut à:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here

Depuis param est juste un autre nom pour arg - c'est-à-dire qu'ils sont la même variable, changements à param se reflètent dans arg. C'est la manière fondamentale dont l'appel par référence diffère de l'appel par valeur.

Très peu de langues supportent l'appel par référence, mais C ++ peut le faire comme ceci:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

Dans ce cas, param n'a pas seulement le même valeur comme arg, en fait est  arg (juste par un nom différent) et ainsi bar peut observer que arg a été incrémenté.

Notez que c'est ne pas comment fonctionne Java, JavaScript, C, Objective-C, Python, ou presque n'importe quelle autre langue populaire aujourd'hui. Cela signifie que ces langues sont ne pas appel par référence, ils sont appelés par valeur.

Addendum: appel par partage d'objet

Si ce que vous avez est appel par valeur, mais la valeur réelle est un Type de référence ou type de pointeur, alors la "valeur" elle-même n’est pas très intéressante (par exemple, en C, c’est juste un entier de taille spécifique à une plate-forme) - ce qui est intéressant, c’est ce que cette valeur pointe vers.

Si ce type de référence (c'est-à-dire, le pointeur) pointe sur mutable alors un effet intéressant est possible: vous pouvez modifier la valeur pointée et l'appelant peut observer les modifications apportées à la valeur pointée, même si l'appelant ne peut pas observer les modifications du pointeur lui-même.

Pour reprendre l'analogie de l'URL, le fait que je vous ai donné un copie de l'URL vers un site Web n'est pas particulièrement intéressant si la chose qui nous préoccupe tous les deux est le site Web, pas l'URL. Le fait que vous écrasiez votre copie de l'URL n'affecte pas ma copie de l'URL n'est pas une chose qui nous préoccupe (et en fait, dans des langages comme Java et Python, la valeur de type "URL" peut ne pas être modifié du tout, seulement la chose pointée par elle peut).

Lorsqu'elle a inventé le langage de programmation CLU (qui avait ces sémantiques), Barbara Liskov s'est rendu compte que les termes existants «appel par valeur» et «appel par référence» n'étaient pas particulièrement utiles pour décrire la sémantique de ce nouveau langage. Elle a donc inventé un nouveau terme: appel par partage d'objets.

Lorsque vous parlez de langues appelées techniquement par valeur, mais où les types courants utilisés sont des types de référence ou de pointeur (c'est-à-dire presque tous les impératifs modernes, orientés objet ou multi-paradigmes), je trouve beaucoup moins déroutant évitez simplement de parler de appel par valeur ou appel par référence. S'en tenir à appel par partage d'objets (ou simplement appel par objet) et personne ne sera confus. :-)


49
2017-11-12 20:07



Avant de comprendre les 2 termes, vous DOIT comprendre ce qui suit. Chaque objet a 2 choses qui peuvent le distinguer.

  • Sa valeur.
  • Son adresse

Donc si vous dites employee.name = "John"

sachez qu'il y a 2 choses à propos de name. Sa valeur qui est "John" et aussi son emplacement dans la mémoire qui est un certain nombre hexadécimal peut-être comme ceci: 0x7fd5d258dd00.

Selon l'architecture de la langue ou le type (classe, struct, etc.) de votre objet, vous seriez transféré "John" ou 0x7fd5d258dd00

Qui passe "John" est considéré comme passant par la valeur. Qui passe 0x7fd5d258dd00 est considéré comme passant par référence. Toute personne qui pointe vers cet emplacement de mémoire aura accès à la valeur de "John".

Pour plus à ce sujet, je vous recommande de lire à propos de déréférencer un pointeur et aussi pourquoi choisir struct (type de valeur) par rapport à la classe (type de référence)


32
2018-06-26 07:31



Le moyen le plus simple d'obtenir ceci est sur un fichier Excel. Disons par exemple que vous avez deux nombres, 5 et 2 dans les cellules A1 et B1 en conséquence, et vous voulez trouver leur somme dans une troisième cellule, disons A2. Vous pouvez le faire de deux façons.

  • Soit par passer leurs valeurs à la cellule A2 en tappant = 5 + 2 dans cette cellule. Dans ce cas, si les valeurs des cellules A1 ou B1 changent, la somme dans A2 reste la même.

  • Ou par passer les "références" des cellules A1 et B1 à la cellule A2 en tappant = A1 + B1. Dans ce cas, si les valeurs des cellules A1 ou B1 changent, la somme dans A2 change également.


20
2017-12-17 01:53



Comparaison: Valeur vs Référence

Passer en valeur Les paramètres locaux sont des copies des arguments originaux transmis dans Modifications effectuées dans la fonction à ces variables n'affecte pas les originaux

Passer par référence Les paramètres locaux sont des références aux emplacements de stockage des arguments d'origine transmis. Changements à ces variables dans la fonction sera affecter les originaux Aucune copie n'est faite, donc les frais généraux de copie (temps, stockage) sont sauvegardés


18
2017-12-17 02:18



En passant par ref, vous passez essentiellement un pointeur sur la variable. Passer en valeur vous passez une copie de la variable. Dans l'utilisation de base, cela signifie normalement que les modifications apportées à la variable seront considérées comme la méthode appelante et qu'elles passeront par valeur.


17
2017-12-17 03:36



Pass by value envoie une COPIE des données stockées dans la variable que vous spécifiez, le passage par référence envoie un lien direct vers la variable elle-même. Donc, si vous passez une variable par référence, puis changez la variable dans le bloc dans lequel vous l'avez passé, la variable d'origine sera modifiée. Si vous passez simplement par valeur, la variable d'origine ne pourra pas être changée par le bloc dans lequel vous l'avez transmise mais vous obtiendrez une copie de ce qu'elle contient au moment de l'appel.


7
2017-12-27 22:57



Une différence majeure entre eux est que les variables de type valeur stockent des valeurs, ainsi la spécification d'une variable de type valeur dans un appel de méthode transmet une copie de la valeur de cette variable à la méthode. Les variables de type référence stockent des références à des objets, ainsi la spécification d'une variable de type référence en tant qu'argument transmet à la méthode une copie de la référence réelle qui fait référence à l'objet. Même si la référence elle-même est passée par valeur, la méthode peut toujours utiliser la référence reçue pour interagir avec, voire modifier l'objet d'origine. De même, lorsque vous renvoyez des informations d'une méthode via une instruction return, la méthode renvoie une copie de la valeur stockée dans une variable de type valeur ou une copie de la référence stockée dans une variable de type référence. Lorsqu'une référence est renvoyée, la méthode appelante peut utiliser cette référence pour interagir avec l'objet référencé. Donc, en effet, les objets sont toujours passés par référence.

En c #, pour passer une variable par référence afin que la méthode appelée puisse modifier celle de la variable, C # fournit des mots-clés ref et out. L'application du mot-clé ref à une déclaration de paramètre vous permet de transmettre une variable à une méthode par référence - la méthode appelée pourra modifier la variable d'origine dans l'appelant. Le mot-clé ref est utilisé pour les variables qui ont déjà été initialisées dans la méthode appelante. Normalement, lorsqu'un appel de méthode contient une variable non initialisée en tant qu'argument, le compilateur génère une erreur. La précédation d'un paramètre avec le mot-clé out crée un paramètre de sortie. Cela indique au compilateur que l'argument sera passé dans la méthode appelée par référence et que la méthode appelée assignera une valeur à la variable d'origine dans l'appelant. Si la méthode n'attribue pas de valeur au paramètre de sortie dans tous les chemins d'exécution possibles, le compilateur génère une erreur. Cela empêche également le compilateur de générer un message d'erreur pour une variable non initialisée transmise en tant qu'argument à une méthode. Une méthode peut renvoyer une seule valeur à son appelant via une instruction return, mais peut renvoyer plusieurs valeurs en spécifiant plusieurs paramètres de sortie (ref et / ou out).

voir c # discussion et exemples ici texte du lien


4
2017-07-27 13:46