Programación en Pascal/Funciones y procedimientos


Programar como lo hemos estado haciendo hasta ahora, es decir, una instrucción tras otra, y cuando llega el end. se acabó, está bien. Pero en la vida real, es normal escribir miles y miles de instrucciones. Poner unas detrás de otras sin más generará pronto un caos difícil de manejar. Además, es normal que unas cuantas rutinas tengan que repetirse, por lo que copiar y pegar el código no solo es un gran engorro, sino que produciría problemas de mantenimiento (si hay que hacer alguna modificación, se debe hacer en todas las copias).

Por ello se inventaron las funciones y procedimientos, con el objetivo de dividir un programa en bloques mas pequeños (programación estructurada).

Hasta ahora, ya has usado algunas, como por ejemplo write o readln.

Veamos como puedes construir las tuyas propias.

Procedimientos sencillos

editar

La programación estructurada trata de dividir el programa en bloques más pequeños, buscando una mayor legibilidad, y más comodidad a la hora de corregir o ampliar.

Por ejemplo, en el caso de nuestra maravillosa agenda, podemos empezar a teclear directamente y crear un programa de 2000 líneas que quizás incluso funcione, o dividirlo en partes, de modo que el cuerpo del programa sea

begin
  InicializaVariables;
  PantallaPresentacion;
  Repeat
  PideOpcion;
  case Opcion of
      '1': MasDatos;
      '2': CorregirActual;
      '3': Imprimir;
    ...
    end;
  Until Opcion = OpcionDeSalida;
  GuardaCambios;
  LiberaMemoria
end.

Bastante más fácil de seguir, ¿verdad?

En Pascal estos bloques serán de dos tipos: procedimientos (procedure) y funciones (function).

La diferencia entre ellos es que un procedimiento ejecuta una serie de acciones que están relacionadas entre sí, puede devolver ningún valor o múltiples valores, y escribir en pantalla y leer datos, mientras que la función devuelve como mínimo 1 valor, y no puede ni escribir en pantalla ni leer datos. Veamoslo con un par de ejemplos:

procedure Acceso;
var
  clave: string;                         (* Esta variable es local *)
begin
  writeln(' Bienvenido a SuperAgenda ');
  writeln('=========================='); (* Para subrayar *)
  writeln; writeln;                      (* Dos líneas en blanco *)
  writeln('Introduzca su clave de acceso');
  readln( clave );                      (* Lee un valor *)
  if clave <> ClaveCorrecta then        (* Compara con el correcto *)
    begin                               (* Si no lo es *)

    writeln('La clave no es correcta!'); (* avisa y *)
    exit                                 (* abandona el programa *)
    end
end;

Primeros comentarios sobre este ejemplo:

  • El cuerpo de un procedimiento se encierra entre "begin" y "end", igual que las sentencias compuestas y que el propio cuerpo del programa.
  • Un procedimiento puede tener sus propias variables, que llamaremos variables locales, frente a las del resto del programa, que son globales. Desde dentro de un procedimiento podemos acceder a las variables globales (como ClaveCorrecta del ejemplo anterior), pero desde fuera de un procedimiento no podemos acceder a las variables locales que hemos definido dentro de él.
  • La orden exit, que no habíamos visto aún, permite interrumpir la ejecución del programa (o de un procedimiento) en un determinado momento.

Procedimientos con argumentos

editar

Un procedimiento también puede tener "parámetros", igual que la función que acabamos de ver:

 {--------------------------}
 {  Ejemplo en Pascal:      }
 {                          }
 {    Ejemplo de procedi-   }
 {    miento al que se le   }
 {    pasan parámetros      }
 {    PROCPAR.PAS           }
 {                          }
 {  Este fuente procede de  }
 {  CUPAS, curso de Pascal  }
 {  por Nacho Cabanes       }
 {                          }
 {  Comprobado con:         }
 {    - Turbo Pascal 7.0    }
 {    - Turbo Pascal 5.0    }
 {--------------------------}

 program ProcConParametros;
 { Para usarlo con SURPAS 1.00 habría    }
 { definir un tipo "str20", por ejemplo, }
 { que usar en vez de "string".          }

 procedure saludo (nombre: string);     (* Nuestro procedimiento *)
 begin
   writeln('Hola ', nombre, ' ¿qué tal estás?');
 end;

 begin                  (* Comienzo del programa *)
   writeln;             (* Linea en blanco *)
   saludo( 'Juan' );    (* Saludamos a Juan *)
 end.                   (* Y se acabó *)

