language-agnostic - valeur - transmission par référence




Passer par référence ou passer par valeur? (8)

Lors de l'apprentissage d'un nouveau langage de programmation, l'un des obstacles potentiels que vous pouvez rencontrer est de savoir si le langage est, par défaut, passe-à-valeur ou passe-à-référence .

Alors voici ma question à vous tous, dans votre langue préférée, comment fait-on réellement? Et quels sont les pièges possibles ?

Votre langue préférée peut bien sûr être celle avec laquelle vous avez jamais joué: popular , obscure , esoteric , new , old ...


Comme je n'ai pas encore vu de réponse Perl, j'ai pensé en écrire une.

Sous le capot, Perl fonctionne efficacement comme référence par passe. Les variables en tant qu'arguments d'appel de fonction sont transmises de manière référentielle, les constantes sont transmises en tant que valeurs en lecture seule et les résultats d'expressions sont transmis en tant que données temporaires. Les idiomes habituels pour construire des listes d'arguments par assignation de liste à partir de @_ , ou par équipe ont tendance à le cacher à l'utilisateur, donnant ainsi l'apparence d'un passage par valeur:

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

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

Cela affichera Value is now 1 $x++ Value is now 1 car $x++ a incrémenté la variable lexicale déclarée dans la fonction incr() , plutôt que la variable transmise. Ce style de définition de la valeur de passage est généralement celui recherché le plus souvent, sous la forme de 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é, vous pouvez y @_ en agissant directement sur les éléments du tableau @_ , car ils constitueront des alias pour les variables transmises à la fonction.

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

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

Cette fois, la Value is now 2 $value à Value is now 2 , car l'expression $_[0]++ incrémenté la variable $value réelle. La façon dont cela fonctionne est que, sous le capot, @_ n’est pas un véritable tableau comme la plupart des autres tableaux (tels que ceux obtenus par my @array ), mais que ses éléments sont construits directement à partir des arguments passés à un appel de fonction. Cela vous permet de construire une sémantique passe-à-référence si nécessaire. Les arguments d'appel de fonction qui sont des variables simples sont insérés tels quels dans ce tableau et les constantes ou les résultats d'expressions plus complexes sont insérés en tant que temporaires en lecture seule.

Il est cependant extrêmement rare de le faire dans la pratique, car Perl supporte les valeurs de référence; c'est-à-dire des valeurs qui font référence à d'autres variables. Normalement, il est beaucoup plus clair de construire une fonction qui a un effet secondaire évident sur une variable, en passant une référence à cette variable. Ceci indique clairement au lecteur du site d’appel que la sémantique de référence par référence est en vigueur.

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

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

Ici, l'opérateur \ renvoie une référence de la même manière que l'opérateur & address-of dans C.


En ce qui concerne J , alors qu’il n’ya que AFAIK, en passant par valeur, il existe une forme de passage par référence qui permet de déplacer beaucoup de données. Vous passez simplement quelque chose connu sous le nom de paramètres régionaux à un verbe (ou une fonction). Il peut s'agir d'une instance de classe ou simplement d'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 manière ou d'une autre dans la fonction appelée. Mais cette technique peut déplacer beaucoup de données sans douleur. C’est pourquoi, même si, techniquement, nous ne le passons pas par référence, j’appelle cela «à peu près ça».


N'oubliez pas qu'il y a aussi passe par nom et passe par valeur-résultat .

Passe par valeur-résultat est similaire à passe par valeur, avec le fait que la valeur est définie dans la variable d'origine qui a été transmise en tant que paramètre. Cela peut, dans une certaine mesure, éviter les interférences avec les variables globales. C'est apparemment mieux en mémoire partitionnée, où un passage par référence pourrait causer une erreur de page ( Reference ).

Passer par nom signifie que les valeurs ne sont calculées que lorsqu'elles sont réellement utilisées, et non au début de la procédure. Algol a utilisé passe par nom, mais un effet secondaire intéressant est qu’il est très difficile d’écrire une procédure de swap ( Reference ). En outre, l'expression passée par nom est réévaluée à chaque accès, ce qui peut également avoir des effets secondaires.


PHP est également passé 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, en PHP4, les objets étaient traités comme des primitives . 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"

Tout ce que vous dites, valeur par valeur ou référence par référence, doit être cohérent dans toutes les langues. La définition la plus commune et la plus cohérente utilisée dans toutes les langues est qu'avec Pass-by-Reference, vous pouvez passer une variable à une fonction "normalement" (c'est-à-dire sans prendre explicitement l'adresse ou quoi que ce soit de la sorte), et la fonction peut affecter à le contenu de) le paramètre à l'intérieur de la fonction et cela aura le même effet que l'affectation à la variable dans la portée de l'appelant.

