[go: up one dir, main page]

0% encontró este documento útil (0 votos)
141 vistas19 páginas

04 Programacion Modular

1) El documento describe el método de programación modular para resolver problemas complejos dividiéndolos en tareas más simples. 2) Cada tarea puede resolverse de forma independiente y los módulos resultantes se combinan para resolver el problema original. 3) Esto permite trabajar en equipos y reutilizar código de manera más eficiente.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
141 vistas19 páginas

04 Programacion Modular

1) El documento describe el método de programación modular para resolver problemas complejos dividiéndolos en tareas más simples. 2) Cada tarea puede resolverse de forma independiente y los módulos resultantes se combinan para resolver el problema original. 3) Esto permite trabajar en equipos y reutilizar código de manera más eficiente.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 19

PROGRAMACIÓN MODULAR

Si nos plantearan el siguiente problema: “Emitir un reporte en el que aparezcan los


alumnos de un curso de acuerdo con el promedio obtenido, de mayor a menos nota”,
quizá la primera sensación que tengamos al leer el problema y saber que lo tenemos
que resolverlo se parezca mucho a la figura siguiente:

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

Sin embargo si luego de un análisis comprendemos que el problema es un conjunto de


tareas, la situación puede cambiar, esto debido a que cada tarea puede ser
considerada como un problema independiente y por lo tanto estamos ahora ya no ante
un problema muy grande, sino ante varios problemas más sencillos.

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

Con esto Si la misma técnica se aplicase a cada tarea se podría descomponer el


problema en partes mucho más pequeñas o simples que finalmente nos permita
resolver el problema inicial.
8
3 7
6
5
4

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.

Implementación de la programación modular


Los diferentes lenguajes de programación modernos permiten implementar programas
haciendo uso de la técnica de programación modular. Las unidades básicas que nos
brindan los lenguajes de programación para este fin se denominan “funciones”
Una función se puede definir como un conjunto de instrucciones que tiene como
finalidad realizar un proceso y eventualmente obtener un valor resultante. Si nosotros
vemos dentro la biblioteca de funciones stdio.h, podemos encontrar que por ejemplo
que la instrucción: printf(“A = %4d\n”, a); está formada por una función (printf) que
realizará el proceso de coger el contenido de la variable a, convertirlo en una cadena
de texto y llevarlo al medio estándar de salida. Esta función devuelve la cantidad de
caracteres enviado al medio estándar de salida, valor que puede o no ser aprovechado
por el usuario.
Declaración, implementación y uso de funciones
Una función se declara de la siguiente manera:
<Tipo de retorno> Nombre de la función (<Parámetros (Tipos)>);

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.

La implementación de la función consiste en desarrollar el código necesario para poder


realizar el proceso o el cálculo del resultado esperado por la función. Una función se
implementa de la siguiente manera:
<Tipo de retorno> Nombre de la función (<Parámetros (Tipos)>){
<Instrucción>
<Instrucción>

return expresión;
}

Ejemplos de implementación de funciones se aprecia a continuación:

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:

Inmediatamente aparecerá una ventana, en ella, en el recuadro “File Name”


colocaremos un nombre al archivo, este no debe ser el nombre de la función porque,
como veremos luego no solo va a contener la definición de la función “factorial” sino
todas las que queramos, por ejemplo podríamos ponerle “FuncionesEstadisticas”. Como
vemos a continuación:

Luego presionamos el botón “Terminar” con lo que podrá observar lo siguiente:

6/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.

Como puede ver se ha creado el archivo “FuncionesEstadisticas.h”. Usted puede


borrar la zona marcada en azul porque no la usaremos en este curso.
Luego crearemos el archivo donde colocaremos la implementación de nuestra función
“factorial” (todas que queremos re utilizar). Para esto repetiremos la creación del
archivo como lo hicimos con el ”Header Files” pero ahora con el “Source Files”, como
se ve en la figura siguiente:

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.

Finalmente escribiremos el código correspondiente, en este caso en el archivo de


cabeceras, colocaremos el encabezado de la función factorial, como se ve a
continuación:

En el archivo fuente “FuncionesEstadisticas.c” escribiremos la implementación de la


función, como se muestra a continuación:

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:

De la misma manera lo hacemos con el archivo que contiene la implementación de la


función factorial, como se ve 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

Parámetros de una función


