Passez par référence ou passer par la valeur?

voix
48

Lors de l' apprentissage d' une nouvelle langue de programmation, l' un des obstacles possibles que vous pourriez rencontrer est la question de savoir si la langue est, par défaut, passer par valeur ou passage par référence .

Voici donc ma question à vous tous, dans votre langue préférée, comment est - il réellement fait? Et quels sont les pièges possibles ?

Votre langue préférée peut, bien sûr, être tout ce que vous avez déjà joué avec: populaire , obscur , ésotérique , nouveau , vieux ...

Créé 05/08/2008 à 09:56
source utilisateur
Dans d'autres langues...                            


11 réponses

voix
31

Voici ma contribution pour le langage de programmation Java .

d'abord un peu de code:

public void swap(int x, int y)
{
  int tmp = x;
  x = y;
  y = tmp;
}

appelant cette méthode se traduira par ceci:

int pi = 3;
int everything = 42;

swap(pi, everything);

System.out.println("pi: " + pi);
System.out.println("everything: " + everything);

"Output:
pi: 3
everything: 42"

même en utilisant des objets « réels » affichera un résultat similaire:

public class MyObj {
    private String msg;
    private int number;

    //getters and setters
    public String getMsg() {
        return this.msg;
    }


    public void setMsg(String msg) {
        this.msg = msg;
    }


    public int getNumber() {
        return this.number;
    }


    public void setNumber(int number) {
        this.number = number;
    }

    //constructor
    public MyObj(String msg, int number) {
        setMsg(msg);
        setNumber(number);
    }
}

public static void swap(MyObj x, MyObj y)
{
    MyObj tmp = x;
    x = y;
    y = tmp;
}

public static void main(String args[]) {
    MyObj x = new MyObj("Hello world", 1);
    MyObj y = new MyObj("Goodbye Cruel World", -1); 

    swap(x, y);

    System.out.println(x.getMsg() + " -- "+  x.getNumber());
    System.out.println(y.getMsg() + " -- "+  y.getNumber());
}


"Output:
Hello world -- 1
Goodbye Cruel World -- -1"

Il est donc clair que Java passe ses paramètres par valeur , comme la valeur de pi et tout et les objets MyObj ne sont pas inversés. être conscient que « en valeur » est la seule façon de java pour passer des paramètres à une méthode. (par exemple une langue comme c ++ permet au développeur de passer un paramètre par référence à l' aide « et après le type du paramètre »)

maintenant la partie la plus délicate , ou tout au moins la partie qui compliqueront la plupart des nouveaux développeurs java: (emprunté à JavaWorld )
Auteur original: Tony Sintes

public void tricky(Point arg1, Point arg2)
{
    arg1.x = 100;
    arg1.y = 100;
    Point temp = arg1;
    arg1 = arg2;
    arg2 = temp;
}
public static void main(String [] args)
{
    Point pnt1 = new Point(0,0);
    Point pnt2 = new Point(0,0);
    System.out.println("X: " + pnt1.x + " Y: " +pnt1.y); 
    System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
    System.out.println(" ");
    tricky(pnt1,pnt2);
    System.out.println("X: " + pnt1.x + " Y:" + pnt1.y); 
    System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);  
}


"Output
X: 0 Y: 0
X: 0 Y: 0
X: 100 Y: 100
X: 0 Y: 0"

délicate change avec succès la valeur de pnt1! Cela impliquerait que les objets sont passés par référence, ce n'est pas le cas! Une déclaration correcte serait: les références d'objets sont passés par valeur.

plus de Tony Sintes:

Le procédé modifie avec succès la valeur de pnt1, même si elle est passé par valeur; cependant, un swap de pnt1 et PNT2 échoue! Ceci est la principale source de confusion. Dans la principale méthode (), pnt1 et PNT2 ne sont que des références d'objet. Lorsque vous passez pnt1 et PNT2 la méthode délicate (), Java passe les références de valeur comme tout autre paramètre. Cela signifie que les références transmises à la méthode sont en fait des copies des références originales. La figure 1 ci-dessous montre deux références pointant vers le même objet Java après passage d'un objet à une méthode.

Figure 1 http://www.javaworld.com/javaworld/javaqa/2000-05/images/03-qa-0512-pass2b.gif

Conclusion ou une longue histoire courte:

  • Java transmet des paramètres par valeur
  • « par la valeur » est le seul moyen en java de passer un paramètre à une méthode
  • à l' aide de méthodes de l'objet donné en tant que paramètre modifient l'objet que les références indiquent les objets originaux. (si cette méthode elle - même modifie certaines valeurs)

