Programar un cRUD en C99

Los CRUD son los primeros tipos de programas administrativos que son impartidos en las universidades o en las escuelas, sin embargo algunos conceptos pueden ser confusos, por ello hemos escrito una mejora del pequeño tutorial que existía en el blog anterior el cual explicará como crear un programa de Altas, Bajas, Modificaciones y Listado (CRUD) en C.

Así que, preparen sus editores de texto favoritos, una buena taza de café y un poco de música para poder seguir el tutorial de la manera más relajada.

NOTAS

El código del tutorial se creó con las siguientes herramientas:

  •  Sistema Operativo:  Ubuntu 20.04 Focal Fossa
  •  Compilador:  gcc
  •  Banderas del Compilador: -Wall -Wpedantic -Wextra -std=c17
  •  IDE: Recomendamos CLion o Visual Studio Code para nuestros lectores.

RECOMENDACIONES & CONSIDERACIONES

Si estás usando un sistema operativo tipo MS (Windows) les recomendamos usar MINGW y el compilador gcc.

Como nota personal del autor: Eviten usar IDE’s obsoletos como DevC++ / Bloodshed DevC++ puesto que estos tienen linternas obsoletas y un compilador cuyo manejo de las direcciones de memoria puede ser descrito como: ineficiente.

Además procuren NO hacer copy-paste del código, traten de entenderlo o hacer tus propias versiones de los conceptos que vean en el tutorial, el código publicado aquí es 100% libre, si, pero dudo que vuestro profesor(es) considere tu trabajo como válido si copias y pegas código de internet. Oh, otra cosa y como nota final.

Redactando este tutorial supondré que ya conocen al menos los elementos básicos del lenguaje de programación C, así que solo nos dentendremos a explicar por que y no como funcionan las cosas.

¿Qué es un CRUD?

Un CRUD (de las siglas: CREATE, READ, UPDATE, DELETE) es un programa capaz de desempeñar las funciones básicas de cualquier base de datos, sean:  CrearLeerActualizar y Borrar.

Nace por la necesidad de crear un lugar para almacenar datos cuya persistencia es de una importancia mayor o cuyo volumen es demasiado como para mantenerlo almacenado en una memoria volátil (RAM).

board, card, chip

Preparando todo

Primero crearemos un archivo donde trabajar, en este caso en especial no

utilizaremos nada que involucre modularidad o cosas un poco más avanzadas en el tema de C. Solo será un archivo plano con extensión .c explicado paso a paso.

Si pierdes el hilo en transcurso del tutorial no hay problema, al final estará el código completo.

El problema

Tenemos que hacer las cosas de forma dinámica, no podemos empezar a hacer un programa sin un problema inicial. Afortunadamente la naturaleza  geek de los programadores nos facilita las cosas al momento de inventar problemas.

El problema a tratar es el siguiente:

Una aldea muy lejana tiene un problema con monstruos, bandidos, dragones y hechiceros. Como los héroes del pueblo están regados el alcalde decidió hacer lo siguiente, contrató a un mago experto en Magic-C11 para que cree un tablero de anuncios donde los héroes puedan darse de alta, baja o modificar sus detalles si es que cometieron un error.

Además deberá de ser capaz de listar a los héroes registrados.

El boletín también deberá de ser capaz de permitir a los aldeanos colocar

misiones y su recompensa en oro & experiencia para los héroes. ¿Puedes tú, un mago experto en Magic-C11 completar dicha hazaña?

Características del Héroe:

Los héroes para registrarse deberán tener los siguientes elementos:

  • id (único)
  • Nombre
  • Apodo
  • Clase
  • Habilidades (80 chars MAX)

Características de las Misiones del boletín:

Los aldeanos necesitarán de lo siguiente para registrar misiones para los héroes:

  • id (único)
  • Recompensa de misión
  • Experiencia otorgada (Entero)
  • Habilidades Necesarias

Creando el CRUD

Vamos a empezar, primero necesitaremos crear un archivo main.c y comenzar a editarlo, en el tutorial estaremos utilizando el Kernel Code Style un poco ajustado al estándar de C.

Si estás usando un IDE solo crea un nuevo proyecto y continúa con el tutorial.

Primero necesitaremos 2 bibliotecas escenciales para la creación de nuestro CRUD

  • stdio.h -> La biblioteca de E/S estándar.
  • stdlib.h -> La biblioteca estándar multipropósito de C.

Podemos incluirlas en nuestro archivo en la primera parte del mismo:

#include <stdio.h>

#include <stdlib.h>