Al igual que se requieren que se ingrese información a los programas para que estos se
puedan ejecutar de acuerdo a las necesidades del momento, en la mayoría de los casos
se requiere introducir información a las funciones para que estas puedan realizar su
trabajo. Piense en la función printf o pow, ambas requieren de datos para realizar su
trabajo, printf requiere de la cadena de formato y de las variables que se quieren
imprimir, pow eleva un número a una potencia, por lo que se necesita de los dos valores
para obtener el resultado esperado. Esta información, que es introducida a las
funciones se denomina parámetros o argumentos.
Parámetros por valor: el lenguaje C solo define un tipo de parámetro, este tipo se
denomina parámetro por valor. La característica principal de este tipo de parámetro
es que, pase lo que pase dentro de la función, el valor de la variable que se use como
parámetro no será alterado.
Para entender esto, analicemos el siguiente programa:
#include <stdio.h> // FuncionesEstadisticas.h
#include <stdlib.h>
#include "FuncionesEstadisticas.h"
int factorial (int x){
int main(int argc, char** argv) { int f=1;
int num, fact; for(int n = x; n>0; n--){
printf("Ingrese un numero: "); f *= n;
scanf("%d", &num); }
fact = factorial(num); return f;
printf("El Factorial de %d es: %d\n", num, fact); }
return (EXIT_SUCCESS);
}

El programa se ejecuta de la siguiente manera:


1. La ejecución de la función main empieza declarando las variables num y fact. Estas
variables son colocadas, sin inicializar, en la pila del segmento de datos, como se
muestra a continuación:
Heap (montón)

∞ ∞ 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.

3. Enseguida se llama a la función factorial, el programa toma el valor de la variable


num y lo envía a otra parte de la pila, como se ve a continuación:

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

5. A partir de allí la función factorial se ejecuta utilizando, cuando se requiera, el


valor asignado a la variable “x”, cualquier modificación que se haga a la variable “x”
no afectará a la variable “num” definida en la función main. Es por eso que al
parámetro se le nombra como parámetro por valor, porque envía el valor de la
variable que se usa como argumento, a la función.
Parámetros por referencia: se denominan parámetros por referencia a aquellos
parámetros que al ser modificados en la función modifican también a la variable
empleada como parámetro. Como indicamos, el lenguaje C no implementa parámetros
por referencia. Sin embargo existe una forma de simular el paso por referencia en el
lenguaje C, y se hace de la misma forma en que lo hace la función scanf, manejando las
direcciones de memoria y punteros. A continuación explicamos este proceso mediante
el siguiente programa:
// FuncionesEstadisticas.h
#include <stdio.h>
#include <stdlib.h>
#include "FuncionesEstadisticas.h" void f (int *x){

int main(int argc, char** argv) { *x =23;


int num;
printf("Ingrese un numero: "); return x;
scanf("%d", &num); }
f (&num);
printf("El valor de num es: %d\n", num);
return (EXIT_SUCCESS);
}
1. Observe que la función “f” el parámetro es colocado como &num al igual que lo hace
la función scanf. Eso quiere decir que en lugar de enviar el contenido de la variable
num, se envío su dirección de memoria, como se ve a continuación:

Heap (montón)

DMnum 5 ∞ Stack (pila)


num fact
13/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.

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)

DMnum 5 ∞ Stack (pila)


num fact

3. Por lo tanto, cualquier asignación que se haga sobre la variable referenciada de x


(*x) se realizará en el espacio de la variable num. Así, como se indica en la
implementación de la función f, al ejecutar la instrucción: *x = 23; la memoria
quedará como sigue:

x *x Heap (montón)

DMnum 23 ∞ Stack (pila)


num fact
4. Finalmente, cuando la función f termine y sea la función main la que tome el control,
el valor que se imprimirá para la variable num será 23.
Solución de problemas empleando diseño descendente y programación modular
A continuación se presenta un problema que ilustra esta metodología.
Se desea escribir un programa en lenguaje C que permita imprimir las facturas que se
han elaborado por las ventas realizadas en una tienda.
Para realizar esta labor, se cuenta con tres archivos de texto con la información
requerida. El primer archivo, denominado “Productos.txt”, contiene la información de
todos los productos que comercializa la tienda. Este archivo, con la finalidad de
simplificar la solución del problema y que así nos podamos concentrar en la
metodología, es similar al que se muestra a continuación:
Productos.txt
123456 1199.90
345678 345.50
626262 689.99

En este archivo se encuentra en cada línea el código del producto y su precio unitario.
El segundo archivo, denominado “Clientes.txt”, contiene la información de todos los
clientes que han comprado alguna vez algún producto en la tienda. De manera similar
que en el archivo anterior, el archivo es parecido al que se muestra a continuación:
Clientes.txt
23764590 25 77665544
45667728 01 12345678
81300045 01 87695511