À partir de cette 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 mises dans le même groupe, je vous mets au défi de proposer un exemple qui les distingue.

La grande majorité des langages, notamment C , Java , Python , Ruby , JavaScript , Scheme , OCaml , ML Standard , Go , Objective-C , Smalltalk , etc., sont tous transmis uniquement par valeur . Passer une valeur de pointeur (certaines langues l'appellent une "référence") ne compte pas comme passe par référence; nous ne nous intéressons qu'à la chose passée, au pointeur, pas à la chose indiquée.

Les langages tels que C ++ , C # , PHP sont par défaut pass-by-value comme les langages ci-dessus, mais les fonctions peuvent explicitement déclarer les paramètres pass-by-reference, en utilisant & ou ref .

Perl est toujours passé par référence; Cependant, dans la pratique, les gens copient presque toujours les valeurs après l'avoir obtenue, de ce fait, ils sont utilisés de manière répétée.


Voici ma propre contribution pour le langage de programmation Java .

d'abord du code:

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

l'appel de cette méthode entraînera 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", le résultat sera 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 , car la valeur de pi et de tout le reste et les objets MyObj ne sont pas échangés. sachez que "par valeur" est le seul moyen en Java de passer des paramètres à une méthode. (Par exemple, un langage comme c ++ permet au développeur de passer un paramètre par référence en utilisant ' & ' après le type du paramètre)

maintenant la partie délicate , ou du moins la partie qui déroutera 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élicat change avec succès la valeur de pnt1! Cela impliquerait que les objets soient passés par référence, ce n'est pas le cas! Une déclaration correcte serait: les références d'objet sont passées par valeur.

plus de Tony Sintes:

La méthode modifie avec succès la valeur de pnt1, même si elle est passée par valeur; Cependant, un échange de pnt1 et pnt2 échoue! C'est la principale source de confusion. Dans la méthode main (), pnt1 et pnt2 ne sont rien de plus que des références d’objet. Lorsque vous transmettez pnt1 et pnt2 à la méthode tricky (), Java transmet les références par valeur, comme tout autre paramètre. Cela signifie que les références transmises à la méthode sont en réalité des copies des références d'origine. La figure 1 ci-dessous montre deux références pointant sur le même objet après que Java a passé un objet à une méthode.


(source: javaworld.com )

Conclusion ou une longue histoire courte:

  • Java passe les paramètres par valeur
  • "par valeur" est le seul moyen en Java de passer un paramètre à une méthode
  • utiliser des méthodes de l'objet donné en paramètre modifiera l'objet car les références pointent vers les objets d'origine. (si cette méthode modifie elle-même certaines valeurs)

Liens utiles:


Python utilise passage par valeur, mais comme toutes ces valeurs sont des références d'objet, l'effet net s'apparente à un passage par référence. Cependant, les programmeurs Python réfléchissent davantage à la question de savoir si un type d'objet est modifiable ou immuable . Les objets mutables peuvent être modifiés sur place (par exemple, dictionnaires, listes, objets définis par l'utilisateur), alors que les objets immuables ne le peuvent pas (par exemple, les entiers, les chaînes, les n-uplets).

L'exemple suivant montre une fonction à laquelle sont passés 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, a , pour la valeur de chaîne "Red" et n'a aucun effet sur l'argument transmis (qui est maintenant masqué, car il doit désormais faire référence au nom local). . L'affectation n'est pas une opération sur place, que l'argument soit modifiable ou immuable.

Le paramètre b est une référence à un objet de liste modifiable, et la méthode .append() effectue une extension sur place de la liste, en s’ajoutant à la nouvelle valeur de chaîne "Blue" .

(Les objets chaîne étant immuables, ils ne disposent d'aucune méthode prenant en charge les modifications sur place.)

Une fois que la fonction est revenue, la réaffectation de a n'a eu aucun effet, tandis que l'extension de b montre clairement la sémantique des appels de style passage par référence.

Comme mentionné précédemment, même si l'argument pour a est un type mutable, la réaffectation au sein de la fonction n'est pas une opération sur place, et la valeur de l'argument transmis ne serait donc pas modifiée:

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

Si vous ne souhaitez pas que votre liste soit modifiée par la fonction appelée, vous utiliserez plutôt le type de tuple immuable (identifié par les parenthèses dans la forme littérale, plutôt que les crochets), qui ne prend pas en charge le. .append() in-place .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'

par valeur

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

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 s'il est utilisé avec des variables globales




pass-by-value