24 septiembre 2014

Pascal - 6d: Parámetros por referencia

6.4. Modificación de parámetros

Ya habíamos visto qué son los parámetros: una serie de datos extra, que indicábamos entre paréntesis en la cabecera de un procedimiento o función.
Es algo que estamos usando, casi sin darnos cuenta, desde el primer apartado del curso, cuando empezamos a usar "WriteLn":
writeLn( 'Hola' );
Esta línea es una llamada al procedimiento "WriteLn", y como parámetros le estamos indicando lo que queremos que escriba, en este caso, el texto "Hola".
Pero vamos a ver qué ocurre si modificamos un parámetro:
(* PROCMOD1.PAS, Procedimiento que modifica parametros *)
(* Primer intento: paso por valor                      *)
(* Parte de CUPAS5, por Nacho Cabanes                  *)
 
program ModificarParametros1;
 
var dato: integer;
 
procedure modifica( variable : integer);
begin
    variable := 3 ;
    writeLn( 'Dentro: ', variable );
end;
 
begin
    dato := 2;
    writeLn( 'Antes: ', dato );
    modifica( dato );
    writeLn( 'Después: ', dato );
end. 
 
¿Qué podemos esperar que pase? Vamos a ir siguiendo cada instrucción:
  • Declaramos el nombre del programa
  • Usaremos la variable "dato", de tipo entero.
  • El procedimiento "modifica" toma una variable de tipo entero, le asigna el valor 3 y la escribe. Lógicamente, siempre escribirá 3.
  • Empieza el cuerpo del programa: damos el valor 2 a "dato".
  • Escribimos el valor de "dato". Claramente, será 2.
  • Llamamos al procedimiento "modifica", que asigna el valor 3 a "dato" y lo escribe.
  • Finalmente volvemos a escribir el valor de "dato"... ¿3?
Pues no es eso lo que ocurre: escribe un 2. Las modificaciones que hagamos a "dato" dentro del procedimiento "modifica" sólo son válidas mientras estemos dentro de ese procedimiento (lo que realmente ocurre es que el procedimiento está recibiendo una copia de la variable, de modo que si hacemos algún cambio, éstos no se cambios no afectan a la variable original, que existe fuera del procedimiento).
Eso es pasar un parámetro por valor: podemos leer su valor, pero no podemos alterarlo (los cambios se pierden al salir).
La alternativa es "pasar parámetros por referencia, añadiendo la palabra "var" delante de cada parámetro que sea modificable dentro del procedimiento (o función):
(* PROCMOD2.PAS, Procedimiento que modifica parametros *)
(* Segundo intento: paso por referencia                *)
(* Parte de CUPAS5, por Nacho Cabanes                  *)
 
program ModificarParametros2;
 
var dato: integer;
 
procedure modifica( var variable : integer);
begin
    variable := 3 ;
    writeLn( 'Dentro: ', variable );
end;
 
begin
    dato := 2;
    writeLn( 'Antes: ', dato );
    modifica( dato );
    writeLn( 'Después: ', dato );
end.
 
Esta vez la última línea del programa sí que escribe un 3 y no un 2, porque hemos permitido que los cambios hechos a la variable salgan del procedimiento (para eso hemos añadido la palabra "var").
El nombre "referencia" alude a que no se pasa realmente al procedimiento o función el valor de la variable, sino la dirección de memoria en la que se encuentra, algo que más adelante llamaremos un "puntero".
Una de las aplicaciones más habituales de pasar parámetros por referencia es cuando debemos devolver más de un valor: habíamos visto que una función era muy parecida a un procedimiento, pero además devolvía un valor (pero sólo uno); cuando necesitamos obtener más de un valor de salida, la forma habitual de conseguirlo es pasándolos como parámetros por referencia, (precedidos por la palabra "var"). Existe alguna otra alternativa menos elegante, como devolver un array, cosas que no tiene mucho sentido si no se trata de datos relacionados..
Otra aplicación puede ser optimizar (ligeramente) velocidad: si pasamos datos de gran tamaño, será algo más lento pasarlos por valor (hay que duplicar toda su estructura) que pasarlos por referencia (sólo se pasa su dirección), pero este tipo de consideraciones hay que tenerlas más adelante, no en la primera implementación del programa, porque tratar de optimizar velocidad antes de tener la certeza de que todo funciona correctamente puede dar lugar a errores difíciles de detectar.
¿Y si la variable tiene el mismo nombre en la función y en el cuerpo del programa? ¿Conseguiremos "engañar" al compilador y modificar su valor sin necesidad de usar "var"?
(* PROCMOD3.PAS, Procedimiento que modifica parametros *)
(* Tercer intento: paso por valor de variables con el  *)
(*     el mismo nombre                                 *)
(* Parte de CUPAS5, por Nacho Cabanes                  *)
 
program ModificarParametros3;
 
var dato: integer;
 
procedure modifica( dato : integer);
begin
    dato := 3 ;
    writeLn( 'Dentro: ', dato );
end;
 
begin
    dato := 2;
    writeLn( 'Antes: ', dato );
    modifica( dato );
    writeLn( 'Después: ', dato );
end.
 
 
Como se ve en este ejemplo, da igual el nombre que tengan las variables. El comportamiento será el mismo: ambas variables son distintas, aunque el nombre parezca indicar lo contrario.

(Como siempre, tienes más detalles y ejercicios propuestos en la versión oficial del curso).