Ahora necesitaremos definir nuestros tipos de datos para Héroes y Misiones, como son tipos de dato compuestos será necesario crear estructuras:

Nota extra del autor: El typedef no es necesario realmente, al usarlo en estructuras su único propósito es el de renombrar y de evitar escribir la palabra typedef antes de llamar a la misma.

typedef struct {

        int id;

        char nombre[20];

        char apodo[20];

        char clase[20]

        char habilidades[20];

} Heroe;

Y del mismo modo para las Misiones:

typedef struct {

        int id;

        double recompensa;

        int experiencia;

        char habilidades[80];

} Heroe;

Perfecto, así nos aseguraremos de guardar todo en un solo tipo de dato unificado.

Bien, pero ahora necesitamos algunas funciones donde procesaremos las cosas, primero necesitamos pensar ¿Cómo decidiremos si vamos a ingresar, borrar, listar o modificar datos?

La opción más simple es un menú principal donde toda opción aparezca, además, necesitamos de funciones que nos ayuden a crear, eliminar, modificar y listar las cosas dentro de nuestro programa.

Al ser un CRUD no es algo complejo y como dijo un profesor de la universidad donde estudió el autor:

 “Todos los CRUD son iguales”

Así que, luego de nuestra declaración de tipos (structs) podemos comenzar a incluir los prototipos de las funciones que necesitemos, en este caso la función menú y las funciones CRUD tanto de Héroes como de Misiones:

void menu();



void AltaHeroes();

void BajaHeroes();

void ModHeroes();

void ListHeroes();



void AltaMision();

void BajaMision();

void ModMision();

void ListMision();

Como estas funciones no necesitan retornar nada serán de tipo void.

Bien, ahora dentro de nuestra función main llamaremos a nuestra función menu , con esto el menú será lo primero que se ejecutará al iniciar nuestro programa:

// -- Función main

int main()

{

        menu();

        return 0;

}

Es importante eliminar los argumentos dentro de la función main, pues utilizaremos E/S estándar y no parámetros por consola, así que con eso nos ahorraremos cuantos bytes.

Creando el menú

Muy bien, ahora necesitamos crear nuestra función menú, primero viene la parte divertida y es crear MONTONES de prints para el usuario final, por supuesto que el “diseño” del menú es libre, yo creé algo así:

        int opcion, opcion2;

        printf("---------------HIRE-A-HERO--------------\n");

        printf("----------------------------------------\n");

        printf("           1) Heroes                    \n");

        printf("           2) Misiones                  \n");

        printf("           3) Salir                     \n");

        printf("----------------------------------------\n");

        printf("----------------------------------------\n");

        scanf("%d",&opcion);

        printf("\n\n\n\n\n\n\n\n\n\n\n\n");

        printf("---------------HIRE-A-HERO--------------\n");

        printf("----------------------------------------\n");

        printf("           1) Alta                      \n");

        printf("           2) Baja                      \n");

        printf("           3) Modificación              \n");

        printf("           4) Listado                   \n");

        printf("           5) Volver al Menú            \n");

        printf("----------------------------------------\n");

        printf(" ---------------------------------------\n");

        scanf("%d",&opcion2);

        printf("\n\n\n\n\n\n\n\n\n\n\n\n");

Bien, pero… esto no funcionará a secas, necesitamos saber que hacer cuando el usuario ingrese una opción además de manejar un error muy común: La entrada del usuario por eso decidí usar 2 switch statements y todo meterlo en un ciclo do + while:

void menu()

{

        int opcion,opcion2;

        do {

                printf("---------------HIRE-A-HERO--------------\n");

                printf("--------------------------------------\n");

                printf("           1) Heroes            \n");

                printf("           2) Misiones               \n");

                printf("           3) Salir                   \n");

                printf("--------------------------------------\n");

                printf("--------------------------------------\n");

                scanf("%d",&opcion);

                printf("\n\n\n\n\n\n\n\n\n\n\n\n");

                printf("---------------HIRE-A-HERO--------------\n");

                printf("--------------------------------------\n");

                printf("           1) Alta                    \n");

                printf("           2) Baja                    \n");

                printf("           3) Modificación            \n");

                printf("           4) Listado                 \n");

                printf("           5) Volver al Menú          \n");

                printf("--------------------------------------\n");

                printf(" -------------------------------------\n");

                scanf("%d",&opcion2);

                printf("\n\n\n\n\n\n\n\n\n\n\n\n");



                switch (opcion) {

                case 1:

                        switch(opcion2) {

                        case 1:

                                AltaHeroes();

                                break;

                        case 2:

                                BajaHeroes();

                                break;

                        case 3:

                                ModHeroes();

                                break;

                        case 4:

                                ListHeroes();

                                break;

                        case 5:

                                menu();

                                break;

                        }

                        break;

                case 2:

                        switch(opcion2) {

                        case 1:

                                AltaMision();

                                break;

                        case 2:

                                BajaMision();

                                break;

                        case 3:

                                ModMision();

                                break;

                        case 4:

                                ListMision();

                                break;

                        case 5:

                                menu();

                                break;

                        }

                        break;

                case 3:

                        break;

                }

        } while (opcion!=5);

}

