REUTILIZACIÓN DE CÓDIGO: utiles.h y utiles.c




El problema
El desarrollo de aplicaciones es una tarea compleja, y por tanto costosa en términos de tiempo y esfuerzo. Por tanto, se intenta eliminar las tareas que se repiten en múltiples ocasiones, o al menos se intenta reducir el tiempo necesario para realizarlas.
De estas tareas que se repiten una y otra vez, la construcción de una interfaz de usuario para el programa es una de las más laboriosas. Por tanto, es interesante disponer de un "esqueleto" de aplicación que resulte fácil de adaptar a las condiciones específicas de la aplicación que se quiera desarrollar. Este esqueleto será necesariamente abstracto, y dispondrá de los elementos necesarios para mostrar distintas opciones, tomar las instrucciones del usuario e invocar los fragmentos de código correspondientes. Idealmente, el esqueleto de programa desarrollado deberá funcionar sin cambios en todas las plafatormas que constituyan el mercado de nuestra aplicación.

Solución propuesta
Se utilizará como base el concepto de menú (una colección de opciones que se invocan mediante órdenes introducidas a través del teclado).

¿Cómo es este programa?
Existen dos variantes, de las cuales se muestra aquí la primera, por ser más sencilla. Se trata de un programa relativamente "monolítico", formado por un bucle while dentro del cual se muestra el menú, se toma una opción y se ejecuta la función correspondiente mediante una sentencia switch() .

Ciertamente, la aplicación se va a construir haciendo uso de utiles.c El objetivo que perseguimos es crear un código fuente que pueda compilarse tanto en un entorno Unix (Linux, Mac OS X) como en un entorno Windows. Esto exige hacer uso de directrices de compilación condicional adaptadas a los entornos de destino. En nuestro caso, utilizaremos el compilador GNU gcc en ambos entornos, lo cual facilita las cosas sin llegar a ofrecer una total compatibilidad. Véase el contenido del archivo utiles.h (la última versión está siempre disponible en la página de Programación ):

#include<stdio.h>
#include <stdlib.h>
#include <string.h>
/*
	Latest change: 2009 09 04
*/

/*
	Si se va a compilar el programa para Windows,
	eliminar el comentario (//) en la línea siguiente
*/

//#define __WINDOWS__

#ifndef __UTILES__
	#define __UTILES__

	typedef char     * charRef;
	typedef double   * doubleRef;
	typedef FILE     * FILERef;
	typedef float    * floatRef;
	typedef int      * intRef;

	charRef		pedir(charRef);
	charRef		pedir_nv(charRef);
	charRef		sgets(void);
	double		pedirDouble(charRef,double,double);
	float		pedirFloat(charRef,float,float);
	int			pedirInt(charRef,int,int);
	int 		confirmar(const charRef indicacion);
	void		spause(void);
	void		leer_cadena(charRef, int);

	#ifdef __WINDOWS__


		#define BORRAR "cls"

	#else


		#define BORRAR "clear"
		#include<termios.h>
		char getch(void);

	#endif

#endif
La constante __WINDOWS__ se ha creado con objeto de identificar el entorno de desarrollo utilizado en Windows, y es preciso declararla ( q.v. ) o bien se puede indicar su definición al compilador empleando la directriz -D__WINDOWS__ . Si se estudia el color de las compilacione condicionales, se observará que en color verde se tienen las definiciones comunes para todas las plataformas . En rojo están marcadas las definiciones que aporta utiles.c en el entorno Windows™, y en azul se muestran adiciones para el entorno Unix.
En cuanto al contenido del archivo utiles.c , tiene el aspecto siguiente:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>
#include "utiles.h"
/*
	Last modified: 2009 09 04
*/

/*
	Si se va a compilar el programa para Windows,
	eliminar el comentario (//) en la línea siguiente.

	También se puede emplear la directriz _D__WINDOWS__ en en la
	compilación:

	gcc _D__WINDOWS__ ...
*/

//#define __WINDOWS__

/*
    sgets() has different implementations in Unix and elsewhere,
    hence our first step is to determine just what the
    underlying OS is
*/

#ifdef __WINDOWS__
/*
    This is not Unix, hence no fgetln() and sgets()
    is somewhat less safe. We hope the user
    will not input more than 1024 characteres at a time.
*/

	charRef sgets(void)
	{
		int MAXCHARS = 1024;
		charRef temp = (charRef)malloc(MAXCHARS);
		charRef resultado;
		fgets(temp,MAXCHARS,stdin);
		resultado = (charRef)malloc(strlen(temp));
		strcpy(resultado, temp);
		resultado[strlen(resultado)-1] = '\0';
		free(temp);
		fflush(stdin); /* No leftovers */
		return resultado;
	}


