Miércoles 2 de Junio

Variables y parámetros

Objetivos: Explicar conceptos como el tiempo de vida y alcance de una variable. Temas:


Término anticipado de una función

La línea return que aparece al final de las funciones es una instrucción como cualquier otra y por lo tanto puede aparecer en cualquier parte del código de la función:

    return exp;
Cuando se ejecuta la instrucción return, se termina con la ejecución de la función en donde aparece y la función entrega el resultado de evaluar la expresión que acompaña return. Por ejemplo, la siguiente función calcula si un número es primo:

    boolean esPrimo(int n) {
      int i= 2;
      while (i<n) {
        if (n%i==0)
          return false;
        i= i+1;
      }
      return true;
    }
La instrucción return en este caso es más ``fuerte'' que un break, puesto que no solo termina el ciclo, también termina la función completa.


Semántica de variables y parámetros

Cuando se declara una variable como la siguiente:

    int n= 123;
podemos distinguir 3 conceptos: la variable, el identificador y el valor. Estos conceptos se visualizan mejor en la siguiente figura.

A continuación presentaremos algunas nociones importantes para comprender el concepto de recursividad.

Tiempo de vida de una variable:

El tiempo de vida se refiere al intervalo de tiempo que trascurre desde que se crea la variable hasta que se destruye. En Java, una variable se crea en el momento que se ejecuta su declaración y se destruye cuando finaliza el bloque de instrucciones en donde fue declarada. Por ejemplo:

    if ( ... ) {
      println( ... );
      int n= 123; // (A)
      ...
      n= ...; // (B)
      ...
    } // (C)
La variable n se crea en el momento de ejecutar la instrucción (A) y se destruye al encontrar el final del bloque de instrucciones en (C). En (B) se asigna un nuevo valor a la variable n, que ya existía.

Un mismo identificador se puede usar para nombrar varias variables distintas. Por ejemplo en el siguiente código:

    {
      int x= a+b;
      ... x ...
    }
    {
      int x= fun(a,b);
      ... x ...
    }
En el código anterior, la variable x del primer bloque no tiene ninguna relación con la variable x del segundo bloque. Se trata de dos variables que se identifican por el mismo nombre. Se podría renombrar la variable x del segundo bloque por y, sin cambiar en nada la ejecución del programa.

Incluso, la declaración de una variable puede aparecer una sola vez en el programa, pero aún así servir para identificar varias variables durante la ejecución del programa. Esto se aprecia en el siguiente ciclo:

    while ( ... ) {
      ...
      double x= 3.14; // (A)
      ...
      x= ...;
      ...
    } // (C)
En cada iteración, en (A) se crea una nueva variable x que se destruye en (C), pero todas se llaman con el mismo nombre (x).

Los parámetros de una función también son variables. Se crean en el momento que la función es invocada y se destruyen al finalizar la ejecución de la función. Por ejemplo, la siguiente función calcula el máximo común divisor:

    int mcd(int x, int y) {
      while (x!=y) {
        if (x>y)
          x= x-y;
        else
          y= y-x;
      }
      return x;
    }
Considere las siguientes invocaciones de esta función (por ejemplo en el procedimiento run):

    int a= 15;
    println( mcd(a, 21) );
    println( mcd(25, a) );
En cada invocación de mcd se crean nuevas variables x e y (sus parámetros) que se inicializan con los valores de los argumentos. Esto se visualiza mejor en la siguiente figura:

Observe en la figura que durante la ejecución del programa se crean dos variables con nombre x. Esto no constituye ninguna confusión en el momento de accesar la variable x, puesto que ambas variables tienen tiempo de vista disjuntos.

Observe también, que la función mcd modifica los parámetros x e y sin modificar el valor de la variable a en ninguna de las invocaciones. Una función no puede producir efectos laterales sobre las variables que se especifiquen como argumentos durante la invocación. Sin embargo, una función sí puede producir efectos laterales sobre los objetos que se pasen como argumentos en la invocación (como por ejemplo un arreglo).

Existen casos que sí existen varias variables con el mismo nombre en un mismo instante. Por ejemplo, consideremos el siguiente programa:

    int x= 105;
    println( mcd(210, x) );
Mientras se ejecuta la función mcd existen dos variables x. ¿Qué variable se modifica al ejecutar x= x-y;? Intuitivamente la respuesta es ``la variable de mcd''. Esto es cierto, pero para aclarar mejor este concepto definiremos el alcance de una variable.

Alcance de una variable:

El alcance de una variable es el conjunto de instrucciones en la que esa variable es visible por medio de su identificador. En Java, el alcance de una variables está delimitado por la primera instrucción que sigue a su declaración en el programa, hasta el final del bloque en donde fue declarada. Por ejemplo en:

    void run() {
      int x= 105; // (A)
      println( mcd(210, x) );
    } // (B)
La variable x es conocida desde (A) hasta (B). En particular esa variable no es conocida en la definición de mcd, ni en la definición de ningún otro procedimiento porque esas definiciones no se encuentran entre (A) y (B).

Es importante entender la diferencia entre alcance y tiempo de vida de una variable. En el ejemplo anterior, mientras se ejecuta la función mcd, la variable x del procedimiento run continúa existiendo, a pesar de que no es visible desde mcd. Si una variable no es visible, no necesariamente ha sido destruida. En cambio, si una variable fue destruida, esa variable no es visible.


Técnicas de depuración de programas

Cuando se escriben programas, es normal cometer errores (bugs). De hecho, en promedio, un programador comete un error cada 10 líneas de programa. Esto significa que la probabilidad de que el programa funcione a la primera vez es prácticamente nula.

Por lo tanto, el desarrollo de un programa siempre incorpora una etapa de depuración (debugging), que consiste en buscar y resolver los errores cometidos durante la programación. Para facilitar la etapa de depuración es conveniente usar herramientas especializadas para estos efectos. La más común es el depurador o también llamado debugger.

El depurador:

Un depurador es una herramienta que permite intervenir durante la ejecución de un programa, para saber cómo se está ejecutando. El depurador permite:

En este curso usaremos el depurador que trae incorporado JBuilder.

Depuración manual:

Cuando no se dispone de un depurador, se debe recurrir a la depuración manual. Esta consiste en preparar el programa para poder conocer como se está ejecutando el programa. La técnica más usual de depuración de programas consiste en colocar println's en puntos estratégicos del programa para desplegar el contenido de las variables. Siga las siguientes recomendaciones para establecer estos puntos estratégicos:

Para que resulte más fácil la depuración, comience ejecutando su programa con poquísimos datos. Nunca olvide, incluir datos que representen las condiciones de borde del programa. Por ejemplo: n=0, el archivo esta vacío, etc.

La biblioteca del curso incluye mecanismos para poder grabar todos los mensajes que aparecen en la pantalla. Para hacer esto, utilice la opción -l al ejecutar su programa:

    java Run -l MiPrograma
Su programa se ejecutará igual que antes, pero además creará un archivo de nombre console.log que contiene todos los mensajes que aparecieron en pantalla durante la ejecución del programa. Esto resulta muy útil, pues las 24 líneas de la pantalla se hacen escasas con tanto despliegue con println.

Cuando Ud. estime que su programa funciona adecuadamente, comente los println que introdujo para efectos de depuración (es decir agregue // para transformalos en comentarios). No los borre, porque más tarde podría darse cuenta que su programa todavía no funciona completamente como debería.