En el próximo apartado veremos la diferencia entre pasar parámetros por valor (lo que hemos estado haciendo) y por referencia (para poder modificarlos), y jugaremos un poco con la recursividad.


Procedimientos con parámetros (2)

editar

Ya habíamos visto qué era eso de los procedimientos y las funciones, pero habíamos dejado aparcados dos temas importantes: los tipos de parámetros y la recursividad.

Vamos con el primero. Ya habíamos visto, sin entrar en detalles, qué es eso de 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, sin saberlo, desde el primer día, 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 hacemos cosas como ésta:

 {--------------------------}
 {  Ejemplo en Pascal:      }
 {                          }
 {    Primer procedimiento  }
 {    que modif. variables  }
 {    PROCMOD1.PAS          }
 {                          }
 {  Este fuente procede de  }
 {  CUPAS, curso de Pascal  }
 {  por Nacho Cabanes       }
 {                          }
 {  Comprobado con:         }
 {    - Turbo Pascal 7.0    }
 {    - Turbo Pascal 5.0    }
 {    - Surpas 1.00         }
 {--------------------------}

 program PruebaDeParametros;

 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.  No hay problema.
   * 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?


¡¡¡¡NO!!!! 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 modificamos es la variable genérica que hemos llamado "variable", y que no existe fuera del procedimiento.

Eso es pasar un parámetro por valor. Podemos leer su valor, pero no podemos alterarlo.

Pero, ¿cómo lo hacemos si realmente queremos modificar el parámetro. Pues nada más que añadir la palabra "var" delante de cada parámetro que queremos permitir que se pueda modificar...


Procedimientos con parámetros (3)

editar

El programa quedaría:

 {--------------------------}
 {  Ejemplo en Pascal:      }
 {                          }
 {    Segundo proc. que     }
 {    modifica variables    }
 {    PROCMOD2.PAS          }
 {                          }
 {  Este fuente procede de  }
 {  CUPAS, curso de Pascal  }
 {  por Nacho Cabanes       }
 {                          }
 {  Comprobado con:         }
 {    - Turbo Pascal 7.0    }
 {    - Turbo Pascal 5.0    }
 {    - Surpas 1.00         }
 {--------------------------}

 program PruebaDeParametros2;

 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"). Esto es pasar un parámetro por referencia.

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 una función debe devolver más de un valor. Habíamos visto que una función era como un procedimiento, pero además devolvía un valor (pero sólo uno). Si queremos obtener más de un valor de salida, una de las formas de hacerlo es pasándolos como parámetros, precedidos por la palabra "var".


Procedimientos con parámetros (4)

editar

Y como ejercicio queda un caso un poco más "enrevesado". Qué ocurre si el primer programa lo modificamos para que sea así:

 {--------------------------}
 {  Ejemplo en Pascal:      }
 {                          }
 {    Tercer proc. que      }
 {    modifica variables    }
 {    PROCMOD3.PAS          }
 {                          }
 {  Este fuente procede de  }
 {  CUPAS, curso de Pascal  }
 {  por Nacho Cabanes       }
 {                          }
 {  Comprobado con:         }
 {    - Turbo Pascal 7.0    }
 {    - Turbo Pascal 5.0    }
 {    - Surpas 1.00         }
 {--------------------------}

 program PruebaDeParametros3;

 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.

Argumentos variables

editar

Hasta ahora siempre hemos utilizado esto para leer números:

write('Escribe el radio: '); readln(radio);

Argumentos constantes

editar

Funciones

editar

Una funcion es un modulo de un programa separado del cuerpo principal, que realiza una tarea especifica y que puede regresar un valor a la parte principal del programa u otra funcion o procedimiento que la invoque. La forma general de una funcion es:

Function NomFuncion(parametros): 

tipodatoregresa;

Begin

Instrucciones;

End;

Donde tipodato especifica el tipo de dato que regresara la funcion.

Y el NomFuncion tiene dos papeles en pascal:

A) Es el nombre que se invocara dentro del principal o de algun procedimiento u otra funcion .

B) Es tambien una variable que debera cargarse dentro del cuerpo de instrucciones (begin ..end) para que pueda regresar el dato o resultado al principal o procedimiento o funcion que la este invocando.

