TEMA 1.13
PROGRAMACIÓN MODULAR





1.13.1 - INTRODUCCIÓN A LA
PROGRAMACIÓN MODULAR

Hasta ahora hemos visto programas muy sencillos que simplemente eran una serie de instrucciones colocadas una detrás de otra en un listado. Esto va muy bien para nuestros programas extremadamente simples, pero conforme los proyectos se hacen algo más grandes van apareciendo una serie de problemas.

Para ir solucionando estos problemas se ha desarrollado lo que se conoce como programación modular y que consiste en dividir un programa en módulos o subprogramas más pequeños que realizan una acción determinada y son llamados por los otros módulos cada vez que haga falta.

Esta forma de trabajar nos proporciona las siguientes ventajas, que solucionan los problemas anteriores.

Imaginemos por ejemplo en Windows los cuadros de diálogo que aparecen para abrir y guardar los archivos en los programas. ¿Has observado que casi siempre son iguales?. Esto es porque son módulos ya programado que se encuentran en una biblioteca de enlace dinámico (DLL) y son llamados por los programas. De esta forma el programador no se tiene que preocupar de programar las listas, los iconos, los botones, etc... sino que estos ya han sido programados anteriormente por otras personas y sólo hay que llamarlos desde nuestro programa de Windows. Además si se encuentra algún error basta con corregirlo y sustituir la DLL correspondiente (Esto es lo que hace constantemente Microsoft con sus "Service Packs" y sus "Segundas ediciones") para que todos los programas que la utilicen queden inmediatamente arreglados.





1.13.2 - LA PROGRAMACIÓN MODULAR EN QBASIC

La programación en Windows se basa en las DLL y por lo tanto en la programación modular. En los lenguajes más avanzados como C++ o Pascal se pueden usar bibliotecas de funciones que son llamadas desde nuestro programa y son enlazadas en el momento de compilar nuestro ejecutable EXE.

En QBasic, al ser un lenguaje interpretado nos tenemos que limitar a usar módulos o más concretamente procedimientos que estén dentro del único fichero BAS que compone nuestro programa. Las posibilidades son muy limitadas pero nos van a servir para aprender los conceptos más importantes de esta técnica.

Hasta ahora hemos escrito las instrucciones en el editor de código de Qbasic. Esta parte del programa es el "Módulo Principal" que debe existir siempre y desde donde se llamará a los distintos módulos del programa, si existen.

En QBasic existen dos tipos de módulos: procedimientos SUB y funciones.





1.13.3 - LLAMADA A
PROCEDIMIENTOS Y FUNCIONES

Ya hemos usado sin saberlo procedimientos y funciones ya incluidas en el lenguaje de QBasic. Por ejemplo al usar la instrucción PRINT lo que hacemos es llamar a un procedimiento ya programado dentro de QBasic que se encargará de dibujar caracteres en la pantalla, o al usar la función SQR dentro de una expresión conseguimos calcular la raíz cuadrada de un número sin tener que programar en ningún sitio las operaciones necesarias para hacer esto.

La diferencia importante entre la llamada a los procedimientos y a las funciones es que para llamar a un procedimiento se escribe su nombre al principio de la línea, mientras que las llamadas a funciones van dentro de expresiones o bien formando parte de condiciones. Si están en una asignación, las llamadas a funciones siempre van a la derecha del signo "igual".

También es muy importante darse cuenta de que los parámetros, si los hay, que se pasan a las funciones van entre paréntesis, mientras que los de los procedimientos no es obligatorio que vayan entre paréntesis, ya veremos por qué.

Veamos algunos ejemplos, donde PRINT es un procedimiento y SQR es una función.

PRINT "Hola"

Llamamos a PRINT y le pasamos como parámetro "Hola". PRINT escribirá "Hola" en la pantalla, pero no devuelve ningún valor.

raiz = SQR(n)

Llamamos a la función SQR y le pasamos como parámetro la variable n. El resultado devuelto lo asignamos a la variable raiz. Observa que SQR está a la derecha del signo igual y que el parámetro n va entre paréntesis.

PRINT SQR(4)