En este archivo se encuentra en cada línea, el DNI del cliente, el código de la ciudad
donde vive y su teléfono.
14/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
El tercer archivo contiene la información de las facturas que se han emitido en la
tienda en un periodo de tiempo. El archivo contiene en cada línea la información
necesaria para poder elaborar cada factura, esto es: el número de factura, la fecha en
que se emitió, el DNI del cliente que hizo la compra, y la lista de productos
comprados, aquí sólo se consigna el código y la cantidad comprada de cada producto,
pudiendo haber muchos productos en una línea. El archivo es similar al que se muestra
a continuación:
Facturas.txt
10001 23/1/2018 81300045 445566 5 890988 10…
10002 1/2/2018 23764590 345678 3
10036 5/2/2108 11223344 626262 1 445566 21 …

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>

int main(int argc, char** argv) {


// Preparar archivos
// Imprimir facturas
// Cerrar archivos

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>

int main(int argc, char** argv) {


// Preparar archivos
FILE *archPro, *archCli, *archFac, *archRep;
archPro = fopen("Productos.txt","r");
if (archPro == NULL){
printf("ERROR: No se pudo abrir el archivo Productos.txt\n");
exit (1);
}
archCli = fopen("Clientes.txt","r");
if (archCli == NULL){
printf("ERROR: No se pudo abrir el archivo Clientes.txt\n");
exit (1);
}
archFac = fopen("Facturas.txt","r");
if (archFac == NULL){
printf("ERROR: No se pudo abrir el archivo Facturas.txt\n");
exit (1);
}

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.

Declaración e implementación de las funciones: imprimirFacturas y


cerrarArchivos.
Como las dos funciones son propias del problema que estamos resolviendo y
difícilmente pueda ser reutilizado en otros programas, definiremos una sola biblioteca
de funciones para este fin. Con la metodología explicada anteriormente definiremos
los archivos “FuncionesAuxiliares.h” y “FuncionesAuxiliares.c”, el primero con el
siguiente código:
/*
* Archivo: FuncionesAuxiliares.h
* Autor: J. Miguel Guanira E.
*
* Creado el 16 de septiembre de 2018, 02:01 PM

16/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
*/

#ifndef FUNCIONESAUXILIARES_H
#define FUNCIONESAUXILIARES_H

void imprimirFacturas (FILE*, FILE*, FILE*, FILE*);


void cerrarArchivos(FILE*, FILE*, FILE*, FILE*);

#endif /* FUNCIONESAUXILIARES_H */

Para la implementación de las funciones, recuerde que cada una es independiente de la


otra, por lo que el orden en que se implementes puede ser cualquiera. Por mi parte, yo
empezaría por el más sencillo, que es la función que cerrará los archivos la cual
presentamos a continuación:
void cerrarArchivos (FILE *archPro, FILE *archCli,
FILE *archFac, FILE *archRep){
fclose(archPro);
fclose(archCli);
fclose(archFac);
fclose(archRep);
}

La función imprimirFacturas es mucho más complicada, primero debemos decidir por


qué archivo empezaremos la tarea, esto es muy importante, de no hacerlo
correctamente no llegaremos a solucionar el problema. En este caso, si queremos
emitir las facturas una a una, debemos empezar precisamente por el archivo de
facturas. La información que tiene este archivo, si es cierto que no está completa,
registra los DNI y códigos de los productos en cada factura, por lo que con esos datos
podemos buscar en los otros archivos lo que nos falta. Sin embargo si comenzamos por
el archivo de clientes, tendremos el problema que allí solo tenemos la información de
cada cliente, no hay allí relación con los otros, lo mismo pasa con el archivo de
productos.
Entonces empezaremos por el archivo de facturas. Como en ese archivo los datos de
cada factura están todos en una línea, la función deberá implementar un ciclo iterativo
en el que se en cada ciclo se procese una factura. El esquema será similar al siguiente:

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

void imprimirFacturas (FILE *archPro, FILE *archCli,


17/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
FILE *archFac, FILE *archRep) {
// Leemos en cada línea, los datos de la factura

int numFact, dd, mm, aa, dniCli, ciudad, telefono;


while(1){
fscanf(archFac,"%d %d/%d/%d %d", &numFact, &dd, &mm, &aa, &dniCli);
if(feof(archFac)) break;
// Buscamos los datos faltantes del cliente
buscarCliente(dniCli, archCli, &ciudad, &telefono);
fprintf (archRep, "No. De Factura: %d %20c Fecha: %02d/%02d/%04d\n",
numFact, ' ',dd, mm, aa);
imprimeLinea('=', MAX_CAR_LIN, archRep);
fprintf(archRep,
"DNI del cliente: %d Ciudad: %03d Telefono: %d\n",
dniCli, ciudad, telefono);
imprimeLinea('-', MAX_CAR_LIN, archRep);
fprintf(archRep,
"Codigo Precio unitario Cantidad Subtotal\n");
imprimeLinea('-', MAX_CAR_LIN, archRep);
imprimeProductos(archFac, archPro, archRep);
imprimeLinea('=',MAX_CAR_LIN,archRep);
}
}
(*) La impresión de los datos iniciales de la factura la podemos hacer en una función aparte.

Las funciones pendientes deberán declararse en el archivo FuncionesAuxiliares.h así:


/*
#ifndef FUNCIONESAUXILIARES_H
#define FUNCIONESAUXILIARES_H

void emitirFacturas(FILE*, FILE*, FILE*, FILE*);


void cerrarArchivos(FILE*, FILE*, FILE*, FILE*);
void buscarCliente(int, FILE*,int*,int*);
void imprimeLinea(char, int, FILE*);
void imprimeProductos(FILE*,FILE*,FILE*);

#endif /* FUNCIONESAUXILIARES_H */

La función imprimeLinea es la más sencilla por lo empezaremos por allí:


void imprimeLinea(char car ,int numCar, FILE *archRep){
for (int c=0; c<numCar; c++)
fputc(car, archRep);
fputc('\n', archRep);
}
Ahora analicemos lo que debemos hacer para buscar un cliente en el archivo. Para
hacer esto debemos recorrer línea por línea el archivo de clientes, hasta encontrar el
cliente cuyo DNI coincida con el que buscamos. Para hacer esto, primero debemos
asegurarnos que el proceso empiece en la primera línea del archivo. Para esto
usaremos una función definida en stdio.h denominada rewind. Luego debemos tomar en
cuenta que a penas encontremos al cliente debemos interrumpir inmediatamente el
proceso iterativo y devolver los datos que nos faltan. Finalmente, y esta es una tarea
obligatoria en una búsqueda, debemos verificar que si llegamos al fin del archivo es
porque no encontramos el DNI del cliente en el archivo, motivo por el cual debemos
devolver un dato que sirva al usuario para saber que no encontramos lo que buscamos.
El código de la función lo mostramos a continuación:
void buscarCliente(int dniCli, FILE *archCli,
int *ciudad, int *telefono){
int dni;
rewind(archCli); // Nos colocamos al inicio del archivo
while(1){
fscanf(archCli, "%d %d %d", &dni, ciudad, telefono);
if(feof(archCli)){
*ciudad=*telefono=-1; // Indica que no se encontró el cliente
break;
}
18/19
Curso: Técnicas de programación [INF144] J. Miguel Guanira E.
if(dniCli == dni)break; //La ciudad y el telefono tienen los
// datos buscados
}
}
En la función imprime producto, debemos desarrollar un ciclo iterativo en el que en
cada ciclo leamos el código y la cantidad comprada de un producto, buscar el precio
del producto, calcular el subtotal, acumular el total e imprimir todos esos datos. El
código será el siguiente:
void imprimeProductos(FILE *archFac, FILE *archPro, FILE*archRep){
// Leemos uno por uno el código y cantidad de cada uno
// de los artículos que compró
int codProd, cant;
double precio, subtotal, total=0.0;
while(1){
fscanf(archFac, "%d %d", &codProd, &cant);
precio = buscarProducto(codProd, archPro);
subtotal = cant*precio;
fprintf(archRep, "%d %15.2lf %15d %15.2lf\n",
codProd, precio, cant, subtotal);
total += subtotal;
if (fgetc(archFac)=='\n')break;
}
fprintf(archRep," Total: %10.2lf\n", total);
}
La función buscarProducto es parecida a la que busca el cliente, pero como esta solo
devuelve un dato (el precio), este valor no lo devolveremos por la vía de los parámetros
sino por la de la orden return.
double buscarProducto(int codProd, FILE *archPro){
int cod;
double precio;
rewind(archPro);
while(1){
fscanf(archPro, "%d %lf", &cod, &precio);
if(feof(archPro)){
precio=-1.0;
break;
}
if(codProd == cod)break;
}
return precio;
}
No olvidar colocar el encabezado de esta función en el archivo .h

19/19

También podría gustarte