CAPITULO 3

 

FUNCIONES Y PROCEDIMIENTOS

 

 

Una de las grandes diferencias de la programación estructurada que fomenta el Pascal sobre la programación "libre" al estilo del lenguaje BASIC, es el hecho que los programas pueden escribirse en forma modular, permitiendo así que un problema general se descomponga en una secuencia de subproblemas individuales. La modularización proporciona dos ventajas importantes. Primero, para tareas que deben efectuarse más de una vez, la modularización evita la necesidad de programación redundante (repetida) en esencia del mismo conjunto de instrucciones. En vez de ello, un módulo de programa puede definirse una vez para luego ser invocado desde varios puntos del programa. Cada vez que el módulo sea llamado podrá procesar un conjunto diferente de datos. El uso de módulos puede, por tanto, reducir apreciablemente la longitud de un programa.

 

De mayor importancia aún es la claridad lógica resultante de la descomposición de un programa en módulos concisos e independientes, donde cada módulo representa una parte bien definida del problema total. Tales programas son más faciles de escribir y depurar, y su estructura lógica es más clara que la de los programas que carecen de este tipo de estructura. Esto es particularmente cierto cuando los programas son largos y complicados. Muchos programas Pascal son, por tanto, modularizados incluso cuando puedan no requerir la ejecución repetida de las mismas tareas. De hecho, la Modularidad de un programa se considera parte importante de una buena práctica de programación, junto con otras características deseables tales como la Precisión del cálculo, Claridad, Simplicidad, Generalidad y la Eficiencia en cuanto a velocidad y uso de la memoria (lo que no debe lograrse a expensas de la claridad o la sencillez).

 

En Pascal estos módulos o subprogramas se implementan como procedimientos y funciones. Un procedimiento es una parte autónoma incluída dentro de un programa Pascal y se activa desde cualquier parte del programa principal con solo invocar su nombre (seguido de una lista de parámetros, si los tiene). Cuando se invoca un procedimiento, el control se transfiere al comienzo del mismo. Se efectúan entonces las sentencias ejecutables del procedimiento. Cuando se han ejecutado todas las instrucciones se devuelve automáticamente el control a la sentencia inmediatamente posterior a la llamada del procedimiento.

 

Un subprograma puede ser Pre-declarado (estándar del Pascal) o puede ser definido por el usuario. Los procedimientos y funciones estándar se pueden llamar sin ninguna declaración previa.

 

 

 

PROCEDIMIENTOS

 

La declaración de un procedimiento definido por el usuario se debe hacer en el bloque de declaraciones de procedimientos del módulo desde el cual será invocado, y consta del encabezamiento y los bloques de declaraciones y de instrucciones (cuerpo del subprograma). El encabezamiento está formado por la palabra PROCEDURE seguida del nombre o identificador del procedimiento y, opcionalmente, una lista de parámetros.

 

Veamos un sencillo ejemplo:

 

 

 

      program procedimientos;

      var

         cadena     : string;

         longitud,

         carácter   : integer;

 

      procedure  PreguntarCad;

      begin

           Write('Cadena a convertir a mayúsculas : ');

           Readln(cadena);

      end; { Preguntar }

 

      procedure A_Mayusculas;

      begin

           longitud := length(cadena);

           for carácter := 1 to longitud do

           cadena[carácter] := UpCase(cadena[carácter]);

      end; { A_Mayusculas }

 

      begin { Programa principal }

           PreguntarCad;

           A_Mayusculas;

           Writeln(cadena)

           ReadLn;

      end.

 

 

 

Este programa convierte los caracteres alfabéticos en minúscula dentro de una cadena de caracteres de entrada a sus correspondientes mayúsculas. Para ello utiliza dos procedimientos; el primero pregunta la cadena a transformar y el siguiente procedimiento (A_Mayusculas) realiza la conversión. Aunque cada sentencia del procedimiento podría ubicarse directamente en el bloque principal, el modularizar el programa le da más claridad y elegancia.

 

Las constantes y variables que aparecen dentro de las sentencias ejecutables de un procedimiento pueden haber sido declaradas externamente (dentro de un módulo que contiene la declaración de procedimiento), o localmente (dentro del propio procedimiento). Aquellas constantes y variables declaradas dentro del bloque que contiene la declaración de procedimiento pueden utilizarse en cualquier punto dentro de este bloque, ya sea interior o exterior al procedimiento. Los identificadores definidos de esta manera se consideran globales al procedimiento. Recuerde que un identificador es un nombre dado a un elemento de un programa, tal como una constante, una variable, un procedimiento o un programa. En general, son preferibles los identificadores locales a los globales.

 