Aquí llamamos a SQR para que nos calcule la raíz de 4, y el valor devuelto por la función se lo pasamos a PRINT para que lo escriba en la pantalla.

DO
	...
LOOP WHILE SQR(n)>10
...
IF SQR(n)>50 THEN
	...
END IF

En estos dos ejemplos usamos los resultados de la función como parte de la expresión que se usará como condición en instrucciones repetitivas y condicionales.

A = PRINT(b)

Esto se ve muy raro a primera vista ¿Cómo le asignamos a una variable lo que escriba PRINT en la pantalla?. Esto no se puede hacer, está mal escrito. Las llamadas a procedimiento siempre van al principio de la línea.

SQR(4)

Aquí se produce un error al pasar de línea porque llamamos a la función, pero cuando nos devuelve el resultado no hacemos nada con él porque la función ni está en una asignación ni en una condición.





1.13.4 - DEFINICIÓN DE PROCEDIMIENTOS

Hasta ahora hemos llamado a procedimientos que ya están inluídos en el lenguaje de QBasic, pero para poder utilizar los nuestros propios además de llamarlos hay que programarlos.

Todo el código que hemos escrito hasta ahora en nuestros programas ha estado en el "Módulo Principal" del programa. Para definir un procedimiento basta con seleccionar "Nueva sub" en el menú "Edición" de QBasic. Entonces aparecerá un pequeño cuadro de diálogo donde tendremos que escribir el nombre del procedimiento y pulsar Enter. Después de hacer esto desaparecerá de la pantalla nuestro módulo principal y en su lugar aparecerá algo así como esto.

SUB Prueba
END SUB

Esto es el principio y el final del procedimiento. Entre estas dos líneas será donde tendremos que escribir todo el código.
Fuera lo único que está permitido son comentarios. Siempre es muy recomendable escribir una pequeña descripción de lo que hace el procedimiento. También es normal escribir los nombres de los procedimientos con la primera letra en mayúsculas. Para elegir los nombres deberemos elegir un nombre descriptivo y tendremos las mismas restricciones que con los nombres de variables (Sin acentos ni ñ, máximo 40 caracteres, etc...).

Para volver al módulo principal o para pasar de un procedimiento a otro tendremos que pulsar la tecla F2 y en la lista que aparece elegir el procedimiento al que queremos ir. Podemos entrar y salir de los procedimiento todas las veces que sea necesario mientras estamos escribiendo el código fuente del programa.

Vemos con un ejemplo paso a paso. Vamos a hacer un programa que contenga un pequeño procedimiento para escribir un rótulo en la pantalla. Al usar programación modular podemos llamar al procedimiento cada vez que queramos escribir el rótulo en lugar de tener que repetir todo el código. Allá vamos.

Abrimos un nuevo programa y empezaríamos a escribir lo que va en su módulo principal. En este caso no vamos a escribir todavía nada.

Ahora vamos a definir el procedimiento que se va a llamar EscribeRotulo. Para hacerlo seleccionamos "Nueva sub" en el menú "Edición". Tras escribir el nombre y pulsar Enter aparecerá en la pantalla esto.

SUB EscribeRotulo
END SUB

Ahora vamos a escribir las instrucciones que necsitamos para escribir el rótulo dentro de las dos líneas que ha escrito QBasic.

SUB EscribeRotulo
	PRINT "******************"
	PRINT "* HECHO EN RONDA *"
	PRINT "******************"
END SUB

También podemos añadir en el encabezado una descripción como comentario.

'Este procedimiento escribe "Hecho en Ronda" en
'la pantalla rodeado de asteriscos.
'
SUB EscribeRotulo
	PRINT "******************"
	PRINT "* HECHO EN RONDA *"
	PRINT "******************"
END SUB

Ahora, para volver al módulo principal pulsamos F2 y seleccionamos el primer nombre de la lista que será "Sin_nombre" si todavía no hemos guardado el programa, o el nombre del fichero si ya lo hemos guardado. En el módulo principal bastará con escribir

EscribeRotulo

que es la llamada al procedimiento para que este se ejecute. En un programa con más instrucciones podemos llamar a los procedimientos todas las veces que haga falta escribiendo su nombre, y por su puesto podemos llamar a un módulo desde dentro de otro.

