ENUMERACIONES EN C







Concepto de Enumeración.Utilidad. Estructura interna.
El concepto de "número mágico" en programación suele ilustrarse con el ejemplo siguiente. Consideremos una compañía con 12 sucursales, 12 presidentes (uno en cada sucursal), 12 vicepresidentes, 12 conciertos administrativos y 12 bares en las 12 sucursales. Entonces se produce una reorganización y pasamos a tener 13 sucursales, 9 presidentes, 11 vicepresidentes, 42 bares, 7 conciertos administrativos y 12 papeleras por establecimiento. Como es natural, nuestro programa de contabilidad contaba con aproximadamente 6423 alusiones al número 12. Si hemos hecho uso del número mágico (el 12), la reorganización exigirá 6423 consideraciones y posiblemente otros tantos cambios. Pero no se puede cambiar en bloque el 12 por 13, 9, 7, 11 ni 42... es imprescindible cambiar manualmente cada caso. Esto supondrá una enorme cantidad de trabajo, y será realmente fácil cometer errores. La solución propuesta consiste en utilizar la instrucción #define con objeto de construir constantes, de forma similar a la siguiente:

#define PRESIDENTES 12
#define	VICEPRESIDENTES 12
#define CONCIERTOS_ADMINISTRATIVOS 12
#define BARES 12

De esta forma, al llegar la reorganización basta con cambiar los valores de estas constantes en un único lugar, y el compilador se encarga de realizar todos los cambios rápidamente y sin errores.
Por otra parte, resulta bastante frecuente que existan colecciones de constantes cuyo valor no importa realmente, por cuanto se utilizan para denotar casos. Considérese, por ejemplo, el estado civil: soltero, casado, viudo, separado, divorciado. El valor asignado a la constante que se utilice para denotar el estado no es, en general, importante. En este sentido, C admite la posibilidad de asignar automáticamente valores enteros a una determinada colección de nombres, con la única condicion de que todos los nombres del grupo sean identificadores válidos. Los valores numéricos asignados al grupo en cuestión son enteros; parten de cero para el primer identificador por orden cronológico y van aumentando de uno en uno. También se admite asignar un valor entero arbitrario a un cierto identificador; los siguientes seguirán por orden ascendente.
La utilidad de las enumeraciones estriba en que es más fácil recordar un nombre que un determinado valor entero, como en el caso de las constantes creadas mediante #define, y además el compilador se encarga de asignar valores.
Internamente, una enumeración es un único byte, interpretado como un número entero.

Enumeraciones Anónimas: definición y declaración
Las enumeraciones anónimas se definen en la forma

enum {lista_de_identificadores};

Los identificadores irán separados mediante comas. Opcionalmente, cualquiera de los identificadores puede recibir un valor inicial entero. Esta definición da lugar a los identificadores incluidos, que toman valores enteros partiendo de cero salvo que se les den otros valores iniciales. Una vez efectuada esta declaración, se pueden emplear los identificadores en nuestro programa; véase un ejemplo:

#include<stdio.h>

enum {pepe, juan};

int main(void)
{
 printf("juan vale %d\n", juan);
 return 0;
}

Como se puede observar, el identificador juan pasa a estar disponible y toma automáticamente el valor 1. ¿Cuánto vale pepe ?

Enumeraciones con nombre: definición y declaración


Es frecuente utilizar enumeraciones con nombre, cuya declaración adopta la forma siguiente:

enum nombre_de_tipo {lista_de_identificadores};


Una vez hecha una definición como esta, se pueden emplear variables del tipo de esta enumeración; estas variables se declaran en la forma:

enum nombre_de_tipo var_1, var_2,..., var_n;

Por ejemplo, en el programa siguiente se crea un nuevo tipo enumerado llamado mis_datos , para luego declarar dos variables x e y de este tipo. El programa da valores a estas variables y los muestra en pantalla:

#include<stdio.h>

enum mis_datos {pepe, juan};
enum mis_datos  x, y;

int main(void)
{
 x = pepe;
 y = juan;
 
 printf("x vale %d\n", x);
 printf("y vale %d\n", y);

 return 0;
}

Comentarios.- ¿Cuánto valen x e y ? ¿Se puede añadir manolo a la lista de valores del tipo mis_datos ? ¿Cuánto vale? ¿Qué ocurre si se hace el cambio siguiente?:

enum mis_datos { pepe=33, juan, manolo};



Lectura y Escritura de Enumeraciones .
Los identificadores que forman parte de enumeraciones no se admiten por teclado ni se pueden escribir directamente en pantalla. El objetivo de las enumeraciones es servir para que el programador comprenda mejor el significado de las constantes. Véase el ejemplo siguiente:
#include <stdio.h>

/*
 Este programa muestra la definición, declaración y utilización
 de tipos enumerados en C, enumeraciones. Las enumeraciones adoptan
 valores internos desde 0 en adelante, salvo indicación expresa
 en contra.
*/

/*
 Una enumeración anónima.
*/

enum {rojizo, verdoso, azulado} una;

/*
 Un tipo enumerado
*/

enum arco_iris {rojo, anaranjado, amarillo, verde, azul, agnil, violeta};

/*
 Una variable de un tipo enumerado
*/

enum arco_iris p;

/*
 Una enumeración con cambios arbitrarios en los valores
*/

enum aleatoria { primero = -7, segundo, tercero, cuarto = 25, quinto, sexto } ale;