Liens utiles:

Créé 05/08/2008 à 09:56
source utilisateur

voix
20

Voici un autre article pour le c # langage de programmation

c # passe ses arguments par valeur (par défaut)

private void swap(string a, string b) {
  string tmp = a;
  a = b;
  b = tmp;
}

appelant cette version de permutation aura donc aucun résultat:

string x = "foo";
string y = "bar";
swap(x, y);

"output: 
x: foo
y: bar"

Cependant, contrairement java c # ne donne le développeur la possibilité de passer des paramètres par référence , cela se fait en utilisant le mot - clé « ref » avant le type de paramètre:

private void swap(ref string a, ref string b) {
  string tmp = a;
  a = b;
  b = tmp;
} 

ce swap va changer la valeur du paramètre référencé:

string x = "foo";
string y = "bar";
swap(x, y);

"output: 
x: bar
y: foo"

c # a aussi un mot - clé sur , et la différence entre ref et out est subtile. de msdn:

L'appelant d'une méthode qui prend un paramètre de sortie n'a pas à affecter à la variable passée comme paramètre préalablement à l'appel; cependant, le callee est nécessaire d'attribuer au paramètre avant de revenir.

et

En revanche les paramètres ref sont considérés comme affectés initialement par le callee. En tant que tel, l'appelé est pas à attribuer à l'arbitre paramètre avant utilisation. Paramètres Ref sont passés à la fois dans et hors d'une méthode.

un petit écueil est, comme en Java, que des objets passés par valeur peuvent encore être modifiées à l' aide de leurs méthodes internes

conclusion:

  • c # passe ses paramètres par défaut, par valeur
  • mais lorsque les paramètres nécessaires peuvent également être transmis par référence en utilisant le mot - clé ref
  • méthodes internes d'un paramètre transmis par la valeur va modifier l'objet (si cette méthode elle - même modifie certaines valeurs)

Liens utiles:

Créé 05/08/2008 à 17:40
source utilisateur

voix
19

Python utilise le passage par valeur, mais étant donné que toutes ces valeurs sont des références d'objet, l'effet net est quelque chose de semblable à passer par référence. Cependant, les programmeurs Python pensent plus de savoir si un type d'objet est mutable ou immuable . Objets mutables peuvent être modifiés en place (par exemple, des dictionnaires, des listes, des objets définis par l' utilisateur), alors que les objets immuables ne peuvent pas (par exemple, entiers, des chaînes, tuples).

L'exemple suivant montre une fonction qui est passé deux arguments, une chaîne immuable, et une liste mutable.

>>> def do_something(a, b):
...     a = "Red"
...     b.append("Blue")
... 
>>> a = "Yellow"
>>> b = ["Black", "Burgundy"]
>>> do_something(a, b)
>>> print a, b
Yellow ['Black', 'Burgundy', 'Blue']

La ligne a = "Red"crée simplement un nom local, apour la valeur de chaîne "Red"et n'a pas d' effet sur l'argument passé dans (qui est maintenant caché, comme adoit se référer au nom local à partir de là). Cession n'est pas une opération en place, que l'argument est mutable ou immuable.

Le bparamètre est une référence à un objet de liste mutable, et le .append()procédé effectue une extension en place de la liste, vireur sur la nouvelle "Blue"valeur de chaîne.