Si abres el fichero del programa (*.BAS) con el editor de MS-DOS o con el Bloc de notas de Windows verás que los módulos están realmente a continuación del módulo principal. Al abrir los programas en QBasic se van separando para no tener listados tan largos y poder pasar fácilmente de uno a otro pulsando F2.





1.13.5 - VARIABLES LOCALES

En nuestro pequeño procedimiento no hemos utilizado ninguna variable, simplemente nos limitamos a escribir un rótulo en la pantalla. Ahora vamos a ver un pequeño programa con un módulo en el que se sumarán los valores de dos variables. Recuerda que aunque aquí aparece todo seguido en QBasic sólo verás el programa principal y tendrás que pulsar F2 para acceder al procedimiento.

CLS
a = 2
b = 4
PRINT a + b
MiProcedimiento	'Aquí llamamos al procedimiento
PRINT a + b


'
'Este procedimiento asigna valores a las variables a y b
'y muestra en pantalla la suma.
SUB MiProcedimiento
	a = 8
	b = 3
	PRINT a + b
END SUB

Veamos ahora el resultado que se produciría en la pantalla al ejecutar el programa:

 6
 11
 6

Extraño ¿No?. Al principio la suma vale 6, después dentro del procedimiento vale 11 con los nuevos valores y después fuera por arte de magia vuelve a valer 6!!!

Veamos lo que hace el programa paso a paso:

Aquí se puede ver fácilmente que las variables son "locales" al módulo donde son declaradas (Recuerda que QBasic las declara automáticamente al usarlas la primera vez), ya sea este el módulo principal, un procedimiento SUB o una función, por lo tanto son variables totalmente distintas aunque tengan el mismo nombre. Para evitar confusiones se recomienda no repetir los nombres de las variables en los distintos módulos.

En los temas anteriores decíamos que el tiempo de vida de una variable comienza cuando es declarada (En Qbasic, cuando se usa por primera vez), y termina cuando el programa termina, liberándose la posición de memoria que utilizaba y perdiéndose su valor, por lo que no se conserva para la próxima vez que ejecutemos el programa. Con las variables locales de los procedimientos ocurre lo mismo, desaparecen al terminar la ejecución del procedimiento y por lo tanto normalmente no se puede esperar que conserven su valor la próxima vez que llamemos al procedimiento, aunque sea durante la actual ejecución del programa.

En QBasic para evitar esto y que las variables conserven su valor entre las distintas veces que llamemos a los procedimientos se dispone de la instrucción STATIC que nos permite declarar variables dentro de módulos de forma que conserven su valor en sucesivas llamadas. Para más detalles sobre esta instrucción consulta la ayuda de QBasic. Si planificamos bien la estructura de nuestro programa normalmente no será necesario utilizar esta instrucción.





1.13.6 - VARIABLES GLOBALES

En algunas situaciones nos puede interesar que una variable "pueda ser vista" por todos los procedimiento que integran nuestro programa. Esta variable la declararemos de una forma especial al propicio del módulo principal del programa y ya podrá ser usada (leída o modificada) por el propio módulo principal o por cualquier otro procedimiento. Veamos el mismo programa del tema anterior, pero esta vez usando variables globales:

DIM SHARED a,b
CLS
a = 2
b = 4
PRINT a + b
MiProcedimiento	'Aquí llamamos al procedimiento
PRINT a + b


'
'Este procedimiento asigna valores a las variables a y b
'y muestra en pantalla la suma.
SUB MiProcedimiento
	a = 8
	b = 3
	PRINT a + b
END SUB

Aahora el resultado sería:

 6
 11
 11

Se puede ver que el código es muy parecido, salvo la (gran) diferencia de que declaramos al principio del módulo principal las variables a y b usando la instrucción DIM SHARED. DIM es para declarar variables y SHARED para que sean globales (compartidas).