Las partes de un subprograma son similares a las de un programa, o sea: encabezamiento, parte de declaraciones y parte de instrucciones. Todas las variables declaradas en un subprograma y definidas dentro de él son variables locales, es decir sólo existen dentro del módulo y durante su ejecución. No son conocidas por el programa principal, ni fuera del procedimiento o función. La parte de instrucciones o cuerpo del subprograma especifica las acciones que se llevarán a cabo cuando el procedimiento sea invocado.

 

 

 

PARAMETROS

 

Como se vió en el ejemplo anterior, sus procedimientos operaban sobre variables comunes a todo el programa; en ocasiones ésto es indeseable, ya que puede hacerse confuso el manejo de los datos y difícil la transferencia de múltiples series de éstos.

 

El uso de parámetros ofrece un método mejor para el intercambio de información entre un proceso y su punto de referencia. Cada dato se transfiere entre un parámetro actual, incluído dentro de la referencia al procedimiento, y un parámetro formal correspondiente, definido dentro del propio procedimiento. Cuando se invoca un procedimiento, los parámetros actuales reemplazan a los parámetros formales, creando así un mecanismo de intercambio de información entre el procedimiento y su punto de refencia. La manera en que se intercambia la información depende, sin embargo, de la manera en que se definan y utilicen los parámetros.

 

En Object Pascal se pueden pasar parámetros por valor  o por referencia. En el primer caso, el parámetro formal representa una variable local al subprograma y los cambios que se hagan en ésta no afectarán el valor actual (o valor en el módulo que invoca el subprograma) del parámetro. En este caso el parámetro actual puede ser una expresión o variable.

 

Cuando los parámetros se pasan por referencia el valor actual del parámetro puede ser modificado por el procedimiento, ya que cualquier cambio hecho al parámetro formal es también hecho al parámetro actual. El parámetro actual debe ser una variable, por lo que también son conocidos como parámetros variables. Al declararlos en la lista de parámetros deben ir precedidos por la palabra reservada VAR.

 

La sintaxis general para el uso de parámetros es:

 

  Procedure Ejemplo(Num1, Num2 : tipo1; VAR Num3, Num4 : tipo2);

 

donde tipo1 y tipo2 son tipos previamente definidos. Num1 y Num2, son parámetros pasados por valor; por lo tanto no quedarán modificados una vez terminada la ejecución del subprograma; mientras que Num3 y Num4 son variables pasadas por referencia, cuyo valor podría ser modificado por el subprograma.

 

Los parámetros de tipo archivo deben ser pasados por referencia. También, se recomienda que las estructuras largas que se pasarán como argumentos, sean pasadas por referencia, para lograr ahorro de memoria y tiempo.

 

 

En resumen:

 

- Un grupo de parámetros, sin la palabra clave Var precediéndolos y seguidos por un tipo, es una lista de parámetros por valor.

- Un  grupo de parámetros precedidos por Var y seguidos por un tipo, es una lista de parámetros por variable.

- Un  grupo de  parámetros precedidos por Var y no seguidos por un tipo, constituyen una lista de parámetros de variable sin tipo.

 

 

Para hacer claridad en el uso de parámetros, considere las siguientes modificaciones al programa visto anteriormente:

 

 

 

      program ejemplo;

      var

         cadena : string;

 

      procedure  Preguntar (mensaje : string; var respuesta : string);

      begin

           Write(mensaje);

           Readln(respuesta);

      end; { Preguntar }

     

      procedure A_Mayusculas (cad : string);

      var

         longitud,

         carácter   : integer;

      begin

           longitud := length(cad);

 

           for carácter := 1 to longitud do

               cad[carácter] := UpCase(cad[carácter]);

 

           Writeln(cad)

      end; { A_Mayusculas }

     

      begin

           Preguntar('Cadena a convertir a mayúsculas : ',cadena);

           A_Mayusculas(cadena);

           Writeln(cadena);

           ReadLn;

      end.

 

 

 

Observe que ahora los procedimientos Preguntar y A_Mayusculas obtienen los datos que manipulan como parámetros; los parámetros formales de Preguntar son mensaje y respuesta, ambas de tipo String, pero declaradas como parámetros por valor y referencia respectivamemte. Por lo tanto el valor de cadena se hará igual al valor que se asigne a respuesta (puesto que se pasó por referencia), mientras que en A_Mayusculas el parámetro cadena es pasado por valor; por lo tanto la conversión a mayúsculas sólo se efectúa sobre la variable local cad, conservándose intacta la variable cadena.

 

En la nueva versión del programa se usan además las variables locales longitud y carácter que son declaradas internamente al procedimiento A_Mayusculas.

 

 

 

FUNCIONES

 