void main(void)
{
 printf("E N U M E R A C I O N E S");
 
 /*
  Manejo de enumeraciones anónimas
 */
 
 printf("\n\nEnumeración anónima, enum (rojizo, verdoso, azulado) una;");
 
 printf("\n\nEl tamaño de azulado es %d y su valor es %d", sizeof(azulado), azulado);
 
 una = verdoso;
 
 printf("\n\nEl tamaño de una (= verdoso) es %d y su valor es %d", sizeof(una), una);
 
 /*
  Manejo de tipos enumerados
 */

 printf("\n\nEnumeración con nombre arco_iris =\n");
 printf(" (rojo, anaranjado, amarillo, verde, azul, agnil, violeta).");
 printf("\n\nEl tamaño de verde es %d y su valor es %d", sizeof(rojo), verde);
 
 p = violeta;
 
 printf("\n\nEl tamaño de p (= violeta) es %d y su valor es %d", sizeof(p), p);
 
 printf("\n\nEl tipo enumerado aleatoria es asi:\n\n");
 printf("enum aleatoria { primero = -7, segundo, tercero, cuarto = 25, quinto, sexto } ale;\n");
 printf("\n\nValores adoptados por los elementos de ale:\n");
 printf("\n\nEl valor de primero es   %d\n", primero);
 printf("\nEl valor de segundo es   %d\n", segundo);
 printf("\nEl valor de tercero es   %d\n", tercero);
 printf("\nEl valor de cuarto es    %d\n", cuarto);
 printf("\nEl valor de quinto es    %d\n", quinto);
 printf("\nEl valor de sexto es     %d\n", sexto);
 printf("\n\nLas enumeraciones ocupan 1 byte, y admiten como máximo 256 elementos.\n");
 printf("Las enumeraciones no se pueden leer ni escribir directamente, y aumentan\n");
 printf("de uno en uno a partir del valor establecido para un elemento.\n");
 printf("\n\nTerminación normal del programa.\n");
}



Comentarios .- Los valores pertenecientes a tipos enumerados no se pueden leer a través del teclado, ni tampoco se pueden imprimir (con formato de cadena) en pantalla. Si se precisa mostrar en pantalla los identificadores de un tipo enumerado, es necesario recurrir a una tabla de cadenas, cuyo índice será el valor del identificador que se quiera mostrar. Los valores de tipos enumerados pueden recibir valores enteros por asignación. No hay comprobación de alcances, ni tampoco de pertenencia a la enumeración correcta.