En la primera parte del programa asignamos los valores a las variables y mostramos la suma. Después llamamos al procedimiento que modifica los valores de las variables a y b, que en este caso son las mismas que las del módulo principal, y muestra la suma, que es 11. Al salir del procedimiento, ya en el módulo principal se muestra la suma que sigue siendo 11 porque el procedimiento ha modificado las variables globales.

Las constantes definidas en el módulo principal siempre serán globales, mientras que las que definimos dentro de un procedimiento SUB o función serán locales, solo visibles dentro del mismo igual que las variables locales.





1.13.7 - PASO DE PARÁMETROS

En los procedimientos que hemos programado hasta ahora no existían parámetros. Lo más normal es que el procedimiento tome alguna información como entrada, igual que hacen procedimientos ya definidos como PRINT que usa como parámetro la cadena de texto o expresión que queremos que se escriba en la pantalla.

Al conjunto de parámetros de un módulo se le conoce como INTERFAZ porque es la principal forma que tiene de compartir e intercambiar información con el resto del programa.

Veamos como ejemplo un procedimiento que acepta como parámetros dos enteros y escribe en la pantalla el mayor de ellos. Le vamos a llamar EscribeElMayor. También está aquí la llamada que se hace desde el módulo principal.

'Principal
CLS
EscribeElMayor 4, 7

'Escribe en pantalla sólo el número mayor de los dos parámetros
'Datos de entrada: N1, N2: Reales
SUB Escribe el Mayor(N1, N2)
	IF N1 > N2 THEN
		PRINT N1
	ELSE
		PRINT N2
	END IF
END SUB

Vamos a ver con detalle la primera línea de la definición del procedimiento. Usamos la palabra SUB seguida del nombre del procedimiento y a continuación viene lo nuevo. Como antes no usábamos parámetros no poníamos nada más, pero como ahora tenemos dos parámetros, los DECLARAMOS entre paréntesis. Los parámetros son variables que serán manejadas desde dentro del módulo, y por lo tanto igual que las otras variables normales tienen su tipo de datos y un nombre que debe de seguir las mismas normas de construcción. En este caso como no hemos usado sufijos se entiende que son reales de precisión sencilla, si hubiéramos puesto al final del nombre un % serían enteros, o de cadenas si hubiéramos puesto un $.

Un módulo (sea un procedimiento o una función que ya veremos más adelante) puede llevar tantos parámetros como haga falta, o ninguno, y estos no tienen por que ser del mismo tipo. Lo importante es que a la hora de llamar al módulo tenemos que escribir a continuación del nombre constantes o expresiones que encajen en número y tipo con los parámetros que acepta el módulo. En este ejemplo se esperan dos números, por lo tanto habrá que poner exactamente dos expresiones numéricas, ni más ni menos, y tampoco podemos usar cadenas en este caso.

En el ejemplo pasamos como parámetros en la llamada al procedimiento dos expresiones constantes y por lo tanto no debe de haber ninguna duda, pero cuando pasamos como expresión una variable pueden pasar algunas cosas algo inesperadas. Veamos otro ejemplo.

'Principal
CLS
n = 4
PRINT n
MultiplicaPorDos n
PRINT n

'Módulo Multiplica por dos
'Multiplica por dos el argumento
'Datos de entrada: numero: Real que se multiplicará por dos
SUB MultiplicaPorDos (numero)
	numero = numero * 2
END SUB

El resultado sería:

 4
 8

Es fácil de entender: En el programa principal se asigna a n un valor, a continuación se llama a un procedimiento que toma n como entrada, aunque dentro se llame numero, y al terminar se observa que el valor de n ha sido cambiado por el procedimiento.

Esto ha ocurrido porque en Qbasic, si no se especifica lo contrario, se pasan los parámetros de los módulos POR REFERENCIA, es decir, si un parámetro es una variable esta podrá ser modificada por el módulo. Para evitar esto, y que los módulos no puedan modificar los valores de las variables que se les pasan como parámetros lo que hay que hacer es escribir en la llamada al módulo la variable entre paréntesis. A esto se llama PASO DE PARÁMETROS POR VALOR y en este caso lo que se pasa es sólo un número, y no la dirección de la variable original que queda "a salvo" del módulo. Veamos un ejemplo:

