Usar múltiples archivos
Los módulos suelen tener bastante código, por lo que dejar todo en un único main.c nos puede resultar difícil de mantener.
Para evitar esto, podemos utilizar múltiples archivos.
Ejemplo inicial
Supongamos que tenemos un main.c con un tipo t_persona:
#include <stdlib.h>
#include <stdio.h>
#include <commons/string.h>
typedef struct persona {
char *nombre;
char *apellido;
int edad;
} t_persona;
int main(void) {
t_persona *messi = malloc(sizeof(t_persona));
messi->nombre = string_duplicate("Lionel");
messi->apellido = string_duplicate("Messi");
messi->edad = 34;
printf("{ nombre: %s, apellido: %s, edad: %d }\n",
messi->nombre,
messi->apellido,
messi->edad
);
free(messi->nombre);
free(messi->apellido);
free(messi);
return 0;
}Sería más organizado convertir el tipo t_persona en un Tipo Abstracto de Dato (TAD) con las funciones asociadas:
persona_new: Que cree una instancia det_persona.persona_to_string: Que convierta una instancia det_personaa string para ser impresa por pantalla conprintf,putso cualquier función de logging.persona_destroy: Que libere toda la memoria ocupada por una instancia det_persona.
De esta forma, si en otra parte del código queremos crear a cr7, podemos reutilizar la misma lógica que usamos para crear a messi.
Crear un header
Para esto, primero crearemos un header en la carpeta src/ con nuestro editor favorito o desde la consola ejecutando:
touch src/persona.hEn él vamos a guardar los prototipos de las funciones del nuevo TAD t_persona:
#ifndef PROJECT_PERSONA_H_
#define PROJECT_PERSONA_H_
typedef struct persona t_persona;
// También podemos agregar Docstrings como incluyen las Commons.
/**
* @NAME: persona_new
* @DESC: Crea una instancia de "t_persona".
*/
t_persona *persona_new(char *nombre, char *apellido, int edad);
/**
* @NAME: persona_to_string
* @DESC: Convierte una instancia de "t_persona" a string.
*/
char *persona_to_string(t_persona *persona);
/**
* @NAME: persona_free
* @DESC: Libera la memoria ocupada por una instancia de "t_persona".
*/
void persona_free(t_persona *persona);
#endifTIP
Nótese que definimos al tipo t_persona como un struct persona pero no expusimos los atributos que lo conforman.[1]
Si bien esto es completamente opcional, si en Paradigmas vieron el concepto de encapsulamiento, esta es la forma más parecida de lograrlo en C, ya que impedimos que quienes incluyan este header manipulen los atributos del struct directamente.
Crear un archivo fuente
Luego, crearemos un archivo fuente:
touch src/persona.cY moveremos ahí la implementación de ese TAD, junto con todas las bibliotecas que necesita.
#include <persona.h>
#include <stdlib.h>
#include <stdio.h>
#include <commons/string.h>
struct persona {
char *nombre;
char *apellido;
int edad;
};
t_persona *persona_new(char *nombre, char *apellido, int edad) {
t_persona *persona = malloc(sizeof(t_persona));
persona->nombre = string_duplicate(nombre);
persona->apellido = string_duplicate(apellido);
persona->edad = edad;
return persona;
}
char *persona_to_string(t_persona *persona) {
return string_from_format(
"{ nombre: %s, apellido: %s, edad: %d }",
persona->nombre,
persona->apellido,
persona->edad
);
}
void persona_free(t_persona *persona) {
free(persona->nombre);
free(persona->apellido);
free(persona);
}También es una buena práctica incluir al inicio su header, como se puede ver al principio.
TIP
Los makefiles, por defecto, incluyen el flag -I./src a la hora de compilar el proyecto con gcc. De esta forma, se puede incluir cualquier header que se encuentre en esa carpeta y sus subcarpetas utilizando corchetes angulares < > como en el ejemplo de arriba.
Sin este flag, también se podría lograr el mismo resultado utilizando rutas relativas entre comillas:
#include "../src/persona.h"¿Cómo funciona el "#include"?
Por defecto, el compilador solamente va a buscar headers en las siguientes dos rutas:
/usr/include
/usr/local/includeSe pueden agregar más rutas pasándole a gcc el flag -I{path} o editando la variable de entorno C_INCLUDE_PATH.
Incluir el archivo fuente en el main.c
Por último, vamos a hacer que nuestra función main() utilice las funciones del nuevo TAD t_persona para crear a messi:
#include <stdlib.h>
#include <stdio.h>
#include <persona.h>
int main(void) {
t_persona *messi = persona_new("Lionel", "Messi", 34);
char* messi_str = persona_to_string(messi);
puts(messi_str);
free(messi_str);
persona_free(messi);
return 0;
}En conclusión, nos quedó un main() mucho más simple y con un TAD que nos permite crear nuevas instancias de t_persona en el proyecto de forma más sencilla.
.
└── src
├── main.c
├── persona.c
└── persona.hEsta forma de declarar tipos de conoce como Opaque Types, si quieren entrar más en detalle les recomiendo este video (en inglés). ↩︎
Create SisOp App