Ejercicios propuestos



  1. Ejercicio 0201r01.- Construir un programa que muestre un menú y e indique la opción seleccionada, empleando una enumeración y una lista de cadenas.

    La solución podría ser la siguente:
    /*
    Construir un programa que muestre un menú y e indique la 
    opción seleccionada, empleando una enumeración y una
    lista de cadenas.
    */
    
    #include<stdio.h>
    #include<stdlib.h>
    
    #define NUM_OPCIONES 5
    int main(int argc, char * argv[])
     {
      enum {Nuevo, Abrir, Guardar, Cerrar, Salir};
      char menu[][10] = {"Nuevo", "Abrir", "Guardar", "Cerrar", "Salir"};
      char opcion;
      int i, caso;
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\nSeleccione una opción:\n\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d) ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Mostrar en pantalla la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Nuevo:  printf("\nSe ha seleccionado la opción %s\n", menu[caso]);
              break;
          case Abrir:  printf("\nSe ha seleccionado la opción %s\n", menu[caso]);
              break;
          case Guardar: printf("\nSe ha seleccionado la opción %s\n", menu[caso]);
              break;
          case Cerrar: printf("\nSe ha seleccionado la opción %s\n", menu[caso]);
              break;
          case Salir:  printf("\nSe ha seleccionado la opción %s\n", menu[caso]);
              break;
          default:  printf("\n\nOpción incorrecta\n\n");
              break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
     
    
    /*
      Seleccione una opción:
    
      
    
      Nuevo (0) Abrir (1) Guardar (2) Cerrar (3) Salir (4)  : 0
    
      
    
      Se ha seleccionado la opción Nuevo
    
      
    
      Seleccione una opción:
    
      
    
      Nuevo (0) Abrir (1) Guardar (2) Cerrar (3) Salir (4)  : 1
    
      
    
      Se ha seleccionado la opción Abrir
    
      
    
      Seleccione una opción:
    
      
    
      Nuevo (0) Abrir (1) Guardar (2) Cerrar (3) Salir (4)  : 2
    
      
    
      Se ha seleccionado la opción Guardar
    
      
    
      Seleccione una opción:
    
      
    
      Nuevo (0) Abrir (1) Guardar (2) Cerrar (3) Salir (4)  : 3
    
      
    
      Se ha seleccionado la opción Cerrar
    
      
    
      Seleccione una opción:
    
      
    
      Nuevo (0) Abrir (1) Guardar (2) Cerrar (3) Salir (4)  : 4
    
      
    
      Se ha seleccionado la opción Salir
    
      
      
    
      Fin del programa
    */
    


    Comentarios .- El uso paralelo de una enumeración y una lista de cadenas facilita notablemente la construcción del programa; además, se entiende claramente el sentido de las acciones desencadenadas por el programa principal. Obsérvese que hemos recurrido a leer un carácter, aun cuando la opción es numérica. Una vez leído el valor de opción, se traduce a un número restándole el valor de '0', de tal modo que el carácter '0' se convierte en el número 0, el carácter '1' se convierte en el número 1, y así sucesivamente. De este modo, no es preciso comparar opcion con '4' sino caso con Salir ... y el método debe funcionar siempre, sin cambios. Obsérvese también que se capturan los posibles errores del usuario: si el valor de caso no aparece en la lista de sentencias case, se ejecuta el default correspondiente. Por último, el uso de fpurge(stdin) garantiza la eliminación de caracteres restantes en el búfer de teclado, quedando este limpio y preparado para nuevas lecturas. Esto tendrá importancia en aquellos programas en que la activación de una opción desencadene la lectura de datos a partir del teclado.
  2. Ejercicio 0201r02.- Construir una calculadora Euros (€)<->Pesetas (Ptas) empleando una enumeración.
    La solución puede ser la siguiente:
    /*
    Construir una calculadora que permita convertir entre euros y 
    pesetas, empleando una enumeración.
    */
    
    #include<stdio.h>
    #include<stdlib.h>
    
    #define NUM_OPCIONES 3
    int main(int argc, char * argv[])
     {
      enum {Paso_a_Euros, Paso_a_Pesetas, Salir};
      char menu[][20] = {"Pasar a Euros", "Pasar a Ptas", "Salir"};
      char opcion;
      int i, caso;
      float numpesetas, numeuros;
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\nSeleccione una opción:\n\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d)        ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Mostrar en pantalla la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Paso_a_Euros:  printf("\nEscriba el número de pesetas: ");
                scanf("%f", &numpesetas);
                numeuros = numpesetas / 166.386;
                printf("\n%10.3f Pesetas equivalen a %10.2f€\n", numpesetas, numeuros);
                fpurge(stdin);
                break;
          case Paso_a_Pesetas: printf("\nEscriba el número de euros: ");
                scanf("%f", &numeuros);
                numpesetas = numeuros * 166.386;
                printf("\n%10.2f€ equivale a %10.3f Pesetas\n", numeuros, numpesetas);
                fpurge(stdin);
                break;
          case Salir:    printf("\nSaliendo...\n");
                break;
          default:    printf("\n\nOpción incorrecta\n\n");
                break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
     /*
     Resultado de la ejecución:
     
     Seleccione una opción:
    
    Pasar a Euros (0)        Pasar a Ptas (1)        Salir (2)         : 0
    
    Escriba el número de pesetas: 166.386
    
       166.386 Pesetas equivalen a       1.00€
    
    Seleccione una opción:
    
    Pasar a Euros (0)        Pasar a Ptas (1)        Salir (2)         : 1
    
    Escriba el número de euros: 1.0
    
          1.00€ equivalen a    166.386 Pesetas
    
    Seleccione una opción:
    
    Pasar a Euros (0)        Pasar a Ptas (1)        Salir (2)         : 2
    
    Saliendo...
    Fin del programa
    */
    
    Notas
    El programa se ha construido por simple modificación del anterior, con la intención de mostrar los beneficios que aporta la reutilización de código en menor o mayor grado. Se utilizan especificaciones de formato para números reales con objeto de visualizar un número de decimales adecuado a cada situación.
  3. Ejercicio 0201r03.- Construir un programa que aplique distintas desgravaciones fiscales en función del estado civil del usuario. El programa recibirá como datos la base imponible y el estado del usuario, proporcionando como resultado la cantidad liquidable, esto es, la base imponible menos la desgravación.

    El programa podría ser el siguiente:
    /*
     Construir un programa que aplique distintas desgravaciones fiscales 
    en función del estado civil del usuario. El programa 
    recibirá como datos la base imponible y el estado del 
    usuario, proporcionando como resultado la cantidad desgravada.
    
    */
    
    #include<stdio.h>
    #include<stdlib.h>
    
    #define NUM_OPCIONES 6
    int main(int argc, char * argv[])
     {
      enum {Soltero, Casado, Viudo, Separado, Divorciado, Salir};
      char menu[][20] = {"Soltero", "Casado", "Viudo", "Separado", "Divorciado", "Salir"};
      float desgravacion[] = {1000.0, 2000.0, 3000.0, 4000.0, 5000.0};
      float base_imponible, cantidad_final;
      char opcion;
      int i, caso;
      system("clear"); /* Para limpiar la pantalla antes empezar la ejecución */
      fpurge(stdin);
      do
       { 
        printf("\nEspecifique su estado civil (5 para salir):\n\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("[%d] %s", i, menu[i]);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
    
        caso = opcion - '0';
        
        switch(caso)
         {
          case Soltero: 
          case Casado: 
          case Viudo:
          case Separado:
          case Divorciado:
              {
               do
                {
                 printf("\n\nEspecifique su base imponible (> 5000€): ");
                 scanf("%f", &base_imponible);
                 fpurge(stdin);
                } while (base_imponible < 5000.0);
               cantidad_final = base_imponible - desgravacion[caso];
               printf("La cantidad final a pagar es de %10.2f€.\n\n", cantidad_final);
               break;
              }
          case Salir:  printf("\nSaliendo...\n\n");
              break;
          default:  printf("\n\nOpción incorrecta\n\n");
              break;
         }
       } while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
     /*
     Resultado de la ejecución:
    
     
    
    	Especifique su estado civil (5 para salir):
    
    [0] Soltero[1] Casado[2] Viudo[3] Separado[4] Divorciado[5] Salir : 0
    Especifique su base imponible (> 5000€): 5000
    La cantidad final a pagar es de    4000.00€.
    Especifique su estado civil (5 para salir):
    
    [0] Soltero[1] Casado[2] Viudo[3] Separado[4] Divorciado[5] Salir : 1
    Especifique su base imponible (> 5000€): 5000
    La cantidad final a pagar es de    3000.00€.
    Especifique su estado civil (5 para salir):
    
    [0] Soltero[1] Casado[2] Viudo[3] Separado[4] Divorciado[5] Salir : 2
    Especifique su base imponible (> 5000€): 5000
    La cantidad final a pagar es de    2000.00€.
    Especifique su estado civil (5 para salir):
    
    [0] Soltero[1] Casado[2] Viudo[3] Separado[4] Divorciado[5] Salir : 3
    Especifique su base imponible (> 5000€): 5000
    La cantidad final a pagar es de    1000.00€.
    Especifique su estado civil (5 para salir):
    
    [0] Soltero[1] Casado[2] Viudo[3] Separado[4] Divorciado[5] Salir : 4
    Especifique su base imponible (> 5000€): 5000
    La cantidad final a pagar es de       0.00€.
    Especifique su estado civil (5 para salir):
    
    [0] Soltero[1] Casado[2] Viudo[3] Separado[4] Divorciado[5] Salir : 5
    
    Saliendo...
    Fin del programa
    */
    Notas
    En el switch() que gobierna el programa se han agrupado varias todas las opciones que requieren indicar la base imponible. De este modo, si el usuario decide Salir , no se le pregunta más; si por el contrario decide calcular la cantidad final a ingresar (base imponible menos desgravación) entonces se le pregunta su estado civil y base imponible. No se admiten bases imponibles que dieran lugar a una cantidad a devolver.

  4. Ejercicio 0201r04.- Construir un programa dotado de un menú de Archivo, otro de Edición y otro de Texto. El menú Archivo ofrecerá las opciones Nuevo, Abrir, Guardar, Cerrar y Salir. El menú Edición ofrecerá las opciones Deshacer, Cortar, Copiar y Pegar. El menú Texto ofrecerá las opciones Menor, Medio y Mayor. El programa debe mostrar una barra con los menús Archivo, Edición y Texto; cuando el usuario seleccione uno de ellos, aparecerán las opciones correspondientes a ese menú y el usuario podrá seleccionar una de ellas. Hecho esto, el programa mostrará el menú y la opción seleccionados, en la forma siguiente: Se ha seleccionado la opción Cortar del menú Edición.

    La solución puede ser como sigue:
    /*
     Construir un programa dotado de un menú de Archivo, otro de 
    Edición y otro de Texto. El menú Archivo 
    ofrecerá las opciones Nuevo, Abrir, Guardar, Cerrar y Salir. 
    El menú Edición ofrecerá las opciones Deshacer, 
    Cortar, Copiar y Pegar. El menú Texto ofrecerá las 
    opciones Menor, Medio y Mayor. El programa debe mostrar una barra 
    con los menús Archivo, Edición y Texto; cuando el 
    usuario seleccione uno de ellos, aparecerán las opciones 
    correspondientes a ese menú y el usuario podrá 
    seleccionar una de ellas. Hecho esto, el programa mostrará el 
    menú y la opción seleccionados, en la forma siguiente: 
    Se ha seleccionado la opción Cortar del menú 
    Edición.
    */
    
    #include<stdio.h>
    #include<stdlib.h>
    
    #define NUM_OPCIONES_BARRA 3
    #define NUM_OPCIONES_ARCHIVO 5
    #define NUM_OPCIONES_EDICION 5
    #define NUM_OPCIONES_TEXTO 3
    
    /*
    	Enumeraciones varias. Disponemos de identificadores
    	para todos los menús y todas las opciones de cada
    	uno de ellos.
    */
    enum {Archivo, Edicion, Texto};
    enum {Nuevo, Abrir, Guardar, Cerrar, Salir};
    enum {Deshacer, Cortar, Copiar, Pegar, Borrar};
    enum {Menor, Medio, Mayor};
    /*
    	Listas de cadenas asociadas a las distintas enumeraciones.
    	Es imprescindible que coincida el nombre de los identificadores
    	y el orden en que aparecen estos en las enumeraciones.
    */
    char barraMenus[][10] = { "Archivo", "Edicion", "Texto"};
    char menuArchivo[][10] = { "Nuevo", "Abrir", "Guardar", "Cerrar", "Salir"};
    char menuEdicion[][10] = { "Deshacer", "Cortar", "Copiar", "Pegar", "Borrar"};
    char menuTexto[][10] = { "Menor", "Medio", "Mayor"};
    
    int menu_seleccionado(void);
    int opcion_seleccionada(int numero_menu);
    
    int main(int argc, char * argv[])
     {
      int num_menu, num_opcion;
      system("clear");
      do
       {
       /* Visualizar menú y leer la opción */
        num_menu = menu_seleccionado();
        num_opcion = opcion_seleccionada(num_menu);
        switch(num_menu)
         {
          case Archivo: printf("\nSeleccionada la opción %s del menú %s\n",
                 menuArchivo[num_opcion],
                 barraMenus[num_menu]);
              break;
          case Edicion: printf("\nSeleccionada la opción %s del menú %s\n",
                 menuEdicion[num_opcion],
                 barraMenus[num_menu]);
              break;
          case Texto: printf("\nSeleccionada la opción %s del menú %s\n",
                 menuTexto[num_opcion],
                 barraMenus[num_menu]);
              break;
         }
       }
       while(!(num_menu == Archivo && num_opcion == Salir));
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
    int menu_seleccionado(void) {
     int i;
     int n;
     do
      {
       for(i=0;i<NUM_OPCIONES_BARRA;i++)
        printf("[%d] %s ", i, barraMenus[i]);
       scanf("%d", &n);
       fpurge(stdin);
      } while ( n< Archivo || n > Texto);
     return n;
      
    }
    int opcion_seleccionada(int numero_menu){
     int i,n;
     switch(numero_menu)
      {
       case Archivo:
           do
            {
             for(i=0;i<NUM_OPCIONES_ARCHIVO;i++)
              printf("[%d] %s ", i, menuArchivo[i]);
             scanf("%d", &n);
            } while (n < Nuevo || n > Salir);
           break;
       case Edicion:
           do
            {
             for(i=0;i<NUM_OPCIONES_EDICION;i++)
              printf("[%d] %s ", i, menuEdicion[i]);
             scanf("%d", &n);
            } while (n < Deshacer || n > Borrar);
           break;
       case Texto:
           do
            {
             for(i=0;i<NUM_OPCIONES_TEXTO;i++)
              printf("[%d] %s ", i, menuTexto[i]);
             scanf("%d", &n);
            } while (n < Menor || n > Mayor);
           break;
      }
     return n;
    }
    /*
    Resultado de la ejecución del programa:
    
    [0] Archivo [1] Edicion [2] Texto 0
    [0] Nuevo [1] Abrir [2] Guardar [3] Cerrar [4] Salir 0
    
    Seleccionada la opción Nuevo del menú Archivo
    [0] Archivo [1] Edicion [2] Texto 0
    [0] Nuevo [1] Abrir [2] Guardar [3] Cerrar [4] Salir 1
    
    Seleccionada la opción Abrir del menú Archivo
    [0] Archivo [1] Edicion [2] Texto 0
    [0] Nuevo [1] Abrir [2] Guardar [3] Cerrar [4] Salir 2
    
    Seleccionada la opción Guardar del menú Archivo
    [0] Archivo [1] Edicion [2] Texto 0
    [0] Nuevo [1] Abrir [2] Guardar [3] Cerrar [4] Salir 3
    
    Seleccionada la opción Cerrar del menú Archivo
    [0] Archivo [1] Edicion [2] Texto 0
    [0] Nuevo [1] Abrir [2] Guardar [3] Cerrar [4] Salir 4
    Seleccionada la opción Salir del menú Archivo
    */
    Notas
    Este programa muestra el denominado comportamiento modal, esto es, la funcionalidad del programa se limita a la ofrecida en el modo (menú) que se activa. Un comportamiento más sofisticado consistiría en admitir cualquier combinación de menú y opción sin necesidad de recurrir a modos para acceder a opciones específicas. Obsérvese también la presencia de un par de funciones, menu_seleccionado() y opcion_seleccionada() , que hacen más comprensible el programa. Otra posible solución sería construir una tabla de cadenas, en que cada fila correspondería a las opciones de un cierto menú; las columnas serían opciones homólogas de distintos menús. Esta organización se podría mantener en disco, con objeto de leerla en el momento del arranque y mostrar una u otra colección de menús y opciones en función de las preferencias del usuario. Podría ser algo así:
    char tm[][6] = 	{
          {"Archivo", "Nuevo", "Abrir", "Cerrar", "Guardar", "Salir"},
          {"Edicion", "Deshacer", "Cortar", "Copiar", "Pegar", "Borrar"},
          {"Texto", "Menor", "Medio", "Mayor"},
         };
    
    ¿Sabría el lector adaptar este ejercicio a la nueva estructura de datos?

  5. Ejercicio 0201r05.- Construir un programa que admita el nombre de un usuario y lo muestre en mayúsculas o minúsculas a voluntad del usuario, que utilizará las opciones de un menú.

    El programa podría ser como sigue:
    /*
     Construir un programa que admita el nombre de un usuario y lo 
    muestre en mayúsculas o minúsculas a voluntad del 
    usuario, que utilizará las opciones de un menú.
    */
    
    #include<stdio.h>
    #include<stdlib.h>
    #include <ctype.h> /* Para poder efectuar el paso a mayúsculas o minúsculas */
    #define NUM_OPCIONES 4
    
    void pedir(char nombre[]);
    void mostrar_en_mayusculas(char nombre[]);
    void mostrar_en_minusculas(char nombre[]);
    int main(int argc, char * argv[])
     {
      enum {Escribir, EnMayusculas, EnMinusculas, Salir};
      char menu[][20] = {"Escribir nombre", "Ver en mayúsculas", "Ver en minúsculas", "Salir"};
      int nombre_leido;
      char nombre[80];
      char opcion;
      int i, caso;
      system("clear"); /* Suponiendo una ejecución en Unix. En otras plataformas es "cls" */
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\nSeleccione una opción:\n\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d) ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Mostrar en pantalla la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Escribir: pedir(nombre);
              nombre_leido = 1;
              break;
          case EnMayusculas:  if (nombre_leido)
                 mostrar_en_mayusculas(nombre);
                else
                 printf("\n\nPor favor, escriba primero el nombre.\n");
              break;
          case EnMinusculas: if (nombre_leido)
                 mostrar_en_minusculas(nombre);
                else
                 printf("\n\nPor favor, escriba primero el nombre.\n");
              break;
          case Salir:  printf("\nSaliendo...\n");
              break;
          default:  printf("\n\nOpción incorrecta\n\n");
              break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
    void pedir(char nombre[])
    {
     int n = 80;
     int i;
     printf("\nPor favor, escriba su nombre: ");
     fgets(nombre, n ,stdin);
     printf("\nGracias, %s\n", nombre);
    }
    void mostrar_en_mayusculas(char nombre[])
    {
     int n = strlen(nombre);
     int i;
     printf("\n\nSu nombre en mayúsculas es: ");
     for(i=0;i<n;i++)
      printf("%c", toupper(nombre[i]));
     printf("\n\n");
    }
    void mostrar_en_minusculas(char nombre[])
    {
     int n = strlen(nombre);
     int i;
     printf("\n\nSu nombre en minúsculas es: ");
     for(i=0;i<n;i++)
      printf("%c", tolower(nombre[i]));
     printf("\n\n");
    }
    
    /*
    Resultado de la ejecución del programa:
    
    Seleccione una opción:
    
    Escribir nombre (0) Ver en mayúsculas (1) Ver en minúsculas (2) Salir (3)  : 0
    
    Por favor, escriba su nombre: coti
    
    Gracias, coti
    Seleccione una opción:
    
    Escribir nombre (0) Ver en mayúsculas (1) Ver en minúsculas (2) Salir (3)  : 1
    Su nombre en mayúsculas es: COTI
    
    Seleccione una opción:
    
    Escribir nombre (0) Ver en mayúsculas (1) Ver en minúsculas (2) Salir (3)  : 2
    Su nombre en minúsculas es: coti
    
    Seleccione una opción:
    
    Escribir nombre (0) Ver en mayúsculas (1) Ver en minúsculas (2) Salir (3)  : 3
    
    Saliendo...
    Fin del programa
    */
    
    
    Notas
    En este programa se sigue la pauta habitual, consistente en crear enumeraciones y listas de cadenas coordinadas. Obsérvese la presencia del centinela nombre_leido , que impide que el usuario intente visualizar en mayúsculas o minúsculas un nombre antes de introducirlo. Las funciones toupper() y tolower() , declaradas en ctype.h , permiten efectuar la transformación deseada. Obsérvese tambien el uso de fgets() para garantizar que el número de caracteres leídos no supera las dimensiones del fragmento de memoria reservado al efecto. Se podría haber utilizado gets() , pero se deplora su uso por plantear precisamente este problema: la imposibilidad de asegurar que no se leerán más caracteres que los reservados al efecto. Otra solución consiste en determinar por anticipado el número de caracteres disponibles para su lectura, empleando la función fgetln() , descrita aquí , y de cuyo uso se muestra un ejemplo aquí .

  6. Ejercicio 0201r06.- Construir un programa que pida el nombre de un archivo de texto y muestre su contenido en pantalla o lo almacene en otro archivo, a voluntad del usuario.
    La solución puede ser la siguiente:
    /*
    Construir un programa que pida el nombre de un archivo de texto y 
    muestre su contenido en pantalla o lo almacene en otro archivo, a 
    voluntad del usuario.
    */
    #include<stdio.h>
    #include<stdlib.h>
    
    #define NUM_OPCIONES 4
    #define MAX_CHARS_NOMBRE 80
    
    char archivo[MAX_CHARS_NOMBRE];
    
    void nombrar(char n[]);
    void mostrar_en_pantalla(char n[]);
    void guardar_en_disco(char n[]);
    
    int main(int argc, char * argv[])
     {
      enum {Nombrar, Pantalla, Disco, Salir};
      char menu[NUM_OPCIONES][30] = {"Nombrar archivo", "Mostrar en pantalla", "Guardar en disco", "Salir"};
      char opcion;
      int i, caso;
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\nSeleccione una opción:\n\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d) ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Ejecutar la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Nombrar: nombrar(archivo);
              break;
          case Pantalla: mostrar_en_pantalla(archivo);
              break;
          case Disco:  guardar_en_disco(archivo);
              break;
          case Salir:  printf("\nSaliendo...\n", menu[caso]);
              break;
          default:  printf("\n\nOpción incorrecta\n\n");
              break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
    void nombrar(char n[]){
     printf("\nPor favor, escriba el nombre del archivo que desea tratar (INTRO para salir): ");
     fgets(n,MAX_CHARS_NOMBRE, stdin);
     if (n[0] == '\n')
      {
       printf("\nPrograma interrumpido por el usuario... Saliendo.\n");
       exit(1);
      }
     n[strlen(n)-1] = '\0';
     printf("\nEl nombre del archivo seleccionado es : >%s<\n\n", n);
    };
    void mostrar_en_pantalla(char n[]){
     FILE * fp;
     char letra = 1;
     if (!(fp = fopen(n,"r")))
      printf("\nEl archivo indicado no existe en este directorio.\n");
     else
      {
       printf("\nEl archivo %s comienza en la línea siguiente a la flecha:\n\n------>\n", n);
       letra = fgetc(fp);
       while (letra != -1)
        {
          putc(letra, stdout);
          letra = fgetc(fp);
        }
       fclose(fp);
       printf("\n<--------\n\nEl archivo %s acaba en la línea anterior a la flecha.\n\n", n);
      }
    }
    void guardar_en_disco(char n[]){
     char copia[MAX_CHARS_NOMBRE];
     FILE * entrada, *salida;
     char letra = 1;
     printf("\nPor favor, escriba el nombre del archivo COPIA (INTRO para salir): ");
     fgets(copia,MAX_CHARS_NOMBRE, stdin);
     if (copia[0] == '\n')
      {
       printf("\nPrograma interrumpido por el usuario... Saliendo.\n");
       exit(1);
      }
     copia[strlen(copia)-1] = '\0';
     salida = fopen(copia, "a");
     if (!salida)
      {
       printf("\nNo fue posible crear el archivo %s. Saliendo.\n\n", copia);
       exit(2);
      }
     printf("\nEl nombre del archivo copia es : >%s<\n\n", copia);
     if (!(entrada = fopen(n,"r")))
      printf("\nEl archivo indicado no existe en este directorio.\n");
     else
      {
       letra = fgetc(entrada);
       while (letra != -1)
        {
          putc(letra, salida);
          letra = fgetc(entrada);
        }
       fclose(entrada);
       fclose(salida);
       printf("\n\nEl proceso de copia en %s ha teminado.\n\n", copia);
      }
    
    }
    
    /*
    El resultado de la ejecución es como sigue:
    
    Seleccione una opción:
    
    Nombrar archivo (0) Mostrar en pantalla (1) Guardar en disco (2) Salir (3)  : 0
    
    Por favor, escriba el nombre del archivo que desea tratar (INTRO para salir): prueba.txt
    
    El nombre del archivo seleccionado es : >prueba.txt<
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Mostrar en pantalla (1) Guardar en disco (2) Salir (3)  : 1
    
    El archivo prueba.txt comienza en la línea siguiente a la flecha:
    
    ------>
    Esto
    es
    una
    prueba.
    
    <--------
    
    El archivo prueba.txt acaba en la línea anterior a la flecha.
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Mostrar en pantalla (1) Guardar en disco (2) Salir (3)  : 2
    
    Por favor, escriba el nombre del archivo COPIA (INTRO para salir): copia.txt
    
    El nombre del archivo copia es : >copia.txt<
    
    
    
    El proceso de copia en copia.txt ha teminado.
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Mostrar en pantalla (1) Guardar en disco (2) Salir (3)  : 3
    
    Saliendo...
    
    
    Fin del programa
    
    */

    Notas
    Este programa tiene la estructura habitual, basada en una enumeración y una lista de cadenas para el menú. Consta de tres funciones además del programa principal:

    Desde luego, el programa permite copiar y archivo en otro y visualizar en pantalla el resultado de la copia.

  7. Ejercicio 0201r07.- Construir un programa que muestre un menú adecuado para leer un fichero de texto y escribirlo en otro fichero en mayúsculas o en minúsculas.
    La solución pueden ser la siguiente:
    /*
    Construir un programa que muestre un menú adecuado para leer 
    un fichero de texto y escribirlo en otro fichero en 
    mayúsculas o en minúsculas.*/
    #include<stdio.h>
    #include<stdlib.h>
    
    #define NUM_OPCIONES 4
    #define MAX_CHARS_NOMBRE 80
    
    char archivo[MAX_CHARS_NOMBRE];
    enum {Nombrar, Minusculas, Mayusculas, Salir};
    
    void nombrar(char n[]);
    void traducir(char n[], int paso);
    
    int main(int argc, char * argv[])
     {
      char menu[NUM_OPCIONES][30] = {"Nombrar archivo", "Paso a minúsculas",
      "Paso a mayúsculas", "Salir"};
      char opcion;
      int i, caso;
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\nSeleccione una opción:\n\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d) ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Ejecutar la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Nombrar:  nombrar(archivo);
               break;
          case Minusculas: traducir(archivo, Minusculas);
               break;
          case Mayusculas: traducir(archivo, Mayusculas);
               break;
          case Salir:   printf("\nSaliendo...\n", menu[caso]);
               break;
          default:   printf("\n\nOpción incorrecta\n\n");
               break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
    void nombrar(char n[]){
     printf("\nPor favor, escriba el nombre del archivo que desea tratar (INTRO para salir): ");
     fgets(n,MAX_CHARS_NOMBRE, stdin);
     if (n[0] == '\n')
      {
       printf("\nPrograma interrumpido por el usuario... Saliendo.\n");
       exit(1);
      }
     n[strlen(n)-1] = '\0';
     printf("\nEl nombre del archivo seleccionado es : >%s<\n\n", n);
    };
    
    void traducir(char n[], int paso){
     char traduccion[MAX_CHARS_NOMBRE];
     FILE * entrada, *salida;
     char letra = 1;
     printf("\nPor favor, escriba el nombre del archivo TRADUCIDO (INTRO para salir): ");
     fgets(traduccion,MAX_CHARS_NOMBRE, stdin);
     if (traduccion[0] == '\n')
      {
       printf("\nPrograma interrumpido por el usuario... Saliendo.\n");
       exit(1);
      }
     traduccion[strlen(traduccion)-1] = '\0';
     salida = fopen(traduccion, "a");
     if (!salida)
      {
       printf("\nNo fue posible crear el archivo %s. Saliendo.\n\n", traduccion);
       exit(2);
      }
     printf("\nEl nombre del archivo traducido es : >%s<\n\n", traduccion);
     if (!(entrada = fopen(n,"r")))
      printf("\nEl archivo indicado no existe en este directorio.\n");
     else
      {
       letra = fgetc(entrada);
       while (letra != -1)
        {
          if (paso == Minusculas)
           letra = tolower(letra);
          else
           letra = toupper(letra);
          putc(letra, salida);
          letra = fgetc(entrada);
        }
       fclose(entrada);
       fclose(salida);
       printf("\n\nEl proceso de traducción a %s en %s ha teminado.\n\n",
           paso==Minusculas ? "minúsculas":"mayúsculas", traduccion);
      }
    
    }
    
    /*
    Resultado de la ejecución
    
    Seleccione una opción:
    
    Nombrar archivo (0) Paso a minúsculas (1) Paso a mayúsculas (2) Salir (3)  : 0
    
    Por favor, escriba el nombre del archivo que desea tratar (INTRO para salir): prueba.txt
    
    El nombre del archivo seleccionado es : >prueba.txt<
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Paso a minúsculas (1) Paso a mayúsculas (2) Salir (3)  : 1
    
    Por favor, escriba el nombre del archivo TRADUCIDO (INTRO para salir): pruebamin.txt
    
    El nombre del archivo traducido es : >pruebamin.txt<
    
    
    El archivo indicado no existe en este directorio.
    
    Seleccione una opción:
    
    Nombrar archivo (0) Paso a minúsculas (1) Paso a mayúsculas (2) Salir (3)  : 0
    
    Por favor, escriba el nombre del archivo que desea tratar (INTRO para salir): prueba.txt
    
    El nombre del archivo seleccionado es : >prueba.txt<
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Paso a minúsculas (1) Paso a mayúsculas (2) Salir (3)  : 1
    
    Por favor, escriba el nombre del archivo TRADUCIDO (INTRO para salir): pruebamin.txt
    
    El nombre del archivo traducido es : >pruebamin.txt<
    
    
    
    El proceso de traducción a minúsculas en pruebamin.txt ha teminado.
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Paso a minúsculas (1) Paso a mayúsculas (2) Salir (3)  : 2
    
    Por favor, escriba el nombre del archivo TRADUCIDO (INTRO para salir): pruebamax.txt
    
    El nombre del archivo traducido es : >pruebamax.txt<
    
    
    
    El proceso de traducción a mayúsculas en pruebamax.txt ha teminado.
    
    
    Seleccione una opción:
    
    Nombrar archivo (0) Paso a minúsculas (1) Paso a mayúsculas (2) Salir (3)  : 3
    
    Saliendo...
    
    
    Fin del programa
    
    [maxus~:] coti% cat pruebamin.txt
    esto
    es
    una
    prueba.
    [maxus~:] coti% cat pruebamax.txt
    ESTO
    ES
    UNA
    PRUEBA.
    */

    Notas
    Este programa es análogo al anterior, y plantea una posibilidad interesante, a saber, "filtrar" el contenido del archivo de entrada para después volcar el resultado en un archivo de salida. En este caso el filtro era trivial, pero es frecuente hallar situaciones en que las transfomaciones sean más complejos. Tal sería el caso de un programa que facilitase la traducción de textos entre distintas plataformas.

  8. Ejercicio 0201r08.- Construir un programa que muestre la trama de bits correspondiente a un valor enumerado (véase 0203 ).

    La solución puede ser la siguiente:
    #include<stdio.h>
    
    /*
     Este programa muestra la estructura de bits de un valor enumerado.
    */
    
    struct muchos_bits {
     unsigned bit1:1;
     unsigned bit2:1;
     unsigned bit3:1;
     unsigned bit4:1;
     unsigned bit5:1;
     unsigned bit6:1;
     unsigned bit7:1;
     unsigned bit8:1;
     unsigned bit9:1;
     unsigned bit10:1;
     unsigned bit11:1;
     unsigned bit12:1;
     unsigned bit13:1;
     unsigned bit14:1;
     unsigned bit15:1;
     unsigned bit16:1;
     unsigned bit17:1;
     unsigned bit18:1;
     unsigned bit19:1;
     unsigned bit20:1;
     unsigned bit21:1;
     unsigned bit22:1;
     unsigned bit23:1;
     unsigned bit24:1;
     unsigned bit25:1;
     unsigned bit26:1;
     unsigned bit27:1;
     unsigned bit28:1;
     unsigned bit29:1;
     unsigned bit30:1;
     unsigned bit31:1;
     unsigned bit32:1;
    };
    
    enum VariosValores { Primero, Segundo = -1, Tercero, Cuarto};
    
    union Ejemplo_union {
     enum VariosValores valor_enumerado;
     struct muchos_bits bits_union;
    } union_prueba;
    
    enum VariosValores valor_estudiado;
    
    void mostrar_valores(union Ejemplo_union eu);
    
    int main(int argc, char * argv[])
    {
     printf("Programa de prueba para uniones y enumeraciones.\n\n");
     
     printf("\nLo que sigue es la estructura de bits de los valores\
    enumerados Primero, Segundo, Tercero y Cuarto:\n\n");
    
     union_prueba.valor_enumerado = Primero;
     mostrar_valores(union_prueba);
    
     union_prueba.valor_enumerado = Segundo;
     mostrar_valores(union_prueba);
     
     
     union_prueba.valor_enumerado = Tercero;
     mostrar_valores(union_prueba);
     
     
     union_prueba.valor_enumerado = Cuarto;
     mostrar_valores(union_prueba);
     
     
     printf("\n\nTerminación normal del programa.\n");
    }
    
    void mostrar_valores(union Ejemplo_union eu){
     printf("%d",eu.bits_union.bit1);
     printf("%d",eu.bits_union.bit2);
     printf("%d",eu.bits_union.bit3);
     printf("%d",eu.bits_union.bit4);
     printf("%d",eu.bits_union.bit5);
     printf("%d",eu.bits_union.bit6);
     printf("%d",eu.bits_union.bit7);
     printf("%d",eu.bits_union.bit8);
     printf("%d",eu.bits_union.bit9);
     printf("%d",eu.bits_union.bit10);
     printf("%d",eu.bits_union.bit11);
     printf("%d",eu.bits_union.bit12);
     printf("%d",eu.bits_union.bit13);
     printf("%d",eu.bits_union.bit14);
     printf("%d",eu.bits_union.bit15);
     printf("%d",eu.bits_union.bit16);
     printf("%d",eu.bits_union.bit17);
     printf("%d",eu.bits_union.bit18);
     printf("%d",eu.bits_union.bit19);
     printf("%d",eu.bits_union.bit20);
     printf("%d",eu.bits_union.bit21);
     printf("%d",eu.bits_union.bit22);
     printf("%d",eu.bits_union.bit23);
     printf("%d",eu.bits_union.bit24);
     printf("%d",eu.bits_union.bit25);
     printf("%d",eu.bits_union.bit26);
     printf("%d",eu.bits_union.bit27);
     printf("%d",eu.bits_union.bit28);
     printf("%d",eu.bits_union.bit29);
     printf("%d",eu.bits_union.bit30);
     printf("%d",eu.bits_union.bit31);
     printf("%d\n",eu.bits_union.bit32);
    }
    
    /*
    Resultados de la ejecución del programa:
    
    Programa de prueba para uniones y enumeraciones.
    
    
    Lo que sigue es la estructura de bits de los valores enumerados Primero, Segundo, Tercero y Cuarto:
    
    00000000000000000000000000000000
    11111111111111111111111111111111
    00000000000000000000000000000000
    00000000000000000000000000000001
    
    */

    Notas
    Como puede observarse, los valores de una enumeración se representan internamente mediante números enteros. Hemos asignado a Segundo el valor -1, lo cual da lugar a la oportuna codificación como número entero; el valor siguiente es el resultado de incrementarlo en 1 unidad, y por tanto es cero. El programa supone una mejora respecto al empleado en 0203 porque plantea el uso de una función, con una notable reducción del código necesario.