'Principal
a = 8
b = 5
c = 7
PonACero (a), (b), (c)
PRINT a, b, c
PonACero (a), b, (c)
PRINT a, b, c
PonACero a, b, c
PRINT a, b, c

'Este procedimiento asigna 0 a los tres parámetros
'Entreada: una, otra, yOtra: Reales
SUB PonACero(una, otra, yOtra)
	una=0
	otra=0
	yOtra=0
END SUB

El resultado sería:

 8      5      7
 8      0      7
 0      0      0

Como se puede ver, la primera vez que llamamos al procedimiento pasamos los tres parámetros por valor y no se produce cambio alguno en las variables del programa principal.
En la segunda llamada pasamos la variable b por referencia (No lleva los paréntesis) y su valor es moificado.
La última vez pasamos todo por referencia y las tres variables son modificadas y acaban con valor 0.

En los ejemplos que hemos puesto los parámetros tienen nombres distintos de las variables del programa principal. Es lo que se suele hacer, ya que deben llevar nombres representativos. Si hubiera coincidido algún nombre no pasa absolutamente nada porque se trata de variables distintas. Lo que no podemos hacer es usar nombres de variables globales ni de otros módulos.

En un apartado posterior se hace una comparación con los procedimientos y se dan sugerencias de cuando usar paso de parámetros por referencia y cuando por valor.





1.13.8 - DEFINICÓN DE FUNCIONES

Este apartado se ha quedado para casi el final del tema de módulos porque la única diferencia que hay entre un procedimiento y una función es que un procedimiento no devuelve directamente ningún valor (Aunque puede modificar variables del interfaz), mientras que una función devuelve un valor que es usado por la expresión en la que está insertada la llamada a la función.

Para definir funciones usaremos la orden "Nueva Función" del menú "Edición" y escribiremos nuestro código entre las líneas

FUNCTION nombreFuncion
END FUNCTION

Como vimos en el apartado de llamadas a procedimientos y funciones para llamar a un procedimiento escribimos su nombre y sus parámetros al principio de una línea y este hace "lo que sea", mientras que para llamar a una función habrá que poner su nombre en una expresión seguida de los argumentos entre paréntesis, si existen.

Para determinar el tipo de dato que devuelve una habrá que ver su nombre. Si no lleva sufijo se entiende que devuelve un valor real de precisión sencilla, si lleva un % devuelve un entero, si lleva un $ devuelve una cadena, etc.

Y dentro de la función para indicar el valor a devolver usaremos el nombre de la función seguido del signo Igual y la expresión cuyo valor se devolverá. Veamos unos cuantos ejemplos de funciones con un programa principal que las llama a todas:

'Principal
CLS
PRINT "La suma de dos y dos es " ; Suma(2, 2)
media = Media3(4, 7, 12)
PRINT "La media de 4, 7 y 12 es ", media
PRINT "El mayor de 15 o 8 es "; ElMayor(15, 8)
IF LaMasLarga("Ronda","Otorrinolaringólogo") = "Ronda" THEN
	PRINT "'Ronda' es más larga que la otra palabra"
ELSE
	PRINT "'Ronda' es más corta que la otra palabra"
ENDIF
PRNT NoHaceNada (2, "Palabra")

'Función Suma: Suma dos valores
'Entrada: n1 y n2: Reales, valores a sumar
'Salida: devuelve la suma como real
FUNCTION Suma(n1, n2)
	Suma = n1 + n2
END FUNCTION

'Función Media3: Calcula la media de 3 valores enteros
'Entrada: n1, n2 y n3: Enteros
'Salida: devuelve la media como real
FUNCTION Media3(n1, n2, n3)
	Media3 = (n1 + n2 + n3) / 3
END FUNCTION

'Función Mayor: Devuelve el mayor de dos números
'Entrada: n1 y n2: Reales, valores a comparar
'Salida: devuelve el mayor de los dos como real
FUNCTION Mayor(n1, n2)
	IF n1 > n2 THEN
		Mayor = n1
	ELSE
		Mayor = N2
	END IF
END FUNCTION