Una función es similar a un procedimiento, pero a diferencia de éste, una función se usa para devolver un solo valor de tipo simple a su punto de llamada. Esta llamada puede hacerse dentro de una expresión como si fuera una variable ordinaria de tipo simple. El nombre de una función puede ir seguido de uno o más parámetros actuales encerrados entre paréntesis y separados por coma.

 

Suponiendo que fac es el nombre de una función que calcula el factorial de una cantidad entera (el factorial de n se define como  n! = 1*2*3*..n). La fórmula

 

f = x!/a!(x-a)!

 

podría entonces ser evaluada como

 

f := fac(x) / (fac(a) * (fac(x-a));

 

donde  f, x y a son variables de algún tipo numérico.

 

La función en sí misma, consiste de una cabecera de función y los bloques de declaraciones y de instrucciones (cuerpo del subprograma). La cabecera se escribe de la forma

 

      FUNCTION  nombre(parámetros formales)  :  tipo;

 

El último elemento, tipo, especifica el tipo de dato del resultado que la función retorna.

 

Los bloque de declaraciones y de instrucciones son similares a los de un procedimiento aplicando las mismas reglas de construcción para éste. Dentro del bloque de instrucciones, sin embargo, el identificador que representa el nombre de la función debe tener asignado un valor del tipo apropiado (según se especifica en la cabecera). Este es el valor que la función devuelve a su punto de referencia. Pueden asignarse valores al nombre de la función en más de un lugar dentro del bloque, siendo válida la última asignación que se efectúa. En Object Pascal tambien es posible asignar el valor que retorna la funcion a la variable result, esta variable se declara implicitamente para toda funcion y se utiliza para expecificar el valor de retorno. Actualmente se da prioridad al uso de result en lugar del nombre de la función.

 

He aquí el desarrollo de un programa para calcular el factorial de un número, por medio de una función definida:

 

 

 

      program factorial;

      var

         numero : real;

 

      function fac(n : Integer) : real;

      var

         i        : integer;

         acum     : real;

      begin

           acum := 1;

 

           for i := 2 to trunc(n) do

               acum := acum * i;

 

           result := acum;  // El código sin usar result seria :

                      // fac := acum;

      end; { fac }

 

      begin

         Repeat

            Write('Numero a calcular su factorial ?');

            Readln(numero);

            Writeln(fac(numero):40:0);

         Until numero = 0;

      end.

 

 

 

La declaracion de la función fac, incluye una declaración del parámetro por valor n. Note también que la última palabra de la línea (real) establece que la función devolverá una cantidad de tipo real.

 

Esta función acepta un valor n y luego calcula el valor de n! usando dos variables locales (i de tipo entero y acum de tipo real). El resultado final se asigna al identificador fac, que es también el nombre de la función.

 

 

 

TIPOS PROCEDIMIENTO

 

En una extensión al Pascal estandar, Object Pascal permite que procedimientos y funciones sean tratados como objetos que pueden ser asignados a variables y pasados como parámetros a subrutinas; esto es posible usando tipos procedimiento (Procedure types).

 

 

Declaración de un tipo procedimiento

 

La declaración de un tipo procedimiento es, en esencia, igual al encabezamiento de un procedimiento o función, excepto que se omite el identificador que sigue a la palabra clave procedure o function. Esta declración debe especificar los parámetros y, en el caso de  funciones, el tipo del resultado. Estos son algunos ejemplos:

 

      type

            Proc       = Procedure(Var x, y : integer);

            Rutina     = Procedure;

            Funcion    = Function (x : real) : real;

            ProcesoCad = Procedure(Var C : String);

 

Los nombres de los parámetros en la declaración de un tipo procedimiento son meramente decorativos, éstos no afectan el significado de la declaración.

 

 

Variables procedimiento

 

Una vez que un tipo procedimiento ha sido declarado, es posible declarar variables de dicho tipo. Estas son llamadas variables procedimiento. Por ejemplo, de acuerdo a las declaraciones anteriores:

 

      var

            F : Funcion;

            P : ProcesoCad;

 

Tal como a una variable entera se le puede asignar un valor entero, a una variable procedimiento se le puede asignar un procedimiento como valor. En este contexto, una declaración de procedimiento o función debe ser vista como una clase especial de declaración de constante; el valor de la constante será el procedimiento o función. Así:

 

      Function Tangente (Angulo : Real) : Real;

      begin

            Tangente := Sin(Angulo) / Cos(Angulo)

      end;

 

      Procedure AMayusculas (Var S : String);

      Var

            cont : byte;

      begin

            for cont := 1 to length(S) do

                s[cont] := UpCase(S[cont])

      end;

 

 

Las variables F y P declaradas previamente pueden ahora tomar estos valores:

 

      F := Tangente;

      P := AMayusculas;

 

Siguiendo estas asignaciones, una llamada a F(x) es equivalente a llamar Tangente(x) y P(cadena) es equivalente a AMayusculas(cadena).

 

Igualmente, pueden hacerse asignaciones entre variables de tipo procedimiento, siempre y cuando los tipos de éstas sean idénticos; ésto es, el número, tipo y orden de los parámetros declarados en su encabezado deben coincidir; finalmente el resultado de las funciones debe ser igual. Como se mencionó anteriormente, los nombres de los parámetros no son significativos.

 

Para que un procedimiento o función pueda ser asignado a una variable procedimiento, debe cumplir los siguientes requerimientos:

 

            - No puede ser un procedimiento o función estándar

            - No puede ser una rutina anidada

            - No puede ser un procedimiento inline

 

 

 

El siguiente programa demuestra el uso de tipos procedimiento para obtener  tablas de diferentes funciones aritméticas:

 

 

Program Tablas;

type

    FunC = Function (x, y : integer) : integer;

 

Function Suma (x, y : integer) : integer;

begin

     Suma := x + y

end;

 

Function Resta (x, y : integer) : integer;

begin

     Resta := x - y

end;

 

Function Multi (x, y : integer) : integer;

begin

     Multi := x * y

end;

 

Procedure ImprimirTabla(a, b : integer; Operacion : Func);

Var

   x, y : integer;

begin

     for x := 1 to a do

         begin

              for y := 1 to b do Write(Operacion(x,y) : 6);

              writeln

         end;

     Writeln('Presione Enter para continuar');

     Readln

end;

 

begin

     ImprimirTabla(10,10,Suma);

     ImprimirTabla(10,10,Resta);

     ImprimirTabla(10,10,Multi)

end.

 

 

 

Parámetros de tipo procedimiento son particularmente útiles en situaciones donde una cierta acción común se lleva a cabo mediante un conjunto de procedimientos o funciones. En este caso el procedimiento ImprimirTabla representa la acción común, la cual es llevada a cabo por las funciones Suma, Resta y Multi.

 

 

 

RECURSION

 

Un característica interesante de los procedimientos y funciones del Pascal, es su capacidad para llamarse a sí mismas. A ésto se le llama recursión. El uso de la recursión es conveniente para aquellos problemas que pueden definirse de una manera natural en términos recursivos, como calcular un determinante por cofactores  por ejemplo (aunque se pueden plantear soluciones no recursivas).

 

Al escribir un procedimiento o función recursivo es esencial que el subprograma incluya una condición de terminación, evitando que la recursión continúe indefinidamente. Mientras esta condición  permanezca insatisfecha, el subprograma se invocará a sí mismo en el lugar apropiado, del mismo modo que invocaría a cualquier otro procedimiento o función.

 

Veamos ahora una variación de la función para obtener el factorial de un número; recordando que el factorial de un número puede calcularse en términos recursivos como:

 

n! = n * (n-1)! , donde  0! = 1

 

Estas     ecuaciones   proporcionan   la   base  para  escribir  la  función  recursivamente (usando 0! = 1 como condición de terminación).

 

He aquí el programa completo:

 

 

            program factorial_Recursivo;

      var

         numero : real;

     

      function fac(n : real) : real;

      begin

           if n = 0 

              then  fac := 1  else  fac := n * fac(n-1);

      end; { fac }

     

      begin

           Repeat

                Write('Numero a calcular su factorial ?');

                Readln(numero);

                Writeln(fac(numero):40:0);

           Until numero = 0;

      end.

 

 

Observe que esta solución es más inmediata que la planteada anteriormente.

 

El siguiente programa ilustra un procedimiento recursivo; el programa lee una línea de texto de longitud indefinida hasta encontrar un retorno de carro (ENTER: cuyo código ASCII es 13). La línea de texto se escribirá luego en orden inverso.

 

Estudie la lógica del procedimiento recursivo usado:

 

 

      program recursividad;

      procedure Escribir_al_reves;

      var

         car : Char;

      begin

           Read(car);

           if car <> #13

        then Escribir_al_reves;

           Write(car);

      end;

 

      begin

           Escribir_al_reves;

           ReadLn;

      end.

 

 

Si el programa se ejecuta con la siguiente entrada:

 

      ESTE PROGRAMA ESCRIBE AL REVES

 

Se obtendrá como salida:

 

      SEVER LA EBIRCSE AMARGORP ETSE

 

Ya que después de leídos todos los caracteres, se escribirá el más reciente (que corresponde a la llamada más reciente del procedimiento), seguido del segundo más reciente, y así sucesivamente.