REUTILIZACIÓN DE CÓDIGO: una arquitectura sencilla
Se va a mostrar en primer lugar una arquitectura de programa que muestra un menú y va admitiendo opciones del usuario hasta que este pulsa la tecla de salida ('Q'). El programa hace uso de
utiles.c
, que se ha descrito detalladamente en la
página anterior
.
Toda la información que manejan las funciones del programa se halla en un bloque señalado por
g
, que es una variable de tipo
GlobRef
(un puntero de una estructura de tipo
Globales
). La estructura está definida en el archivo
tipos.h
. De este modo, el archivo
main.c
actúa como distribuidor: admite opciones y ejecuta las opciones asociadas (en el ejemplo, si se pulsa H aparece la ayuda, y si se pulsa S el programa concluye).
El programa básico hace uso de las funciones siguientes:
-
arranque()
-
parada()
-
ayuda()
-
salir()
Las tres primeras funciones están definidas en
cda.h
y
cda.c
. La función
salir()
está definida en
main.c
. Con esto concluye la relación de funciones empleadas por nuestra arquitectura: es sencilla, aunque no sofisticada porque la adaptación a una funcionalidad concreta requiere muchos cambios (algunos delicados) en el programa principal
main.c
.
Para dar una funcionalidad real a este programa, que no hace nada, pero lo hace muy bien, es preciso añadir al archivo
tipos.h>
otros archivos de encabezado. Estos archivos contienen funciones que se usan como herramientas (primitivas en la jerga informática).
Nuestro programa siempre va a utilizar funciones que reciben como único argumento la variable
g
y son de tipo
void
.
Programa básico
main.c
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tipos.h"
#include "cda.h"
void salir(GlobRef g);
int main(int argc, char * argv[])
{
GlobRef g = malloc(sizeof(Globales));
char respuesta;
/* *** El orden de las opciones y de las funciones debe coincidir!!! *** */
charRef menu = "(H) Ayuda (S) Salir";
charRef opciones = "HS";
funRef funciones[] = { ayuda, salir, NULL };
arranque(g, argc, argv);
do
{
printf("%s ",menu);
respuesta = toupper(abs(getch()));
switch(respuesta) {
case 'H': funciones[0](g); break;
case 'S': funciones[1](g); break;
default: printf("\n\n\7***Opción incorrecta\n\n"); break;
}
} while(!g->salirDePrograma);
parada(g);
printf("\n\nTerminación normal\n\n");
return 0;
}
void salir(GlobRef g)
{
g->salirDePrograma = confirmar("¿Desea realmente salir del programa? ");
}
|
tipos.h
|
#include <coti/utiles.h>
#ifndef __TIPOS__
#define __TIPOS__
/*
Normalmente, será preciso incluir aquí los archivos de encabezado
que aportan tipos específicos para la funcionalidad deseada.
*/
typedef struct Globales {
int salirDePrograma;
} Globales;
typedef Globales * GlobRef;
typedef void (*funRef)(GlobRef);
#endif
|
cda.h
|
#include <coti/utiles.h>
#include "tipos.h"
void arranque(GlobRef g, int argc, char * argv[]);
void parada (GlobRef g);
void ayuda (GlobRef g);
|
cda.c
|
#include "cda.h"
/*
Constructor, destructor y ayuda
- - -
*/
void arranque(GlobRef g, int argc, charRef argv[])
{
printf("\n\nSe ha ejecutado el procedimiento de arranque.\n\n");
g->salirDePrograma = 0; // No se va a salir en el momento de la entrada */
}
void parada(GlobRef g)
{
printf("\n\nSe ha ejecutado el procedimiento de parada.\n\n");
}
void ayuda(GlobRef g)
{
printf("\n\nEste es el mecanismo de ayuda.\n\n");
}
|
makefile
Para simplificar la compilación se puede utilizar el siguiente archivo
makefile
, que permite crear el archivo de código ejecutable escribiendo simplemente
make
en la línea de órdenes.
#
# makefile general para compilar un programa
#
# Las variables terminadas en 64 deben utilizarse para
# efectuar compilaciones en 64 bits.
#
# Última modificación: 2009 09 06
#
OBJECTS=cda.o main.o
OBJECTS64=cda64.o main64.o
UTILES=/usr/include/coti/utiles.o
UTILES64=/usr/include/utiles64.o
CC=gcc
PROGRAM=program
#PROGRAM=program.exe
RM=rm -rf
#RM=del
program: $(OBJECTS)
$(CC) $(UTILES) $(OBJECTS) -o program
cda.o: cda.c
$(CC) -c cda.c
main.o: main.c
$(CC) -c main.c
clean:
$(RM) *.o
$(RM) $(PROGRAM)
Un ejemplo de programa con esta arquitectura.
Se trata de emplear la arquitectura anterior para construir un programa que haga algo sencillo, como pedir dos números, calcular su suma y mostrar el resultado (empleando la arquitectura propuesta). Se han marcado en
verde
las modificaciones.
main.c
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tipos.h"
#include "cda.h"
void salir(GlobRef g);
int main(int argc, char * argv[])
{
GlobRef g = malloc(sizeof(Globales));
char respuesta;
/* El orden de las opciones y de las funciones debe coincidir !!! */
charRef menu = "(1) Escribir números (2) Calcular"
" (3) Ver resultados (H) Ayuda (S) Salir";
charRef opciones = "123HS";
funRef funciones[] = {leer_datos,calcular,mostrar_informacion,ayuda, salir, NULL};
arranque(g, argc, argv);
do
{
printf("%s ",menu);
respuesta = toupper(abs(getch()));
/* Hay que adaptar el switch. */
switch(respuesta) {
case '1': funciones[0](g); break;
case '2': funciones[1](g); break;
case '3': funciones[2](g); break;
case 'H': funciones[3](g); break;
case 'S': funciones[4](g); break;
default: printf("\n\n\7***Opción incorrecta\n\n"); break;
}
} while(!g->salirDePrograma);
parada(g);
printf("\n\nTerminación normal\n\n");
return 0;
}
void salir(GlobRef g)
{
printf("\n\n");
g->salirDePrograma = confirmar("¿Desea realmente salir del programa? ");
}
|
tipos.h
|
#include <coti/utiles.h>
#ifndef __TIPOS__
#define __TIPOS__
/* Archivos de encabezado adicionales */
#include "funciones.h"
typedef struct Globales {
int primerSumando,segundoSumando,resultado;
int salirDePrograma;
} Globales;
typedef Globales * GlobRef;
typedef void (*funRef)(GlobRef);
#endif
|
cda.h
|
#include <coti/utiles.h>
#include "tipos.h"
void arranque(GlobRef g, int argc, char * argv[]);
void parada (GlobRef g);
void ayuda (GlobRef g);
|
cda.c
|
#include "cda.h"
/* Constructor, destructor y ayuda */
void arranque(GlobRef g, int argc, charRef argv[])
{
printf("\n\nSe ha ejecutado el procedimiento de arranque.\n\n");
g->salirDePrograma = 0; // No se va a salir en el momento de la entrada */
}
void parada(GlobRef g)
{
printf("\n\nSe ha ejecutado el procedimiento de parada.\n\n");
}
void ayuda(GlobRef g)
{
printf("\n\nEste es el mecanismo de ayuda.\n\n");
}
|
funciones.h
|
#include "tipos.h"
void leer_datos(GlobRef);
void calcular(GlobRef);
void mostrar_informacion(GlobRef);
|
funciones.c
|
#include "funciones.h"
void leer_datos(GlobRef g)
{
printf("\n\n");
g->primerSumando = pedirInt("Escriba el primer número : ",-2147000000,2147000000);
g->segundoSumando = pedirInt("Escriba el segundo número : ",-2147000000,2147000000);
printf("\n\n");
}
void calcular(GlobRef g)
{
g->resultado = g->primerSumando + g->segundoSumando;
printf("\n\nCálculos efectuados\n\n");
}
void mostrar_informacion(GlobRef g)
{
printf("\n\nLa suma de %d y %d es %d\n\n",
g->primerSumando,
g->segundoSumando,
g->resultado);
}
|
Conclusiones
La adaptación de esta arquitectura es relativamente sencilla:
-
Se añaden las variables necesarias (
primerSumando
,
segundoSumando
y
resultado
) a la estructura
Globales
. Dicho de otro modo, se añaden a
Globales
las estructuras de datos necesarias para el funcionamiento de la aplicación. Este paso es fundamental: si no se crean unas estructuras de datos, no se puede efectuar el tratamiento de información.
-
Se añade en
tipos.h
el archivo de encabezado
funciones.h
. Si hubiera más archivos de encabezado, se añaden también aquí.
-
Se modifican las variables
menu
,
opciones
y
funciones
. Obsérvese que las opciones
"123HS"
tienen el mismo orden que las funciones asociadas
{ leer_datos, calcular, mostrar_informacion, ayuda, salir }
. No hay que olvidar el
NULL
que marca el final de la lista de funciones. Nota: ciertamente, se podrían escribir directamente los nombres de las funciones en el
switch()
. La razón por la cual almacenamos los punteros de función en una lista se comprenderá en la próxima arquitectura de programa que se estudia.
-
Se modifica por completo el contenido de la sentencia
switch()
. Concretamente, se hace lo siguiente:
-
A la opción situada en primer lugar dentro de
opciones
, que es '1', se le asocia la primera función (la de índice 0)
presente en
funciones
.
-
A la opción situada en segundo lugar dentro de
opciones
, que es '2', se le asocia la segunda función (la de índice 1) presente en
funciones
.
-
A la opción situada en tercer lugar dentro de
opciones
, que es '3', se le asocia la tercera función (la de índice 2) presente en
funciones
.
-
A la opción situada en cuarto lugar dentro de
opciones
, que es 'H', se le asocia la cuarta función (la de índice 3) presente en
funciones
.
-
A la opción situada en quinto lugar dentro de
opciones
, que es 'S', se le asocia la quinta función (la de índice 4) presente en
funciones
.
-
Basándose en los nombres de funciones que se han utilizado al principio del programa, se crean los archivos
funciones.h
y
funciones.c
, que aportan la funcionalidad deseada. En las funciones, que reciben todas ellas la variable g, se efectuarán posiblemente llamadas a subprogramas pertenecientes a otras bibliotecas predefinidas o creadas para este programa en concreto.
El principal problema que plantea esta arquitectura plantea es la complejidad asociada a modificar la sentencia
switch()
. Una arquitectura ideal solo precisaría modificar el menú, las opciones y la lista de funciones asociadas.
Ejercicios
-
Tomando como base el makefile propuesto, crear dos versiones llamadas
makefile_unix
y
makefile_windows
. Cada una debe servir únicamente para la plataforma correspondiente. Comprobar que ambas funcionan.
-
Crear una