'Función LaMasLarga$: Devuelve la cadena más larga de las dos
'Entrada: cad1$ y cad2$: Cadenas de longitud variable
'Salida: devuelve la cadena más larga
FUNCTION LaMasLarga$(cad1$, cad2$)
	IF LEN(cad1$) > LEN(cad2$) THEN
		LaMasLarga$ = cad1$
	ELSE
		LaMasLarga$ = cad2$
	END IF
END FUNCTION

'Función NoHaceNada$: Devuelve 0 siempre porque no le asignamos nada.
'                     (Los cálculos que hace no tienen ningun efecto)
'Entrada: n% y cad$: Un entero y una cadena de longitud variable
'Salida: Siempre 0
FUNCTION NoHaceNada(n%, cad$)
	x = 123456 + 78
	cadena$ = cad$ + "Hecho en Ronda"
	x = x / n%
END FUNCTION

Por si hay alguna duda el resultado sería este:

La suma de dos y dos es 4
La media de 4, 7 y 12 es 7.666667
El mayor de 15 o 8 es 15
'Ronda' es más corta que la otra palabra
 0

Ahora veamos algunas cosas importantes que hay en los ejemplos.

La llamada a las funciones se puede hacer desde la intrucción PRINT o cualquier otra instrucción, en una expresión que se asignará a una variable, siempre a la derecha del signo igual, o en una expresión que se usa como condición de un IF, WHILE, etc...

Para devolver el resultado se asigna una expresión al nombre de la función, en su interior, normalmente al final. Si esto no se llega a hacer nunca, como pasa en la función NoHaceNada, se devuelve cero o una cadena de longitud cero si era una función de tipo cadena. Si se llegan a ejecutar más de una de estas asignaciones el valor que se devuelve es el último. Por supuesto el valor que se asigna tiene que ser numérico o de cadena dependiendo del tipo de datos de la función.

En el mismo programa no puede haber dos módulos con el mismo nombre.





1.13.9 - RECUSIVIDAD

Como hemos visto la programación modular consiste en que unos módulos se llamen a otros para estructurar y dividir el programa, evitar partes repetidas, reutilizar código, etc. El programa principal llama a unos módulos (Ya sean procedimientos o funciones) y estos a su vez pueden llamar a otros módulos. Conforme los módulos terminan de ser ejecutados devuelven el control al que lo llamó que sigue ejecutándose y cuando termina, si era el módulo principal, el programa termina.

Llamamos recursividad a la posibilidad de que un módulo se pueda llamar a si mismo. Esto normalmente se hace con funciones que devuelven valores que vamos acumulando, comparando, etc.

El principal problema de esta forma de programar es que si una función se llama indefinidamente a si misma las direcciones de retorno de las instrucciones se van acumulando en la memoria del ordenador hasta que no hay espacio suficiente y se produce un error de tipo "Espacio en pila agotado". Una "Pila" es una forma de almacenar datos en la memoria que usa internamente QBasic (Y cualquier otro lenguaje) de forma que se va amontonando información igual que si se tratara de una pila de libros o de platos para poder sacarla en el orden inverso (cogiendo siempre el de arriba), y si esta se hace muy grande ocupará todo el espacio que tiene disponible y provocará este error que no tiene nada que ver con ninguna batería de electricidad ni nada de eso.

Para evitar el error lo que se hace siempre es poner alguna condición que si ya no se cumple no seguimos llamando a la función y ya lo que se hace es ir volviendo hacia atrás y terminando todas las copias de la función que tenemos "empezadas" hasta que acaba la última y se vuelve al módulo principal y el programa termina.

Veamos un ejemplo muy típico de un programa que calcula el factorial de un número usando recursividad.

'Principal
INPUT "Escribe el número: "; n#
PRINT "El factorial de "; n#; " es "; Factorial#(n#)

'FACTORIAL: Calcula el factorial de forma recursiva
'Entrada: Real de doble precisión: Número a calcular
'Salida: El factorial como real de doble precisión
FUNCTION Factorial#(n#)
	IF n# = 1 THEN
		Factorial# = 1
	ELSE
		Factorial# = n# * Factorial#(n# - 1)
END FUNCTION

