language agnostic - how - ¿Pasar por referencia o pasar por valor?
how to pass by reference in java (11)
Al aprender un nuevo lenguaje de programación, uno de los posibles obstáculos que puede encontrar es la pregunta de si el idioma es, por defecto, paso por valor o paso por referencia .
Así que aquí está mi pregunta para todos ustedes, en su idioma favorito, ¿cómo se hace realmente? ¿Y cuáles son las posibles trampas ?
Tu idioma favorito puede ser, por supuesto, cualquier cosa con la que hayas jugado: popular , obscure , esoteric , new , old ...
Aquí está mi propia contribución para el lenguaje de programación Java .
primero un código:
public void swap(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
llamar a este método dará como resultado esto:
int pi = 3;
int everything = 42;
swap(pi, everything);
System.out.println("pi: " + pi);
System.out.println("everything: " + everything);
"Output:
pi: 3
everything: 42"
incluso el uso de objetos ''reales'' mostrará un resultado similar:
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"
por lo tanto, está claro que Java pasa sus parámetros por valor , ya que el valor de pi y todo y los objetos MyObj no se intercambian. tenga en cuenta que "por valor" es la única forma en Java de pasar parámetros a un método. (por ejemplo, un lenguaje como c ++ permite al desarrollador pasar un parámetro por referencia usando '' & '' después del tipo de parámetro)
ahora la parte difícil , o al menos la parte que confundirá a la mayoría de los nuevos desarrolladores de Java: (tomado de javaworld )
Autor 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"
complicado cambia con éxito el valor de pnt1! Esto implicaría que los objetos se pasan por referencia, ¡este no es el caso! Una declaración correcta sería: las referencias a objetos se pasan por valor.
más de Tony Sintes:
El método altera con éxito el valor de pnt1, aunque se pasa por valor; sin embargo, un intercambio de pnt1 y pnt2 falla. Esta es la principal fuente de confusión. En el método main (), pnt1 y pnt2 no son más que referencias de objeto. Cuando pasa pnt1 y pnt2 al método tricky (), Java pasa las referencias por valor como cualquier otro parámetro. Esto significa que las referencias pasadas al método son en realidad copias de las referencias originales. La Figura 1 a continuación muestra dos referencias que apuntan al mismo objeto después de que Java pasa un objeto a un método.
(fuente: javaworld.com )
Conclusión o una larga historia corta:
- Java pasa sus parámetros por valor
- "por valor" es la única forma en Java de pasar un parámetro a un método
- El uso de métodos del objeto dado como parámetro alterará el objeto a medida que las referencias apuntan a los objetos originales. (si ese método en sí altera algunos valores)
Enlaces útiles:
Aquí hay otro artículo para el lenguaje de programación C #
c # pasa sus argumentos por valor (por defecto)
private void swap(string a, string b) {
string tmp = a;
a = b;
b = tmp;
}
llamar a esta versión de intercambio no tendrá ningún resultado:
string x = "foo";
string y = "bar";
swap(x, y);
"output:
x: foo
y: bar"
sin embargo, a diferencia de java c # le da al desarrollador la oportunidad de pasar parámetros por referencia , esto se hace usando la palabra clave ''ref'' antes del tipo del parámetro:
private void swap(ref string a, ref string b) {
string tmp = a;
a = b;
b = tmp;
}
Este intercambio cambiará el valor del parámetro referenciado:
string x = "foo";
string y = "bar";
swap(x, y);
"output:
x: bar
y: foo"
c # también tiene una palabra clave out , y la diferencia entre ref y out es sutil. de msdn:
El llamador de un método que toma un parámetro de salida no está obligado a asignar a la variable pasada como parámetro de salida antes de la llamada; sin embargo, el destinatario debe asignar el parámetro de salida antes de regresar.
y
En contraste, los parámetros de referencia se consideran inicialmente asignados por la persona que llama. Como tal, la persona que llama no está obligada a asignar el parámetro ref antes de usarlo. Los parámetros de referencia se pasan dentro y fuera de un método.
Un pequeño inconveniente es, como en Java, que los objetos pasados por valor todavía se pueden cambiar utilizando sus métodos internos.
conclusión:
- c # pasa sus parámetros, por defecto, por valor
- pero cuando los parámetros necesarios también se pueden pasar por referencia usando la palabra clave ref
- los métodos internos de un parámetro pasado por valor alterarán el objeto (si ese método en sí altera algunos valores)
Enlaces útiles:
Aquí hay una buena explicación para .NET.
Mucha gente se sorprende de que los objetos de referencia se pasen realmente por valor (tanto en C # como en Java). Es una copia de una dirección de pila. Esto evita que un método cambie a dónde apunta realmente el objeto, pero aún permite que un método cambie los valores del objeto. En C # es posible pasar una referencia por referencia, lo que significa que puede cambiar a dónde apunta un objeto real.
Como todavía no he visto una respuesta de Perl, pensé en escribir una.
Debajo del capó, Perl funciona eficazmente como referencia de referencia. Las variables como argumentos de llamada de función se pasan referencialmente, las constantes se pasan como valores de solo lectura y los resultados de las expresiones se pasan como temporales. Los modismos habituales para construir listas de argumentos por asignación de @_
desde @_
, o por shift
tienden a ocultar esto al usuario, dando la apariencia de pasar por valor:
sub incr {
my ( $x ) = @_;
$x++;
}
my $value = 1;
incr($value);
say "Value is now $value";
Esto imprimirá El Value is now 1
porque $x++
ha incrementado la variable léxica declarada dentro de la función incr()
, en lugar de la variable que se pasa. Este estilo de paso por valor suele ser lo que se desea la mayor parte del tiempo, como funciones que modifican sus argumentos son raros en Perl, y se debe evitar el estilo.
Sin embargo, si por algún motivo este comportamiento se desea específicamente, se puede lograr operando directamente en elementos de la matriz @_
, porque serán alias para las variables que se pasan a la función.
sub incr {
$_[0]++;
}
my $value = 1;
incr($value);
say "Value is now $value";
Esta vez se imprimirá El Value is now 2
, porque la expresión $_[0]++
incrementó la variable real de $value
. La forma en que esto funciona es que debajo del capó @_
no es una matriz real como la mayoría de las otras matrices (como la que obtendría my @array
), sino que sus elementos se crean directamente a partir de los argumentos pasados a una llamada de función. Esto le permite construir una semántica de paso por referencia si fuera necesario. Los argumentos de llamada de función que son variables simples se insertan tal cual en esta matriz, y las constantes o resultados de expresiones más complejas se insertan como temporales de solo lectura.
Sin embargo, es extremadamente raro hacer esto en la práctica, porque Perl admite valores de referencia; es decir, valores que se refieren a otras variables. Normalmente, es mucho más claro construir una función que tenga un efecto secundario obvio en una variable, al pasar una referencia a esa variable. Esta es una indicación clara para el lector en el sitio de llamadas, que la semántica de paso por referencia está vigente.
sub incr_ref {
my ( $ref ) = @_;
$$ref++;
}
my $value = 1;
incr(/$value);
say "Value is now $value";
Aquí el operador /
produce una referencia de la misma manera que el operador &
address-of en C.
Con respecto a J , aunque solo hay AFAIK, pasando por valor, hay una forma de pasar por referencia que permite mover muchos datos. Simplemente pasa algo conocido como configuración regional a un verbo (o función). Puede ser una instancia de una clase o simplemente un contenedor genérico.
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
La desventaja obvia es que necesita saber el nombre de su variable de alguna manera en la función llamada. Pero esta técnica puede mover muchos datos sin dolor. Es por eso que, aunque técnicamente no pasa por referencia, lo llamo "más o menos eso".
De manera predeterminada, ANSI / ISO C usa cualquiera de ellos: depende de cómo declare su función y sus parámetros.
Si declara los parámetros de su función como punteros, la función será pasada por referencia, y si declara los parámetros de su función como variables sin puntero, la función será pasada por valor.
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.)
Puede tener problemas si crea una función que devuelve un puntero a una variable no estática que se creó dentro de esa función. El valor devuelto del siguiente código no estaría definido: no hay forma de saber si el espacio de memoria asignado a la variable temporal creada en la función se sobrescribió o no.
float *FtoC(float temp)
{
float c;
c = (temp-32)*9/5;
return &c;
}
Sin embargo, podría devolver una referencia a una variable estática o un puntero que se pasó en la lista de parámetros.
float *FtoC(float *temp)
{
*temp = (*temp-32)*9/5;
return temp;
}
Lo que diga como paso por valor o paso por referencia debe ser coherente en todos los idiomas. La definición más común y consistente que se usa en todos los idiomas es que con el paso por referencia, puede pasar una variable a una función "normalmente" (es decir, sin tomar explícitamente la dirección ni nada de eso), y la función puede asignar (no mutar) el contenido de) el parámetro dentro de la función y tendrá el mismo efecto que asignar a la variable en el alcance de la llamada.
Desde esta vista, los idiomas se agrupan de la siguiente manera; cada grupo tiene la misma semántica pasajera. Si crees que dos idiomas no se deben poner en el mismo grupo, te reto a que encuentres un ejemplo que los distinga.
La gran mayoría de los lenguajes, incluidos C , Java , Python , Ruby , JavaScript , Scheme , OCaml , Standard ML , Go , Objective-C , Smalltalk , etc., son solo valores de paso . Pasar un valor de puntero (algunos idiomas lo llaman "referencia") no cuenta como pasar por referencia; solo nos preocupa lo que pasó, el puntero, no lo que señaló.
Los lenguajes como C ++ , C # , PHP son, por defecto, de paso por valor como los lenguajes anteriores, pero las funciones pueden declarar explícitamente los parámetros como paso por referencia, usando &
o ref
.
Perl siempre es paso por referencia; sin embargo, en la práctica, las personas casi siempre copian los valores después de obtenerlos, por lo tanto, los usan de forma pasiva.
No olvide que también hay pasar por nombre y pasar por valor-resultado .
Pasar por valor-resultado es similar a pasar por valor, con el aspecto agregado de que el valor se establece en la variable original que se pasó como parámetro. Puede, hasta cierto punto, evitar interferencias con variables globales. Aparentemente es mejor en la memoria particionada, donde un pase por referencia podría causar un error de página ( Reference ).
Pasar por nombre significa que los valores solo se calculan cuando se usan realmente, en lugar de al comienzo del procedimiento. Algol usó pass-by-name, pero un efecto secundario interesante es que es muy difícil escribir un procedimiento de intercambio ( Reference ). Además, la expresión pasada por nombre se vuelve a evaluar cada vez que se accede a ella, lo que también puede tener efectos secundarios.
PHP también se pasa por valor.
<?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";
Salidas:
a b
Sin embargo, en PHP4 los objetos fueron tratados como primitives . Lo que significa:
<?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"
Python usa paso por valor, pero dado que todos esos valores son referencias de objeto, el efecto neto es algo similar a pasar por referencia. Sin embargo, los programadores de Python piensan más sobre si un tipo de objeto es mutable o inmutable . Los objetos mutables pueden cambiarse in situ (p. Ej., Diccionarios, listas, objetos definidos por el usuario), mientras que los objetos inmutables no (p. Ej., Enteros, cadenas, tuplas).
El siguiente ejemplo muestra una función a la que se le pasan dos argumentos, una cadena inmutable y una lista 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 línea a = "Red"
simplemente crea un nombre local, a
, para el valor de cadena "Red"
y no tiene ningún efecto en el argumento pasado (que ahora está oculto, ya que debe referirse al nombre local a partir de ese momento) . La asignación no es una operación in situ, independientemente de si el argumento es mutable o inmutable.
El parámetro b
es una referencia a un objeto de lista mutable, y el método .append()
realiza una extensión in situ de la lista, añadiendo el nuevo valor de cadena "Blue"
.
(Debido a que los objetos de cadena son inmutables, no tienen ningún método que admita modificaciones en el lugar).
Una vez que la función regresa, la reasignación de a
ha tenido efecto, mientras que la extensión de b
muestra claramente la semántica de llamadas de estilo de paso por referencia.
Como se mencionó anteriormente, incluso si el argumento para a
es un tipo mutable, la reasignación dentro de la función no es una operación en el lugar, por lo que no habría cambio en el valor del argumento pasado:
>>> a = ["Purple", "Violet"]
>>> do_something(a, b)
>>> print a, b
[''Purple'', ''Violet''] [''Black'', ''Burgundy'', ''Blue'', ''Blue'']
Si no desea que su lista sea modificada por la función llamada, en su lugar usaría el tipo de tupla inmutable (identificado por los paréntesis en forma literal, en lugar de corchetes), que no admite el in situ .append()
método:
>>> 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''
por valor
- es más lento que por referencia ya que el sistema tiene que copiar el parámetro
- usado solo para entrada
por referencia
- más rápido ya que solo se pasa un puntero
- utilizado para entrada y salida
- puede ser muy peligroso si se usa junto con variables globales