(Parce que les objets de chaîne sont immuables, ils n'ont pas de méthodes qui prennent en charge les modifications en place.)

Une fois que la fonction retourne, la réaffectation de an'a eu aucun effet, alors que l'extension bmontre clairement le passage par référence sémantique d'appel de style.

Comme mentionné précédemment, même si l'argument pour aun type mutable, la réaffectation au sein de la fonction n'est pas une opération en place, et donc il n'y aurait pas de changement à la valeur de l'argument passé:

>>> a = ["Purple", "Violet"]
>>> do_something(a, b)
>>> print a, b
['Purple', 'Violet'] ['Black', 'Burgundy', 'Blue', 'Blue']

Si vous ne voulez pas que votre liste modifiée par la fonction appelée, vous utilisez plutôt le type de tuple immuable (identifiée par les parenthèses sous la forme littérale, plutôt que de crochets), qui ne supporte pas la place dans la .append()méthode:

>>> a = "Yellow"
>>> b = ("Black", "Burgundy")
>>> do_something(a, b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in do_something
AttributeError: 'tuple' object has no attribute 'append'
Créé 23/08/2008 à 17:50
source utilisateur

voix
7

Depuis que je ne l'ai pas vu une réponse encore Perl, je pensais écrire un.

Sous le capot, Perl fonctionne efficacement comme passage par référence. Les variables comme arguments d'appel de fonction sont transmises référentielle, les constantes sont transmises sous forme de valeurs en lecture seule, et les résultats des expressions sont passées en temporaires. Les idiomes habituels pour construire des listes d'arguments par affectation de liste de @_, ou shiftont tendance à cacher de l'utilisateur, donnant l'apparence de passage par valeur:

sub incr {
  my ( $x ) = @_;
  $x++;
}

my $value = 1;
incr($value);
say "Value is now $value";

Cela affichera Value is now 1parce que l' $x++ont entraîné un accroissement de la variable lexicale déclarée au sein de la incr()fonction, plutôt que la variable transmise. Ce style passe par valeur est généralement ce que l' on voulait la plupart du temps, comme des fonctions qui modifient leurs arguments sont rares en Perl, et le style doit être évité.

Toutefois, si pour une raison quelconque ce comportement est spécifiquement souhaité, il peut être réalisé en agissant directement sur les éléments du @_tableau, car ils seront des alias pour les variables passées dans la fonction.

sub incr {
  $_[0]++;
}

my $value = 1;
incr($value);
say "Value is now $value";

Cette fois - ci imprimera Value is now 2, parce que l' $_[0]++expression incrémente la réelle $valuevariable. La façon dont cela fonctionne est que sous le capot @_est pas un tableau réel comme la plupart des autres réseaux (comme on obtiendrait par my @array), mais ses éléments sont construits directement des arguments passés à un appel de fonction. Cela vous permet de construire la sémantique passe par référence si cela serait nécessaire. Arguments d'appel de fonction qui sont des variables simples sont insérées en-est dans ce tableau, et les constantes ou les résultats d'expressions plus complexes sont insérés comme en lecture seule temporaires.

Il est cependant très rare de le faire dans la pratique, car Perl prend en charge les valeurs de référence; qui est, les valeurs qui se réfèrent à d'autres variables. Normalement, il est beaucoup plus claire pour construire une fonction qui a un effet secondaire évident sur une variable, en faisant passer dans une référence à cette variable. Ceci est une indication claire pour le lecteur à la callsite, que la sémantique passer par référence sont en vigueur.

sub incr_ref {
  my ( $ref ) = @_;
  $$ref++;
}

my $value = 1;
incr(\$value);
say "Value is now $value";

Ici , l' \opérateur donne une référence de la même manière que l' &adresse de l' opérateur en C.

Créé 13/04/2012 à 16:33
source utilisateur

voix
6

Il y a une bonne explication ici pour .NET.

Beaucoup de gens sont surpris que les objets de référence sont effectivement passés par valeur (en C # et Java). Il est une copie d'une adresse de pile. Cela empêche une méthode de changer où l'objet pointe en réalité, mais permet encore une méthode pour changer les valeurs de l'objet. En C # il est possible de passer une référence par référence, ce qui signifie que vous pouvez changer où un des points d'objet réels.

Créé 06/08/2008 à 23:43
source utilisateur

voix
5

Ne pas oublier qu'il ya aussi passer par son nom , et passer par la valeur résultat .

Passe par la valeur-résultat est similaire à passer par valeur, avec l'aspect supplémentaire que la valeur est définie dans la variable d' origine qui a été adoptée en tant que paramètre. Il peut, dans une certaine mesure, d' éviter les interférences avec les variables globales. Il est apparemment mieux en mémoire cloisonné, où un laissez - passer par référence pourrait provoquer une défaillance de page ( de référence ).

Passe par nom signifie que les valeurs sont calculées uniquement quand ils sont utilisés, plutôt qu'au début de la procédure. Algol utilisé passe par nom, mais un effet secondaire intéressant est qu'il est très difficile d'écrire une procédure d'échange ( de référence ). En outre, l'expression adoptée par nom est réévalué chaque fois qu'il est accessible, ce qui peut aussi avoir des effets secondaires.

Créé 05/08/2008 à 11:00
source utilisateur

voix
4

Tout ce que vous dites que le passage par valeur ou passage par référence doit être cohérente dans plusieurs langues. La définition la plus commune et cohérente utilisée dans plusieurs langues est que , avec passage par référence, vous pouvez passer une variable à une fonction « normalement » (sans prendre explicitement adresse ou quelque chose comme ça), et la fonction peut affecter à (non muter le contenu) du paramètre dans la fonction et il aura le même effet que l' attribution à la variable dans le champ d' appel.

De ce point de vue, les langues sont regroupées comme suit; chaque groupe ayant la même sémantique de passage. Si vous pensez que deux langues ne devraient pas être dans le même groupe, je vous mets au défi de venir avec un exemple qui les distingue.

La grande majorité des langues , y compris C , Java , Python , Ruby , JavaScript , Scheme , OCaml , Standard ML , Go , Objective-C , Smalltalk , etc. sont tous passer par valeur uniquement . Le passage d' une valeur de pointeur (certaines langues appellent une « référence ») ne compte pas comme passage par référence; nous sommes seulement préoccupés par la chose passée, le pointeur, pas la chose souligné.

Langages tels que C ++ , C # , PHP sont par défaut passe par valeur comme les langues ci - dessus, mais les fonctions peuvent déclarer explicitement les paramètres être passe par référence, en utilisant &ou ref.

Perl est passe par référence toujours; Cependant, dans la pratique des gens copient presque toujours les valeurs après l' avoir obtenu, en utilisant donc d'une manière passe par valeur.

Créé 13/04/2012 à 21:00
source utilisateur

voix
4

en valeur

  • est plus lent que par référence puisque le système doit copier le paramètre
  • utilisé pour l'entrée uniquement

par référence

  • plus rapide puisque seul un pointeur est passé
  • utilisé pour l' entrée et la sortie
  • peut être très dangereux si elle est utilisée en conjonction avec les variables globales
Créé 05/08/2008 à 10:10
source utilisateur

voix
3

En ce qui concerne J , alors il n'y a que, autant que je sache, en passant par la valeur, il y a une forme de passage par référence qui permet le déplacement d' un lot de données. Vous passez simplement quelque chose de connu comme un lieu à un verbe (ou la fonction). Il peut être une instance d'une classe ou tout simplement un conteneur générique.

spaceused=: [: 7!:5 <
exectime =: 6!:2
big_chunk_of_data =. i. 1000 1000 100
passbyvalue =: 3 : 0
    $ y
    ''
)
locale =. cocreate''
big_chunk_of_data__locale =. big_chunk_of_data
passbyreference =: 3 : 0
    l =. y
    $ big_chunk_of_data__l
    ''
)
exectime 'passbyvalue big_chunk_of_data'
   0.00205586720663967
exectime 'passbyreference locale'
   8.57957102144893e_6

L'inconvénient évident est que vous devez connaître le nom de votre variable d'une certaine façon dans la fonction appelée. Mais cette technique peut déplacer un grand nombre de données sans douleur. Voilà pourquoi, tout passe techniquement par référence, je l'appelle « à peu près ça. »

Créé 21/11/2009 à 18:14
source utilisateur

voix
2

PHP est également passer par valeur.

<?php
class Holder {
    private $value;

    public function __construct($value) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }
}

function swap($x, $y) {
    $tmp = $x;
    $x = $y;
    $y = $tmp;
}

$a = new Holder('a');
$b = new Holder('b');
swap($a, $b);

echo $a->getValue() . ", " . $b->getValue() . "\n";

Les sorties:

a b

Cependant , dans les objets PHP4 ont été traités comme des primitifs . Ce qui signifie:

<?php
$myData = new Holder('this should be replaced');

function replaceWithGreeting($holder) {
    $myData->setValue('hello');
}

replaceWithGreeting($myData);
echo $myData->getValue(); // Prints out "this should be replaced"
Créé 11/08/2008 à 03:21
source utilisateur

voix
-1

Par défaut, la norme ANSI / ISO C utilise soit - cela dépend de la façon dont vous déclarez votre fonction et ses paramètres.

Si vous déclarez vos paramètres de fonction en tant que pointeurs alors la fonction sera passée par référence, et si vous déclarez vos paramètres de fonction en tant que variables non-pointeur alors la fonction sera passée par valeur.

void swap(int *x, int *y);   //< Declared as pass-by-reference.
void swap(int x, int y);     //< Declared as pass-by-value (and probably doesn't do anything useful.)

Vous pouvez rencontrer des problèmes si vous créez une fonction qui renvoie un pointeur vers une variable non statique qui a été créé au sein de cette fonction. La valeur retournée du code suivant serait indéfini - il n'y a aucun moyen de savoir si l'espace mémoire alloué à la variable temporaire créé dans la fonction a été écrasé ou non.

float *FtoC(float temp)
{
    float c;
    c = (temp-32)*9/5;
    return &c;
}

Vous pouvez cependant renvoyer une référence à une variable statique ou un pointeur qui a été adoptée dans la liste des paramètres.

float *FtoC(float *temp)
{
    *temp = (*temp-32)*9/5;
    return temp;
}
Créé 13/01/2011 à 21:48
source utilisateur

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more