Ahora veamos el planteamiento del problema. El factorial de un número es el producto de todos los números desde 1 hasta el número, por ejemplo el factorial de 4 es 4 * 3 * 2 * 1, es decir, 24. Para calcularlo de forma recursiva hemos definido el factorial de un número como el producto de dicho número por el factorial del "número anterior". Para salir de la recursividad ponemos como condición que si el número es 1 el factorial ya es 1 y no tenemos que seguir llamando a la función.

Hemos definido tanto la función como las variables de tipo real de doble precisión (poniéndole el & como sufijo) porque es el tipo de datos de QBasic que admite valores más altos ya que el factorial puede alcanzar valores muy elevados.

Veamos lo que ocurriría paso a paso suponiendo que el usuario quiere calcular el factorial de 4.

Como se ha podido ver conforme vamos entrando en las funciones se van llamando a si mismas sucesivamente hasta el punto en que una de ellas ya no lo hace más y termina devolviendo un valor a la anterior que lo va acumulando y termina. Así todas se van cerrando hasta que llegamos al programa principal que se queda con el último valor y termina el programa.

Si hubiera habido alguna variable local es muy importante comprender que aunque haya muchas copias de la función y todas tienen el mismo nombre se trata de variables totalmente distintas, cada una de su copia de la función, que no compartirán sus valores en ningún caso y en todo caso estarían ocupando memoria por separado.

Podemos comprobar que si intentamos calcular con este programa el factorial de un número superior a aproximadamente 30 se produce el error de "Espacio en pila agotado" ya que tenemos en memoria a la vez 30 copias de la función con sus variables de entorno y sus punteros a las funciones que los han llamado. En un programa no recursivo casi nunca llegaremos a tener funciones "anidadas tan profundamente" y no se producirá este error.

Como se ha podido ver la programación recursiva es algo difícil de entender y que puede llegar a producir errores (En otros lenguajes se bloquearía el ordenador al desbordarse la pila). Este problema se podía haber resuelto con un bloque FOR de una forma tan sencilla como esta:

factorial& = 1
FOR i = 1 TO n
	factorial& = factorial& * n
NEXT

Todos los problemas se pueden resolver siempre de la forma normal con condiciones y bucles (Esquema iterativo) o usando módulos que se llaman a si mismo (Esquema recursivo). Solo deberemos utilizar la recursividad cuando demostremos en la fase de análisis del programa que el algoritmo recursivo es más rápido o más corto que el iterativo. En la mayoría de problemas sencillos normales y con las distintas posibilidades de usar bucles REPETIR y MIENTRAS que ofrece QBasic y los otros lenguajes estructurados es aconsejable evitar la recursividad siempre que se pueda ya que puede ser difícil demostrar matemáticamente que un procedimiento complicado usado de forma recursiva no va a llegar a ejecutarse demasiadas veces desbordando la pila y llegando a detener la ejecución del programa.





1.13.10 - DIFERENCIA ENTRE
PROCEDIMIENTOS Y FUNCIONES

En la programación modular hemos diferenciado dos tipo de módulos: Las funciones, que devuelven un valor tras su ejecución y los procedimientos que no devuelven ningún valor.

En el lenguaje de QBasic la forma de llamar a uno y a otro es distinta pero en otros lenguajes como C todos los módulos son iguales y es el usuario el que tiene que determinar si usa al valor devuelto por los módulos insertando su llamada en una expresión, o no.

Queda claro que tendremos que usar una función cuando queramos obtener un valor y tendremos que usar un procedimiento cuando solo queramos que se haga algo o bien queramos estructurar el programa dividiéndolo en varias partes que serán llamadas cuando haga falta por un menú situado en el módulo principal.

Nos puede ocurrir que necesitemos que un módulo nos devuelva dos o más valores del mismo o de distinto tipo. En este caso no podemos usar una función porque esta sólo devuelve un valor. Habrá que usar un procedimiento al que le pasaremos parámetros por referencia y nos los devolverá modificados cuando termine. Veamos un ejemplo muy sencillo de un procedimiento que intercambia el valor de dos variables:

