Seguimos con el cursete de programación. Según vaya avanzando, me meteré más en profundidad en algoritmos genéricos, ejemplos y demás… pues el anterior post del curso igual me parecía muy básico para extenderme demasiado…
Programación estructural, modular y orientada a objetos
Estos tres conceptos hacen referencia a una serie de principios o pautas que definen la filosofía a seguir en los programas.
La programación estructural es la que se compone de las 3 estructuras básicas ya vistas: secuencial (las instrucciones se ejecutan una detrás de otra, en orden descendente), alternativa (los if) e iterativa (los bucles), teniendo en cuenta que todo programa comienza en un sitio y termina en otro o, dicho de otro modo, tiene un principio y un final. (Todo principio, tiene un final, que decía Morpheus)
La programación modular consiste en dividir un programa en partes bien diferenciadas, llamadas módulos (o subprogramas), que pueden ser analizadas y programadas por separado. Existe un algoritmo o programa principal, que cede el control al resto de módulos y, una vez éstos se han ejecutado, vuelve a tomar el control, continuando la ejecución del programa por dónde los llamó.
Entre las reglas principales para programar de forma modular (práctica más que conveniente), están:
- Cada módulo tiene que tener un punto de entrada y otro de salida. (Es decir, el módulo una vez haya realizado su tarea, debe devolver el control al programa principal desde donde fue llamado).
- En el programa principal, se debe definir todos los módulos que se van a utilizar y definirlos en consecuencia.
- Los módulos deben tener independencia, en la medida de lo posible, respecto del programa principal. La idea es que los módulos realicen una tarea de forma genérica, para poder reutilizar código si fuese necesario en otro programa, sin tener que tocar el código del módulo.
- Los datos del programa principal que el módulo necesite, serán pasados desde allí. (Evitar las variables globales).
De la POO ó OOP, programación orientada objetos, hablaré más adelante, pues primero conviene tener soltura con las “programaciones” anteriores, para pillar bien ciertos conceptos.
Funciones y procedimientos
Para no cambiar de tema y después retomarlo, seguiré hablando de subprogramación o programación modular. Las funciones y los procedimientos forman parte de ello.
¿Qué es un procedimiento? Teniendo en cuenta lo anterior, se definiría como un módulo. Concretamente, como un módulo que no retorna ningún valor, ejecuta lo que tenga que ejecutar y devuelve el control al programa que lo llamó.
¿Qué es una función Un módulo que retorna un valor.
La llamada de ambas desde un programa, se hace de forma diferente, atendiendo a su definición. Cuando trabajemos con una función, al llamarla no podremos llamarla sin más, si no que deberemos hacerlo en alguna parte en el que el valor devuelto sea evaluable. Imagina que tenemos una función llamada EsNumero, de tipo booleano (devuelve un valor de tipo bool, true o false, según la cadena pasada esté compuesta toda de números o haya otros caracteres). Si la llamamos como
Sentencia 1
Sentencia 2
EsNumero(cadena)
Sentencia 3
A dónde iría el valor devuelto por la función? A ninguna parte. Y tampoco sería evaluable de ningún modo. La llamada correcta sería en un if, o igualando a una variable. (if EsNumero(cadena) then writeln(”es numero”); ó miVar=EsNumero(cadena), donde miVar es una variable de tipo boolean).
Observa la diferencia.
También al margen, observa que en el if no he puesto EsNumero(cadena)=true. En un if, cuando la evaluación de la condición da true, verdadero, es cuando se ejecuta las instrucciones que hay dentro de él. Al retornar nuestra función un valor booleano, si devuelve true, en el if habrá algo como if true then… puesto que true, es true…. entrará. Si retorna false, if false then.. como la evaluación de false no es verdadero, no entrará. Por supuesto puedes ponerlo de forma completa con EsNumero=true, pero estos detalles muestran la soltura de un programador.
Por el contrario, la llamada a un procedimiento se hace en “solitario”, como en el ejemplo primero de EsNumero (el que está en el recuadro).
Dentro de una función, deberemos tener una sentencia que indique el retorno de un valor. En pascal, tomaremos el nombre de la función y le asignaremos el valor. (EsNumero:=true). En C, se hará mediante return valor; Para no olvidar este retorno, una buena práctica es declarar una variable del tipo de la función, en este caso bool. Es decir, al principio de la función, haremos var retorno: bool; En el código de la función, iremos asignando a esa variable el valor que proceda en cada caso y al final de la misma, asignaremos esa variable a la función, EsNumero:=retorno. Así, la lógica de la función podrá fallar, pero siempre devolveremos un valor de forma clara.
Paso de parámetros
Los parámetros son los datos que pasaremos a los subprogramas, cuando necesiten trabajar con datos ajenos a ellos. Hay dos formas de pasar estos parámetros, por valor o por referencia. Tanto las funciones como los procedimientos, aceptan ambas formas.
Paso por valor. Se pasa una copia de la variable. Los cambios que hagamos a esa variable, sólo tendrán efecto en el subprograma, pues al tratarse de una copia, en el programa principal tendremos el valor original.
Paso por refencia. En este caso se pasa la dirección de memoria en dónde está la variable, una referencia a su ubicación. (de ahí el nombre). Por tanto, cualquier cambio que hagamos en ella, quedará modificado en el resto del programa, pues se trata de la misma variable.
Y… ¿qué sistema es mejor? Depende de lo que queramos hacer. Es cuestión de valorar cada caso y decidir. (Cuando se trabaje con TAD´s, tipos abstractos de datos, los pasaremos siempre por referencia, pues por valor carecería de sentido por concepto).
Parámetros formales / parámetros actuales. Cuando hablamos de parámetros formales, nos referimos a los parámetros presentes en la declaración de un subprograma. Por parámetros actuales entendemos los parámetros pasados al subprograma en cada llamada. Los parámetros formales y actuales han de coincidir en número y en tipo. De forma opcional, también pueden coincidir en el nombre.
Vida y visibilidad de variables
De vuelta a las variables, comentar una cosilla en relación a la última frase del apartado anterior. Las variables globales, a evitar, son aquellas que son accesibles en cualquier lugar del programa principal y de los subprogramas. En cambio, las locales sólo son accesibles desde el subprograma donde fueron creadas. (Si se crean en el programa principal, sólo serán visibles en el programa principal, no así en los módulos). Por este motivo, si tenemos dos subprogramas y en cada uno de ellos declaramos una variable con el mismo nombre, el programa no daría error, pues se trataría de dos variables diferentes (no se ven entre sí y por tanto, no influyen entre ellas, y tampoco pasaría nada por tener una variable llamada i en el programa principal y en dos subprogramas). La vida de esas variables, terminará (se liberará el espacio en memoria destinado a esa variable), cuando se finalice el subprograma… por ello hay que tener en cuenta que las variables locales de cada subprograma no son accesibles desde el resto de subprogramas o programa principal… si nos hace falta un dato procesado en el subprograma, lo enviaremos por medio de una función, retornando un valor.