04 Programacion Modular
04 Programacion Modular
Problema
Al parecer el problema es cómo esta roca, tan complejo o grande que quizá no lo
podamos solucionar o detener, y nos pasará por encima aplastándonos.
Problema
Tareas:
1 1- Obtener los datos
2 2- Separar a los alumnos que llevan el curso
3- Obtener los promedios de cada alumno
5 4- Ordenar a los alumnos por promedio
3 5- Imprimir los datos ordenados
4
5
4
3
2
1
Una vez descritas las tareas que tiene el problema, se coge una a una las tareas y se
les empieza a analizar. Es muy probable que esas tareas sean aún muy complejas, pues
bien, a esas tareas les podemos aplicar el método anterior, esto es subdividirlas en
una serie de sub tareas, por ejemplo:
1/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Tarea: Obtener los promedios de cada alumno
1- Tomar un alumno
2- Calcular el promedio de prácticas (PPr).
3- Calcular el promedio de laboratorios (PLab).
4- Aplicar la fórmula (2xPPr + PLab + 3xEx1 + 4Ex2)/10 para obtener la nota del curso
5- Si hay más alumnos, repetir los pasos 1 al 5
Observe que las tareas que se han llegado a establecer, además de darnos una idea
general de lo que tenemos que hacer para solucionar el problema, tienen algunas
características.
En primer lugar cada tarea es independiente de la otra, esto quiere decir que si
tomamos una de las tareas podemos tratar de resolverla independientemente si se
tiene o no una solución para las otras. Por ejemplo la tarea 2, “Separar los alumnos que
llevan el curso”, teniendo la lista de alumnos podemos separarlos independientemente
de saber cómo se obtuvo la lista (por el teclado, por un archivo de textos, etc.),
tampoco interesa aquí qué se va a hacer con la lista seleccionada (se va a ordenar por
nombres, por promedio ponderado o por un curso, o si se va o no a imprimir y en qué
medio). Esta característica hace que, como habrá podido darse cuenta, podamos
empezar a solucionar el problema por cualquiera de sus tareas, sin necesidad que sea
en un orden determinado, y más aún podemos solucionar una parte del problema sin
tener idea de cómo se va a solucionar alguna otra tarea.
Otra característica de esto es que las tareas que se describen no están detalladas, no
se menciona por ejemplo el nombre del archivo de donde se obtendrán los datos, qué
método se va a emplear para ordenar los datos, ni la forma cómo se va a obtener la
nota del curso. Esos detalles se irán colocando conforme ataquemos cada uno de los
problemas.
Esta metodología de solución de problemas ha sido utilizada por muchos años con gran
éxito y se denomina “Diseño descendente”, se espera, en este texto, que usted pueda
llegar a dominar esta metodología.
Otra característica de esta metodología es que una vez definida la lista de tareas de
un problema se puede conformar varios equipos de personas, a cada equipo se le puede
entregar una tarea que deberá resolver. Esto hará que la solución del problema se dé
más rápidamente, porque estarán trabajando en paralelo. Cada grupo dará solución a
una tarea, que entregará en lo que se denomina un “módulo” con todas las sub tareas
resueltas, luego se juntarán los módulos y se tendrá la solución para el problema
inicial, esto se conoce con el nombre de “programación modular”.
2/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Otra ventaja que se puede apreciar en esta forma de trabajar es que si luego de
implementar la solución nos damos cuenta que una parte del programa no es muy
eficiente, por ejemplo se demora mucho en mostrar los resultados, podemos
reemplazar el módulo por otro que resuelva la misma tarea, pero más eficiente, sin
tener que modificar todo el programa.
Según esto:
int factorial (int); Declara una función denominada “factorial” que
recibe como parámetro un valor entero. La función
devolverá o retornará como resultado un valor entero.
Seguramente la función calculará el factorial de un
número entero dado como parámetro.
double longitudDeSegmento (double, double, double, double); Declara
una función denominada “longitudDeSegmento” que
recibe como parámetro cuatro valores de punto
flotante y retorna un valor de tipo double.
Probablemente la función recibirá dos coordenadas (x1,
y1), (x2, y2) y retornará la longitud del segmento que
lo forma.
3/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
int factorial (int){
int fact=1;
for (int n = num; n > 0; n--){
fact *= n;
}
return fact;
}
double longitudDeSegmento (double x1, double y1, double x2, double y2){
return sqrt (pow(x2-x1, 2) + pow(y2-y1, 2));
}
Una vez declara e implementada la función, se puede usar como “printf” o “scanf”.
Bibliotecas de funciones
En el lenguaje C se puede trabajar modularmente de muchas formas, sin embargo nos
centraremos en una forma ligada el concepto de “Reutilización de código”. La idea es
plantear el trabajo con funciones pensando que cada vez que desarrollemos un
proyecto no lo hagamos todo desde cero. Esto es que cuando diseñemos por primera
vez un grupo de funciones las coloquemos, desde un principio, en un archivo especial,
de modo que la próxima vez que necesitemos alguna de esas funciones solo tengamos
que incorporar ese archivo al proyecto sin tener que volverlo a escribir, reutilizando el
código. En otras palabras, el trabajo en este nuevo proyecto se limitará a repetir de
modo similar lo que hacemos cuando necesitamos usar por ejemplo la función printf,
esto es colocar la definición de esa función mediante la cláusula #include y luego
simplemente utilizarla. Ese archivo especial del que hablamos se suele denominar
“Biblioteca de funciones” y la idea es que en una de esas bibliotecas se coloque un
grupo de funciones que estén relacionadas de alguna forma, por ejemplo funciones
como la media, mediana, moda, desviación estándar, etc. se pueden agrupar en una
biblioteca de funciones estadísticas, funciones para graficar líneas, círculos,
cuadriláteros, etc. pueden agruparse en una biblioteca de funciones gráficas.
Un ejemplo explicará lo que queremos hacer:
Supongamos que queremos desarrollar un programa que nos permita calcular el
factorial de un número, esto es, se ingresar un número entero y el programa calcula y
nos devuelve su factorial.
Entonces las tares que debe realizar el programa sería:
1. Leer el valor entero
2. Calcular el factorial
3. Mostrar el resultado
Plasmar este programa en NetBeans es una tarea sencilla, simplemente creemos un
proyecto y escribamos el código siguiente:
4/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Observe que nuestro programa usará una función “factorial” de la misma forma que
usamos printf o scanf, con la diferencia que nuestra función factorial está subrayada
en rojo, lo que significa que no se reconocerá el significado la palabra “factorial” por
lo que si compilamos el programa nos dará una error.
Para corregir el error debemos realizar dos tareas:
1. Declara la función factorial
2. Implementar la función factorial.
Estas dos tareas deben ser hechas pensando siempre en la reutilización del código.
Entonces, debemos crear primero “un archivo de cabecera”, “header file” o “archivo.h”
donde coloquemos allí su declaración. Debe tener en cuenta que nuestro ejemplo solo
define una función pero, para que esto sea práctico, deberíamos pensar en varias
funciones que se coloque en este archivo de cabecera. Para esto nos dirigimos a las
carpetas del proyecto y en la carpeta “Header Files” presionaremos el botón derecho
de mouse para que se despliegue un menú como se ve a continuación:
5/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Allí nos dirigimos a la opción “Nuevo” y se desplegará otro menú, allí se elegirá la
opción C Header File…, como el que se aprecia a continuación:
6/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
En la ventana que aparece coloque siempre como nombre (como una buena práctica de
programación) el mismo que usó para el archivo de cabecera, en este caso
FuncionesEstadisticas. Con esto ahora tendremos otro archivo con extensión “.c” como
se ve a continuación:
7/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Observe que su proyecto ahora está formado por tres archivos. En la ventana de
proyectos a la izquierda del NetBeans se puede ver esto.
8/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Finalmente debemos saber que cuando compilemos el proyecto, cada módulo con
extensión “.cpp” se compilará de manera independiente y luego se enlazará al programa
ejecutable “.exe”, por lo tanto cualquier función que utilicemos en un módulo debe ser
declarada antes de utilizarla. Por lo tanto, así como cuando usamos en main la función
printf necesitamos incluir (#include) la biblioteca stdio.h, la definición de nuestra
función factorial debe ser incluida en todos los módulos en donde se use. Regresando a
nuestro ejemplo, por esa razón, para que se pueda compilar el módulo “main.c” del
programa debemos colocar la definición de la función de la función factorial colocando
la orden #include correspondiente como se ve a continuación:
Una vez realizado esto, su programa debe ejecutarse sin problemas. Sin embargo lo
más importante de este proceso es que la próxima vez que en un programa
necesitemos realizar el cálculo del factorial ya no tendremos que volver a escribirla,
solo la usaremos como usamos la función printf.
Reutilización de código
Ahora veremos cómo podemos implementar un proyecto que requiera el cálculo del
factorial de un número sin volverlo a escribir. En el siguiente ejemplo queremos
9/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
desarrollar un proyecto en el que se calcule las combinaciones de “n” elementos
tomados de “p” en “p” sin repetición, para esto debemos emplear la siguiente formula:
𝑛𝑛 𝑛𝑛!
𝐶𝐶𝑝𝑝𝑛𝑛 = �𝑝𝑝� =
𝑝𝑝! ∙ (𝑛𝑛 − 𝑝𝑝)!
Creemos entonces en NetBeans el proyecto “CalculoDeCombinaciones”. Luego, antes
de comenzar a escribir el código del programa, debemos copiar, en la carpeta que
contiene nuestro nuevo proyecto, el archivo de cabecera (.h) y de implementación (.c)
donde están la definición e implementación de nuestra función factorial que se
encuentra en la carpeta del proyecto anterior. La carpeta debe quedar como se
muestra a continuación:
Luego debemos ligar al proyecto estos archivo, para esto nos dirigimos al NetBeans y
en la ventana izquierda haremos algo similar a lo que hicimos en el anterior proyecto
pero en el menú que se despliega elegiremos la opción “Add Existing Item…”, y en la
ventana que aparece seleccionamos el archivo “FuncionesEstadistica.h” como se
aprecia a continuación:
10/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Una vez hecho esto, podemos escribir y ejecutar nuestro programa de combinaciones
solo escribiendo código en la función main como se muestra a continuación:
Tipos de variables
Variables Globales: Una variable global es aquella variable que ha sido declarada fuera
del ámbito de cualquier función, tienen como característica principal que, dependiendo
de la ubicación de su declaración, pueden ser utilizadas dentro del código de las
funciones del programa.
Variables Locales: Las variables locales, son variables que se definen dentro de una
función, estas variables se crean en el instante en que se empieza a ejecutar la
función y se destruyen cuando esta termina. Si la función es invocada varias veces en
un programa, las variables locales declaradas en él, se crearán y destruirán tantas
veces como la función sea invocada.
Esta característica hace que las variables definidas como locales, no puedan ser
utilizadas por otra función, incluso dos funciones podrían definir sus propias variables
locales y emplear los mismos nombres sin que se afecten unas contra otras.
11/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Variables Estáticas: Las variables estáticas son variables que, como las variables
locales, sólo se pueden emplear en la función que la declaró, pero a diferencia de éstas
últimas no se destruyen cuando la función es nuevamente invocada, manteniendo el
valor que tenía cuando termino la ejecución la función la vez anterior.
La manera de declarar una variable estática es muy simple, ya que sólo hay que
anteponer la palabra “static” a la declaración de la variable
∞ ∞ Stack (pila)
num fact
2. Luego se lee un valor para la variable num, colocándose ese valor en la posición de
memoria asignada a esa variable, supongamos que se lee el valor de 5. Entonces:
Heap (montón)
5 ∞ Stack (pila)
num fact
12/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
Heap (montón)
5 5 ∞ Stack (pila)
num fact
4. Luego el sistema entrega el control a la función factorial, en ese momento el
sistema relaciona el nombre del parámetro “x” con el espacio de memoria donde se
encuentra el valor enviado, como se aprecia a continuación:
Heap (montón)
x
5 5 ∞ Stack (pila)
num fact
Heap (montón)
2. Esta dirección de memoria solo puede ser manejado por una variable puntero, por
eso en la implementación de la función f, el parámetro se define como int *x. Siendo
x un puntero y teniendo como valor la dirección de la variable num, la variable
referenciada por x (*x) coincidirá con la variable num, como se aprecia a
continuación:
x *x Heap (montón)
x *x Heap (montón)
Finalmente, el programa deberá imprimir las facturas de una manera similar a lo que
se muestra a continuación:
No. De Factura: 10001 Fecha: 23/01/2018
==================================================================
DNI del cliente: 81300045 Ciudad: 005 Teléfono: 445566
------------------------------------------------------------------
Código Precio Unitario Cantidad Subtotal
------------------------------------------------------------------
123453 25.40 4 101.60
545454 3.80 2 7.60
101022 156.30 1 156.30
Total: 265.50
==================================================================
No. De Factura: 10002 Fecha: 01/02/2018
…
Solución: Al tratarse de un problema complejo. La solución debe plantearse empleando
un diseño descendente, modulando el programa mediante funciones. El aplicar el diseño
descendente nos permitirá ir encontrando la solución en el camino e ir escribiendo el
código del programa aun así no tengamos una idea muy clara de cada detalle de la
solución completa.
Primero analicemos a grandes rasgos las tareas que hay que realizar para elaborar el
problema; por un lado se tendrá que manejar esos archivos descritos en el problema,
por lo que habrá que definir las correspondientes variables de archivo. Luego una
tarea que debemos hacer es preparar los archivos (asignarlos, abrirlos, verificar su
apertura) para que podamos trabajar con ellos, luego que los archivos estén abiertos
habrá que procesar los datos para conseguir la impresión de las facturas. Finalmente
se deberá cerrar esos archivos.
Entonces escribamos estas tareas como primer paso al desarrollo del problema:
#include <stdio.h>
#include <stdlib.h>
return (EXIT_SUCCESS);
}
Analicemos las tareas, la primera es una tarea trivial y repetitiva y, por razones que
se estudiarán más adelante, no tenemos por ahora herramientas para poder manejarla
en una función por lo que tendremos que implementarla en la función main. Las otras
15/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
tareas si las podremos implementar con funciones. Por lo que el programa lo podemos
modificar de la forma siguiente:
#include <stdio.h>
#include <stdlib.h>
archRep = fopen("Reporte.txt","w");
if (archRep == NULL){
printf("ERROR: No se pudo abrir el archivo Reporte.txt\n");
exit (1);
}
// Imprimir facturas
imprimirFacturas(archPro, archCli, archFac, archRep);
// Cerrar archivos
cerrarArchivos(archPro, archCli, archFac, archRep);
return (EXIT_SUCCESS);
}
Luego de haber escrito este código podríamos decir que hemos concluido la
elaboración de la función main, ya no requerimos agregarle algo más. Sin embargo
como hemos visto anteriormente, aun no hemos terminado con el proyecto, tenemos
que declarar e implementar las funciones emitirFacturas y cerrarArchivos. Lo
importante de esto es que hay que observar que, dejando de lado la declaración y
apertura de los archivos, el código de la función main es muy simple y fácil de
entender.
Una buena práctica de programación es la de procurar desarrollar funciones de poca
extensión de modo que se pueda entender fácilmente. Una función que tenga más de
25 o 30 líneas de código es sospechosa de no haber sido analizada correctamente, que
es ineficiente y hasta que puede contener errores.
16/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
*/
#ifndef FUNCIONESAUXILIARES_H
#define FUNCIONESAUXILIARES_H
#endif /* FUNCIONESAUXILIARES_H */
while (1){
// Procesar una factura
}
Los datos de una factura se pueden dividir en tres partes, los datos de la factura
propiamente dicho (numero de factura y fecha), los datos del cliente (solo el DNI) y
los datos de productos que compró (códigos y cantidades).
Si observamos el reporte final, apreciaremos que con los primeros datos podemos
imprimir la primera línea de la factura, con el DNI de cliente podemos buscar los
datos que nos faltan del cliente en el archivo de clientes, sin embargo esta tarea
puede ser algo compleja y como por ahora no sabemos cómo hacerlo, simplemente lo
anotaremos como una tarea pendiente. El procesamiento de los productos también
parece ser complicado por lo que haremos lo mismo que con la búsqueda, sin embargo
imprimir los subtítulos se puede ir haciendo.
De acuerdo a esto, la función emitir facturas se puede implementar de la siguiente
manera:
#define MAX_CAR_LIN 70
#endif /* FUNCIONESAUXILIARES_H */
19/19