'Principal
a = 92
b = 7
PRINT "a vale "; a; " y b vale "; b
Intercambia a, b
PRINT "a vale "; a; " y b vale "; b

'Procedimiento Intercambia
'Intercambia los valores de dos variables numéricas
'Entrada: a y b: Reales cuyos valores serán intercambiados
SUB Intercambia(a, b)
	aux = a
	a = b
	b = aux
END SUB

Donde el resultado sería:

a vale 92 y b vale 7
a vale 7 y b vale 92

El programa es muy fácil de entender. El procedimiento modifica las dos variables que se le han pasado por referencia. En este caso las dos variables son del mismo tipo, pero en otro problema podían haber sido de cualquiera y se modificarían igual.

Por ultimo aclarar dos conceptos importantes en la teoría de la programación modular:

Llamamos "Funciones Puras" a las que tienen estas características:

De esta forma conseguimos que al llamar a una función con unos determinados parámetros siempre nos devuelva el mismo valor, y también conseguimos poderla usar en otro programa de QBasic solo con copiarla y pegarla sin tener que modificar ni añadir nada estando completamente seguros de que su funcionamiento va a ser siempre idéntico.

Llamamos "procedimientos puros" a los que tienen estas características:

En algunos casos, especialmente en las funciones tendremos que intentar que sean puras, y en todos los programas será conveniente no abusar de las variables globales ya que pueden hacer que los errores lógicos sean difíciles de encontrar, además de que si usamos un módulo en otro programa será necesario que existan las mismas variables globales para que todo funcione bien, cosa que puede ser un problema.





1.13.11 - PROTOTIPOS

Si has guardado algún programa de los que incluyen procedimientos o funciones habrás observado que al hacerlo aparecen automáticamente al principio del listado, en el módulo principal, unas líneas que empiezan con DECLARE SUB o DECLARE FUNCTION seguida de los nombres y los parámetros de los módulos.

Estas líneas son lo que se conoce en programación como prototipos de procedimientos y funciones. No tienen nada que ver con los prototipos de un nuevo invento que está en desarrollo ni nada de eso, sirven para indicarle a QBasic de que tipo son los parámetros y los datos que devuelven los módulos para que los "conozca" antes de ser llamados y no tenga que ir a buscarlos por todo el código fuente del programa cada vez que hagan falta.

Estas líneas las escribe automáticamente QBasic al guardar los programas y nosotros no tenemos que molestarnos en escribirlas ni conocer su sintaxis correcta.

Si nuestro programa no tiene los prototipos, ya sea porque los hemos borrado o porque todavía no hemos guardado el programa y no se han creado, todo funciona perfectamente pero se supone que las llamadas a módulos van algo más lento (no lo he cronometrado). Si borramos los prototipos volverán a aparecer la próxima vez que guardemos el programa.

Los prototipos, si existen, siempre van a ser la primera línea del programa, antes no puede haber ninguna instrucción ejecutable. Si movemos los prototipos hacia abajo o escribimos alguna instrucción antes se generará un error y tendremos que volver a dejarlos al principio. Lo que sí podemos escribir antes de los prototipos al principio del programa son líneas de comentario (Que empiezan con un apóstrofo). Es común en QBasic, cuando tenemos un programa terminado más o menos largo, escribir al principio veintitantas líneas de comentario que incluyen un título bonito y alguna descripción para que ocupen la primera pantalla del listado y sea lo primero que se ve en el editor al abrir el programa.

Si ya tenemos nuestros módulos, hemos guardado el programa y se han generado los prototipos, y después hacemos algún cambio en los parámetros de alguno de ellos es posible que la cabecera del módulo no se corresponda con su prototipo y al ejecutar el programa se produzca algún error. En este caso la forma más cómoda y más rápida de solucionar el problema es borrando todos los prototipos y guardando el programa para que se generen de nuevo correctamente, mejor que intentar arreglarlos nosotros mismos.

También puede ser importante saber que un prototipo no se generará si todavía no llamamos al módulo desde ninguna otra parte del programa, aunque el código ya esté escrito.













CuRSo De iNTRoDuCCióN a La PRoGRaMaCióN CoN QBaSiC
© 2004 Juan M. González