La lista de parametros formales es una lista de variables separadas por punto y coma ( ;) que almacenaran los valores que reciba la funcion, estas variables actuan como locales dentro del cuerpo de la funcion.

Recordar ademas que cuando se llame una funcion debera haber una variable que reciba el valor que regresara la funcion, es decir generalmente se llama una funcion mediante una sentencia de asignacion, por ejemplo resultado=funcion(5, 3.1416);

Prog12.pas
program prog11;
uses crt;
var
dolar:real;
FUNCTION dolares(pesos,tc:real):real;
begin
dolares := pesos /tc;
end;
begin
clrscr;
(* llamando funcion y cargando resultado *)
dolar := dolares(123.45 , 11.25);
write('SON ',dolar:0:2, ' DOLARES');
readln;
end.

Corrida:

EXISTEN 3 CLASES USUALES DE FUNCIONES. Las primeras son de tipo computacional que son disenadas para realizar operaciones con los argumentos y regresan un valor basado en el resultado de esa operacion. Las segundas funciones son aquellas que manipulan informacion y regresan un valor que indican la terminacion o la falla de esa manipulacion. Las terceras son aquellas que no regresan ningun valor, es decir son estrictamenta procedurales. Esto quiere decir que en general toda operacion o calculo en un programa debera convertirse a una o muchas funciones y el resto deberan ser procedimientos.

Recursividad

editar

Recursividad. Vamos al segundo apartado de hoy: qué es eso de la "recursividad". Pues la idea en sí es muy sencilla: un procedimiento o función es recursivo si se llama a sí mismo.

¿Y qué utilidad puede tener eso? Habrá muchos problemas que son más fáciles de resolver si se descomponen en pasos cada vez más sencillos.

Vamos a verlo con un ejemplo clásico: el factorial de un número.

Partimos de la definición de factorial de un número n:

n! = n · (n-1) · (n-2) · ... · 3 · 2 · 1

Por otra parte, el factorial del siguiente número más pequeño (n-1) es

(n-1)! = (n-1) · (n-2) · ... · 3 · 2 · 1

Se parecen mucho. Luego podemos escribir cada factorial en función del factorial del siguiente número:

n! = n · (n-1)!

¡Acabamos de dar la definición recursiva del factorial! Así vamos "delegando" para que el problema lo resuelva el siguiente número, y este a su vez se lo pasa al siguiente, y este al otro, y así sucesivamente hasta llegar a algún caso que sea muy fácil. Pues ahora sólo queda ver cómo se haría eso programando:

{--------------------------}
{  Ejemplo en Pascal:      }
{                          }
{    Factorial, ejemplo de }
{    recursividad          }
{    FACT.PAS              }
{                          }
{  Este fuente procede de  }
{  CUPAS, curso de Pascal  }
{  por Nacho Cabanes       }
{                          }
{  Comprobado con:         }
{    - Turbo Pascal 7.0    }
{    - Turbo Pascal 5.0    }
{    - Surpas 1.00         }
{--------------------------}
program PruebaDeFactorial;
var numero: integer;
function factorial( num : integer) : integer;
begin
  if num = 0 then
    factorial := 1        (* Aseguramos que tenga salida siempre *)
  else
    factorial := num * factorial( num-1 );       (* Caso general *)
end;
begin
  writeln( 'Introduce un número entero (no muy grande)  guiño' );
  readln(numero);
  writeln( 'Su factorial es ', factorial(numero) );
end. 


Un par de comentarios sobre este programilla:

  • Atención a la primera parte de la función recursiva: es muy importante comprobar que hay salida de la función, para que no se quede dando vueltas todo el tiempo y nos cuelgue el ordenador. Normalmente, la condición de salida será que ya hallamos llegado al caso fácil (en este ejemplo: el factorial de 1 es 1).
  • No pongáis números demasiado grandes. Recordad que los enteros van desde -32768 hasta 32767, luego si el resultado es mayor que este número, tendremos un desbordamiento y el resultado será erróneo. ¿Qué es "demasiado grande"? Pues el factorial de 8 es cerca de 40.000, luego sólo podremos usar números del 1 al 7.

Si este límite del tamaño de los enteros os parece preocupante, no le deis muchas vueltas, porque en la próxima lección veremos que hay otros tipos de datos que almacenan números más grandes o que nos permiten realizar ciertas cosas con más comodidad.