#else
	/*
		This is Unix, hence fgetln() is available but getch() is not.
		We produce a reasonable implementation of sgets() and
		of getch()
	*/

	charRef sgets(void)
	{
		charRef puntero_linea;
		size_t longitud_linea;
		charRef resultado;
		puntero_linea = fgetln(stdin, &longitud_linea);/* No leftovers */
		resultado = (charRef)malloc(longitud_linea+2);
		memcpy(resultado,puntero_linea,longitud_linea);
		resultado[longitud_linea-1] = '\0';
		return resultado;
	}


	char getch(void) {
		struct termios conf_vieja, conf_nueva;
		char c;
		tcgetattr(0,&conf_vieja);
		conf_nueva = conf_vieja;
		conf_nueva.c_lflag &= (~ICANON);
		conf_nueva.c_cc[VTIME] = 0;

		conf_nueva.c_cc[VMIN] = 1;
		tcsetattr(0,TCSANOW,&conf_nueva);
		c = getchar();
		tcsetattr(0,TCSANOW,&conf_vieja);
		return c;
	}

#endif

/*
    Once we have sgets() and getch() at our disposal, we build
    further useful functions like pedir (ask) and confirmar
    (confirm). All these functions are shared between platforms.
*/

charRef pedir(const charRef indicacion)
{
	charRef resultado;
	printf("%s ", indicacion);
	resultado = sgets();
	return resultado;
}

charRef pedir_nv(const charRef indicacion)
{
	charRef resultado;
	int longitud;
	do
		{
			printf("%s ", indicacion);
			resultado = sgets();
			longitud = strlen(resultado);
			if(0 == longitud)
				printf("\nPerdón, no se admite una respuesta en blanco.\n");
		} while (0 == longitud);
	return resultado;
}

int pedirInt(charRef indicacion,int limInf,int limSup)
{
	charRef linea = NULL;
	charRef temp  = NULL;
	int numero;
	do
		{
			numero = limInf-1;
			printf("(%d <= x <= %d) ",limInf,limSup);
			linea = pedir(indicacion);
			numero = strtol(linea,&temp, 10);
			free(linea);
		} while(linea == temp || numero < limInf || numero > limSup);
	return numero;
}

float pedirFloat(charRef indicacion,float limInf,float limSup)
{
	charRef linea = NULL;
	charRef temp  = NULL;
	float numero;
	do
		{
			numero = limInf -1.0;
			printf("(%.2f <= x <= %.2f) ",limInf,limSup);
			linea = pedir(indicacion);
			numero = strtof(linea,&temp);
			free(linea);
		} while(linea == temp || numero < limInf || numero > limSup);
	return numero;
}

double pedirDouble(charRef indicacion,double limInf,double limSup)
{
	charRef linea = NULL;
	charRef temp  = NULL;
	double numero;
	do
		{
			numero = limInf -1.0;
			printf("(%.2f <= x <= %.2f) ",limInf,limSup);
			linea = pedir(indicacion);
			numero = strtof(linea,&temp);
			free(linea);
		} while(linea == temp || numero < limInf || numero > limSup);
	return numero;
}

void spause(void)
{
	charRef temp;
	printf("\n\nPulse <INTRO> para continuar\n\n");
	temp = sgets();
	free(temp); /* No leaks! */
	return;
}

int confirmar(charRef indicacion)
{
	charRef respuesta;
	int resultado;
	printf("%s (s/n): ", indicacion);
	respuesta = sgets();
	resultado = toupper(respuesta[0]) == 'S';
	free(respuesta); /* No leaks! */
	return resultado;
}


Comentarios .- Si se estudia la estructura de compilaciones condicionales, se observará que sgets() se ha construido de forma diferente para Unix y Windows, debido a que en Windows no existe la función fgetln() . Se observará también que en Unix se ha creado la función getch() , que no existía originalmente. Las funciones pedir() , spause() y confirmar() , comunes para todas las plataformas, se han creado basándose en sgets() , para todas las plataformas. El esquema de colores es el mismo que se ha empleado para describir utiles.h .

Una vez descritas las herramientas que se van a utilizar, podemos pasar a estudiar una arquitectura de programa muy sencilla en la página siguiente .