Listo, de esta manera el menú de abajo se compartirá, pues solo llamaremos a funciones diferentes en el segundo menú via opcion2 dependiendo de la variable opcion y dentro de los ciclos manejaremos la entrada de una forma apropiada, ya que, mientras la opción ingresada **NO** sea un 5 el menú seguirá apareciendo o simplemente se morirá así que el problema del usuario está resuelto. O simplemente se morirá así que el problema del usuario está resuelto.

Todo perfecto, ahora necesitamos una función para dar de alta a los Héroes, necesitaremos de un lugar especial donde guardaremos los datos de los héroes.

Alta

Como somos todos unos expertos en Magic-C11 utilizaremos archivos binarios y guardaremos todo al final de la función, con ello aseguramos que cada entrada será única y por lo tanto podría considerarse un registro separado de los demás que podrá borrarse en cualquier momento.

Para la función AltaHeroes decidímos hacer lo siguiente:

void AltaHeroes()

{

        FILE *pf;

        Heroe heroes;

        pf = fopen("Heroes.dat","ab");



        printf("Ingrese el id del heroe\n");

        scanf("%i" ,&heroes.id);



        printf("Ingrese el nombre del heroe\n");

        scanf("%s", heroes.nombre);



        printf("Ingrese el apodo del heroe\n");

        scanf("%s", heroes.apodo);



        printf("Ingrese la clase del heroe\

 (Ladrón, Caballero, Hechicero, etc)\n");

        scanf("%s", heroes.clase);



        printf("Ingrese la habilidades que el héroe posee\n");

        scanf("%s", heroes.habilidades);



        fseek(pf, 0L, SEEK_END);

        fwrite(&heroes, sizeof(Heroe), 1, pf);

        fclose(pf);

        

        printf("\n\n\n\n\n\n\n\n\n\n\n\n");

        menu();

}

Ok, una vez se llama a la función crearemos un apuntador a un archivo y un nuevo “Héroe”, luego crearemos un archivo binario y posteriormente pasaremos a pedir la entrada de datos del usuario para nuestro héroe, después pasamos a utilizar la función fseek(), la cual posicionará un “cursor” (no realmente) dentro del archivo en la posición que deseemos.

fseek() Sitúa el puntero de lectura/escritura de un archivo en la posición indicada.

La función requiere, en primer lugar, el handle o identificador de archivo devuelto por la función fopen() al abrirlo.

En segundo lugar se especifica la nueva posición en la que debe situarse el puntero del archivo. Tras llamar a esta función, todas las operaciones de lectura o escritura que se efectúen, lo harán a partir de esta posición.

Como último parámetro se debe indicar el modo en el que se especifica la nueva posición del puntero, puede ser uno de estos tres:

SEEK_SET // Posición respecto al inicio del archivo  (0)

SEEK_CUR // Incremento relativo a la posición actual (1)

SEEK_END // Posición respecto al final del archivo   (2)

Finalmente escribimos los cambios en el archivo con fwrite().

Oh, antes de que se nos olvide, SIEMPRE CIERREN SUS PUNTEROS A ARCHIVOSfclose() no se llama solo y no existe compilador que cierre los archivos de forma automática así que, a menos de que odien su memoria volátil y sus unidades de almacenamiento, sugerimos que cuiden mucho esa parte.

Luego de imprimir un centenar de newlines podemos volver a llamar a la función menu para una nueva instrucción.

Listado

Ahora vamos a crear una pequeña función para listar los héroes que el usuario haya ingresado, en caso de no ingresar nada solo se mostrará una pantalla vacía así que nos ahorramos el problema de trabajar con arreglos.

void ListHeroes()

{

        FILE *pf;

        Heroe heroes;

        pf = fopen("Heroes.dat","rb");

        fread(&heroes, sizeof(Heroe), 1, pf);

        while (!feof(pf)) {

                printf("%i ; %s ; %s ; %s ; %s\n",heroes.id,heroes.nombre,

                       heroes.apodo,heroes.clase,heroes.habilidades);

                fread(&heroes, sizeof(Heroe), 1, pf);

        }

        fclose(pf);

}

Listo, abrimos nuestro archivo, leemos todas las entradas y nos mantendremos imprimiendo todas las entradas del archivo, al menos hasta llegar a la línea final del mismo.

Posteriormente cerramos el archivo abierto y la función termina.

Modificación de datos

Podemos ingresar y listar datos pero…¿qué pasa si nos equivocamos? ¿Sería el fin del mundo?

Esperemos que no, así que hagamos una función para modificar las entradas anteriores.

void ModHeroes()

{

        FILE *pf,*pfaux;

        Heroe heroes;

        int codigoaux;



        pf = fopen("Heroes.dat","rb");

        pfaux = fopen("Heroesaux.dat","ab");



        printf("Ingrese el ID a modificar\n");

        scanf("%i",&codigoaux);

        fread(&heroes, sizeof(Heroe), 1, pf);



        while (!feof(pf)) {

                if (heroes.id != codigoaux) {

                        fseek(pfaux,0l,SEEK_END);

                        fwrite(&heroes,sizeof(Heroe),1,pfaux);

                } else {

                        printf("Ingrese el nombre\n");

                        scanf("%s",heroes.nombre);

                        printf("Ingrese un nuevo apodo\n");

                        scanf("%s",heroes.apodo);

                        printf("Ingrese la clase\n");

                        scanf("%s",heroes.clase);

                        printf("Ingrese las habilidades\n");

                        scanf("%s",heroes.habilidades);

                        fseek(pfaux, 0l, SEEK_END);

                        fwrite(&heroes, sizeof(Heroe), 1, pfaux);

                }

                fread(&heroes,sizeof(Heroe),1,pf);

        }

        fclose(pf);

        fclose(pfaux);

        remove("Heroes.dat");

        rename("Heroesaux.dat","Heroes.dat");

}

La función de modificación es muy parecida a la de altas, solo que aquí no

pediremos un cambio en el ID del héroe pues establecimos que sería único, además de que lo utilizaremos como un término de búsqueda para encontrar el registro que deseamos cambiar en nuestro pequeño archivo binario, además de que creamos dos archivos, uno de ellos es un archivo auxiliar donde se escriben los cambios y en su lugar reemplaza al archivo anterior.

OBLITERANDO HEROES

Si tenemos héroes cobardes tendremos que darles la opción para correr, así que crearemos una función para dar de baja a uno o varios héroes de nuestro archivo binario: 

void BajaHeroes()

{

        FILE *pf,*pfaux;

        Heroe heroes;

        int codigoaux;



        pf = fopen("Heroes.dat","rb");

        pfaux = fopen("Heroesaux.dat","ab");



        printf("Ingrese el ID a buscar\n");

        scanf("%i",&codigoaux);



        fread(&heroes, sizeof(Heroe), 1, pf);

        while (!feof(pf)) {

                if (heroes.id != codigoaux) {

                        fseek(pfaux, 0l, SEEK_END);

                        fwrite(&heroes, sizeof(Heroe), 1, pfaux);

                }

                fread(&heroes,sizeof(Heroe),1,pf);

        }



        fclose(pf);

        fclose(pfaux);

        remove("Heroes.dat");

        rename("Heroesaux.dat","Heroes.dat");

}

Al igual que la función de modificación necesitaremos crear un archivo auxiliar, buscar el ID del héroe que deseamos eliminar y finalmente buscar por todo el archivo hasta dar con el héroe que deseamos eliminar y finalmente eliminamos el bloque completo, cerramos nuestros punteros y reemplazamos los archivos.

Conclusión

Luego de crear todas nuestras funciones podemos compilar nuestro archivo y comenzar a probarlo sin pena ni gloria.

En nuestro caso compilamos el archivo de manera manual utilizando el comando: gcc -Wall -Wpedantic -Wextra --std=c11 main.c

C11 está disponible en gcc8 y clang-8, en caso de no contar con el, puedes cambiarlo a C99

Si, estamos consciente de que no cubrimos la parte de las “Misiones” en el blog y lo hicimos de manera completamente intencional, pues la diferencia entre funciones es mínima y básicamente solo es cambiar funciones printf y scanf de modo que les sirva de ejercicio de práctica para que mejoren sus habilidades de programación en C.

Pueden encontrar el código fuente aquí

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

A %d blogueros les gusta esto: