perl - tiene - paso de parametros por valor
¿Son las subrutinas de Perl llamadas por referencia o llamadas por valor? (5)
Estoy tratando de descubrir las subrutinas Perl y cómo funcionan. Desde perlsub entiendo que las subrutinas son llamadas por referencia y que se necesita una asignación (como my(@copy) = @_;
) para convertirlas en call-by-value.
En lo que sigue, veo que el change
se llama por referencia porque "a" y "b" se cambian a "x" e "y". Pero estoy confundido acerca de por qué la matriz no se extiende con un elemento extra "z".
use strict;
use Data::Dumper;
my @a = ( "a" ,"b" );
change(@a);
print Dumper(/@a);
sub change
{
@_[0] = "x";
@_[1] = "y";
@_[2] = "z";
}
Salida:
$VAR1 = [
''x'',
''y''
];
A continuación, paso un hash en lugar de una matriz. ¿Por qué no se cambia la clave de "a" a "x"?
use strict;
use Data::Dumper;
my %a = ( "a" => "b" );
change(%a);
print Dumper(/%a);
sub change
{
@_[0] = "x";
@_[1] = "y";
}
Salida:
$VAR1 = {
''a'' => ''y''
};
Sé que la verdadera solución es pasar la matriz o hash por referencia con /@
, pero me gustaría entender el comportamiento de estos programas exactamente.
(Tenga en cuenta que el use warnings
es incluso más importante que el use strict
).
@_
no es una referencia a nada, es una matriz (realmente, solo una vista de la pila, aunque si haces algo así como tomar una referencia a ella, se transforma en una matriz real) cuyos elementos son cada uno un alias a un parámetro pasado. Y esos parámetros pasados son los escalares individuales pasados; no hay concepto de pasar una matriz o hash (aunque puede pasar una referencia a uno).
Por lo tanto, los cambios, empalmes, elementos adicionales añadidos, etc. a @_
no afectan a nada pasado, aunque pueden cambiar el índice de o quitar de la matriz uno de los alias originales.
Entonces, donde llamas change(@a)
, esto pone dos alias en la pila, uno a $a[0]
y uno a $a[1]
. change(%a)
es más complicado; %a
aplana en una lista alterna de claves y valores, donde los valores son los valores hash reales y al modificarlos se modifica lo que está almacenado en el hash, pero donde las claves son meras copias, ya no están asociadas con el hash.
En primer lugar, está confundiendo el @ sigil como indicando una matriz. Esta es en realidad una lista. Cuando llamas a Change (@a) estás pasando la lista a la función, no a un objeto de matriz.
El caso con el hash es ligeramente diferente. Perl evalúa su llamada en una lista y pasa los valores como una lista en su lugar.
Las subrutinas de Perl aceptan parámetros como listas planas de escalares. Una matriz pasada como parámetro es para todos los propósitos prácticos una lista plana también. Incluso un hash se trata como una lista plana de una tecla seguida de un valor, seguida de una tecla, etc.
Una lista plana no se pasa como referencia a menos que lo haga explícitamente. El hecho de que modificar $_[0]
modifica $a[0]
es porque los elementos de @_
convierten en alias para los elementos pasados como parámetros. Modificar $_[0]
es lo mismo que modificar $a[0]
en su ejemplo. Pero si bien esto es aproximadamente similar a la noción común de "pasar por referencia" tal como se aplica a cualquier lenguaje de programación, esto no está pasando específicamente una referencia de Perl; Las referencias de Perl son diferentes (y de hecho "referencia" es un término sobrecargado). Un alias (en Perl) es un sinónimo de algo, donde como referencia es similar a un puntero a algo.
Como dice perlsyn, si asigna a @_
como un todo, rompe su estado de alias. También tenga en cuenta que si intenta modificar $_[0]
, y $_[0]
pasa a ser un literal en lugar de una variable, obtendrá un error. Por otro lado, modificar $_[0]
modifica el valor de la persona que llama si es modificable. Entonces en el ejemplo uno, cambiar $_[0]
y $_[1]
propaga a @a
porque cada elemento de @_
es un alias para cada elemento en @a
.
Tu segundo ejemplo es un poco complicado. Las teclas hash son inmutables. Perl no proporciona una forma de modificar una clave hash, además de eliminarla. Eso significa que $_[0]
no es modificable. Cuando intenta modificar $_[0]
Perl no puede cumplir con esa solicitud. Probablemente debería lanzar una advertencia, pero no es así. Verá, la lista plana que se le pasa consiste en una clave no modificable seguida de un valor modificable, etc. Esto no es un problema. No puedo pensar en ninguna razón para modificar elementos individuales de un hash en la forma en que estás demostrando; dado que los hash no tienen un orden en particular, no tendrías un control simple sobre qué elementos en @_
propagan a los valores en %a
.
Como señaló, el protocolo adecuado es pasar /@a
o /%a
, de modo que se les pueda llamar $_[0]->{element}
o $_[0]->[0]
. A pesar de que la notación es un poco más complicada, se convierte en una segunda naturaleza después de un tiempo, y es mucho más clara (en mi opinión) en cuanto a lo que está sucediendo.
Asegúrese de echarle un vistazo a perlsub . En particular:
Cualquier argumento pasado en aparece en la matriz
@_
. Por lo tanto, si llamó a una función con dos argumentos, estos se almacenarían en$_[0]
y$_[1]
. La matriz@_
es una matriz local, pero sus elementos son alias para los parámetros escalares reales. En particular, si se actualiza un elemento$_[0]
se actualiza el argumento correspondiente (o se produce un error si no se puede actualizar). Si un argumento es un elemento de matriz o hash que no existía cuando se llamó a la función, ese elemento se crea solo cuando (y si) se modifica o se toma una referencia a él. (Algunas versiones anteriores de Perl creaban el elemento independientemente de si el elemento estaba asignado o no). La asignación a toda la matriz@_
elimina ese alias y no actualiza ningún argumento.
Perl no pasa la matriz o hash por referencia, despliega las entradas (los elementos de la matriz, o las claves y valores de hash) en una lista y pasa esta lista a la función. @_ le permite acceder a los escalares como referencias.
Esto es más o menos lo mismo que escribir:
@a = (1, 2, 3);
$b = /$a[2];
${$b} = 4;
@a now [1, 2, 4];
Notarás que en el primer caso no pudiste agregar un elemento adicional a @a, todo lo que sucedió fue que modificaste los miembros de @a que ya existían. En el segundo caso, las claves hash realmente no existen en el hash como escalares, por lo que deben crearse como copias en escalares temporales cuando la lista expandida del hash se crea para pasar a la función. La modificación de este escalar temporal no modificará la clave hash, ya que no es la tecla hash.
Si desea modificar una matriz o hash en una función, deberá pasar una referencia al contenedor:
change(/%foo);
sub change {
$_[0]->{a} = 1;
}
Perl siempre pasa por referencia. Es solo que a veces la persona que llama pasa escalares temporales.
Lo primero que debes tener en cuenta es que los argumentos de los subs pueden ser una sola y única: una lista de escalares. * No se pueden pasar matrices o hashes a ellos. Las matrices y los hash se evalúan y devuelven una lista de su contenido. Eso significa que
f(@a)
es lo mismo que
f($a[0], $a[1], $a[2])
Perl pasa por referencia. Específicamente, Perl alía cada uno de los argumentos a los elementos de @_
. La modificación de los elementos @_
cambiará los escalares devueltos por $a[0]
, etc. y por lo tanto modificará los elementos de @a
.
Lo segundo importante es que la clave de una matriz o elemento hash determina dónde se almacena el elemento en la estructura. De lo contrario, $a[4]
y $h{k}
requerirían examinar cada elemento de la matriz o hash para encontrar el valor deseado. Esto significa que las claves no son modificables. Mover un valor requiere crear un nuevo elemento con la nueva clave y eliminar el elemento en la clave anterior.
Como tal, cada vez que obtenga las claves de una matriz o hash, obtendrá una copia de las claves. Escalares frescos, por así decirlo.
De vuelta a la pregunta,
f(%h)
es lo mismo que
f(
my $k1 = "a", $h{a},
my $k2 = "b", $h{b},
my $k2 = "c", $h{c},
)
@_
todavía tiene un alias para los valores devueltos por %h
, pero algunos de ellos son solo escalares temporales utilizados para mantener una tecla. Cambiarlos no tendrá un efecto duradero.
* - Algunos integradores (por ejemplo, grep
) son más como declaraciones de control de flujo (por ejemplo, while
). Tienen sus propias reglas de análisis y, por lo tanto, no están limitadas al modelo convencional de un submarino.
** - Los prototipos pueden afectar la forma en que se evalúa la lista de argumentos, pero de todos modos generará una lista de escalares.