Introducción
C no tiene clases, pero tiene structs — una forma de agrupar variables de distintos tipos bajo un mismo nombre. Son el mecanismo que permite modelar entidades del mundo real: una Persona con nombre y edad, un Producto con precio y stock, un nodo de una lista enlazada.
Combinadas con punteros y gestión dinámica de memoria, las structs son la base de todas las estructuras de datos clásicas: listas enlazadas, pilas, colas, árboles, tablas hash.
¿Qué vas a aprender en este lab?
- Declarar e inicializar structs
- Usar
typedefpara simplificar la sintaxis - Pasar structs a funciones por valor y por puntero
- Anidar structs
- Combinar structs con malloc para estructuras dinámicas
- Implementar una lista enlazada simple
Requisitos previos: fundamentos de C y punteros (especialmente
malloc,free, y aritmética de punteros).
1. Declaración básica
#include <stdio.h>
#include <string.h>
struct Persona {
char nombre[50];
int edad;
double altura;
};
int main(void) {
/* Declarar e inicializar con inicializador designado (C99) */
struct Persona julian = {
.nombre = "Julian",
.edad = 25,
.altura = 1.78
};
/* Acceso con el operador punto */
printf("Nombre: %s\n", julian.nombre); /* Julian */
printf("Edad: %d\n", julian.edad); /* 25 */
printf("Altura: %.2f\n", julian.altura); /* 1.78 */
/* Modificar un campo */
julian.edad = 26;
return 0;
}
sizeof de una struct
printf("Tamaño de Persona: %zu bytes\n", sizeof(struct Persona));
/* 50 + 4 + 8 = 62, pero el compilador puede añadir padding para alinear
→ normalmente 64 bytes */
2. typedef — simplificar la sintaxis
Sin typedef debes escribir struct Persona cada vez. Con typedef defines un alias:
typedef struct {
char nombre[50];
int edad;
double altura;
} Persona; /* Persona es ahora un tipo como int o double */
/* Ahora puedes escribir: */
Persona julian = { .nombre = "Julian", .edad = 25, .altura = 1.78 };
Persona ana = { .nombre = "Ana", .edad = 30, .altura = 1.65 };
Para structs que se referencian a sí mismas (como nodos de lista), necesitas el nombre de la struct:
typedef struct Nodo {
int valor;
struct Nodo *siguiente; /* apunta a otro Nodo — necesita el nombre */
} Nodo;
3. Structs y funciones
Paso por valor (copia)
void imprimir_persona(Persona p) {
printf("%-20s %3d años %.2fm\n", p.nombre, p.edad, p.altura);
}
/* Se pasa una copia completa — modificar dentro no afecta al original */
Persona julian = { .nombre = "Julian", .edad = 25, .altura = 1.78 };
imprimir_persona(julian);
Paso por puntero (referencia — eficiente y modificable)
void cumpleanios(Persona *p) {
p->edad++; /* operador flecha: equivale a (*p).edad++ */
}
void inicializar(Persona *p, const char *nombre, int edad, double altura) {
strncpy(p->nombre, nombre, sizeof(p->nombre) - 1);
p->nombre[sizeof(p->nombre) - 1] = '\0'; /* asegura terminación */
p->edad = edad;
p->altura = altura;
}
Persona p;
inicializar(&p, "Carlos", 28, 1.75);
cumpleanios(&p);
printf("%s tiene %d años\n", p->nombre, p->edad); /* Carlos tiene 29 años */
Regla de oro: pasa structs por puntero (
const Persona *ppara solo lectura,Persona *ppara modificar) — es más eficiente que copiar toda la struct.
4. Arrays de structs
#include <stdio.h>
#include <string.h>
typedef struct {
char nombre[30];
double precio;
int stock;
} Producto;
void imprimir_catalogo(const Producto *productos, int n) {
printf("%-25s %8s %6s\n", "Producto", "Precio", "Stock");
printf("%-25s %8s %6s\n", "--------", "------", "-----");
for (int i = 0; i < n; i++) {
printf("%-25s %8.2f %6d\n",
productos[i].nombre,
productos[i].precio,
productos[i].stock);
}
}
double valor_inventario(const Producto *productos, int n) {
double total = 0.0;
for (int i = 0; i < n; i++) {
total += productos[i].precio * productos[i].stock;
}
return total;
}
int main(void) {
Producto catalogo[] = {
{ .nombre = "Laptop", .precio = 1200.0, .stock = 5 },
{ .nombre = "Mouse", .precio = 25.0, .stock = 50 },
{ .nombre = "Teclado", .precio = 80.0, .stock = 30 },
{ .nombre = "Monitor", .precio = 400.0, .stock = 10 },
};
int n = sizeof(catalogo) / sizeof(catalogo[0]);
imprimir_catalogo(catalogo, n);
printf("\nValor total: $%.2f\n", valor_inventario(catalogo, n));
return 0;
}
Ejercicio 1
Amplía el programa de catálogo con:
buscar_por_nombre(Producto *cat, int n, const char *nombre)→ puntero al producto oNULLordenar_por_precio(Producto *cat, int n)→ ordena en su lugar conqsortaplicar_descuento(Producto *cat, int n, double porcentaje)→ reduce precio de todos
5. Structs anidadas
typedef struct {
double latitud;
double longitud;
} Coordenada;
typedef struct {
char nombre[50];
char pais[30];
int poblacion;
Coordenada ubicacion;
} Ciudad;
Ciudad bogota = {
.nombre = "Bogotá",
.pais = "Colombia",
.poblacion = 7412566,
.ubicacion = { .latitud = 4.7110, .longitud = -74.0721 }
};
printf("%s está en (%.4f, %.4f)\n",
bogota.nombre,
bogota.ubicacion.latitud,
bogota.ubicacion.longitud);
/* Bogotá está en (4.7110, -74.0721) */
6. Structs con memoria dinámica
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *nombre; /* puntero — tamaño dinámico */
int edad;
char **emails; /* array dinámico de strings */
int num_emails;
} Usuario;
Usuario *usuario_crear(const char *nombre, int edad) {
Usuario *u = malloc(sizeof(Usuario));
if (!u) return NULL;
u->nombre = malloc(strlen(nombre) + 1);
strcpy(u->nombre, nombre);
u->edad = edad;
u->emails = NULL;
u->num_emails = 0;
return u;
}
void usuario_agregar_email(Usuario *u, const char *email) {
u->emails = realloc(u->emails, (u->num_emails + 1) * sizeof(char *));
u->emails[u->num_emails] = malloc(strlen(email) + 1);
strcpy(u->emails[u->num_emails], email);
u->num_emails++;
}
void usuario_destruir(Usuario *u) {
if (!u) return;
free(u->nombre);
for (int i = 0; i < u->num_emails; i++) free(u->emails[i]);
free(u->emails);
free(u);
}
int main(void) {
Usuario *u = usuario_crear("Julian", 25);
usuario_agregar_email(u, "[email protected]");
usuario_agregar_email(u, "[email protected]");
printf("Usuario: %s (%d años)\n", u->nombre, u->edad);
for (int i = 0; i < u->num_emails; i++) {
printf(" Email %d: %s\n", i + 1, u->emails[i]);
}
usuario_destruir(u);
return 0;
}
7. Lista enlazada
Una lista enlazada es la primera estructura de datos dinámica que se construye con structs y punteros. Cada nodo contiene un valor y un puntero al siguiente nodo.
#include <stdio.h>
#include <stdlib.h>
typedef struct Nodo {
int valor;
struct Nodo *siguiente;
} Nodo;
/* Crear un nodo */
Nodo *nodo_crear(int valor) {
Nodo *n = malloc(sizeof(Nodo));
n->valor = valor;
n->siguiente = NULL;
return n;
}
/* Insertar al frente — O(1) */
Nodo *lista_insertar_frente(Nodo *cabeza, int valor) {
Nodo *nuevo = nodo_crear(valor);
nuevo->siguiente = cabeza;
return nuevo; /* la nueva cabeza */
}
/* Insertar al final — O(n) */
Nodo *lista_insertar_final(Nodo *cabeza, int valor) {
Nodo *nuevo = nodo_crear(valor);
if (!cabeza) return nuevo;
Nodo *actual = cabeza;
while (actual->siguiente) actual = actual->siguiente;
actual->siguiente = nuevo;
return cabeza;
}
/* Eliminar un valor */
Nodo *lista_eliminar(Nodo *cabeza, int valor) {
if (!cabeza) return NULL;
if (cabeza->valor == valor) {
Nodo *nueva_cabeza = cabeza->siguiente;
free(cabeza);
return nueva_cabeza;
}
Nodo *actual = cabeza;
while (actual->siguiente && actual->siguiente->valor != valor) {
actual = actual->siguiente;
}
if (actual->siguiente) {
Nodo *a_eliminar = actual->siguiente;
actual->siguiente = a_eliminar->siguiente;
free(a_eliminar);
}
return cabeza;
}
/* Imprimir */
void lista_imprimir(const Nodo *cabeza) {
for (const Nodo *n = cabeza; n; n = n->siguiente) {
printf("%d", n->valor);
if (n->siguiente) printf(" → ");
}
printf(" → NULL\n");
}
/* Liberar toda la lista */
void lista_destruir(Nodo *cabeza) {
while (cabeza) {
Nodo *siguiente = cabeza->siguiente;
free(cabeza);
cabeza = siguiente;
}
}
int main(void) {
Nodo *lista = NULL;
lista = lista_insertar_final(lista, 10);
lista = lista_insertar_final(lista, 20);
lista = lista_insertar_final(lista, 30);
lista = lista_insertar_frente(lista, 5);
lista_imprimir(lista); /* 5 → 10 → 20 → 30 → NULL */
lista = lista_eliminar(lista, 20);
lista_imprimir(lista); /* 5 → 10 → 30 → NULL */
lista_destruir(lista);
return 0;
}
Ejercicio 2
Amplía la lista enlazada con:
lista_longitud(Nodo *cabeza)→ número de nodoslista_invertir(Nodo *cabeza)→ invierte la lista en su lugar y devuelve la nueva cabezalista_buscar(Nodo *cabeza, int valor)→ puntero al nodo con ese valor oNULLlista_a_array(Nodo *cabeza, int *n)→ aloja un array dinámico con los valores y guarda el tamaño en*n
8. enum y union
enum — constantes con nombre
typedef enum {
LUNES = 1,
MARTES,
MIERCOLES,
JUEVES,
VIERNES,
SABADO,
DOMINGO
} DiaSemana;
DiaSemana hoy = MIERCOLES;
if (hoy == SABADO || hoy == DOMINGO) {
printf("Fin de semana\n");
} else {
printf("Día laboral: %d\n", hoy); /* 3 */
}
union — misma memoria, distintos tipos
Un union almacena varios tipos en el mismo espacio de memoria. Solo un campo está activo a la vez. Útil en protocolos de red, parseo binario y código de sistemas.
typedef union {
int entero;
float decimal;
char bytes[4];
} Valor32;
Valor32 v;
v.entero = 0x41200000; /* representación binaria de 10.0f en IEEE 754 */
printf("Como float: %f\n", v.decimal); /* 10.000000 */
printf("Primer byte: 0x%02X\n", (unsigned char)v.bytes[0]);
/* sizeof(union) = sizeof del miembro más grande */
printf("Tamaño: %zu\n", sizeof(Valor32)); /* 4 */
9. Errores Comunes
Error 1 — Usar struct después de free
Persona *p = malloc(sizeof(Persona));
strcpy(p->nombre, "Julian");
free(p);
/* p->nombre ahora apunta a memoria liberada */
printf("%s\n", p->nombre); /* comportamiento indefinido */
/* */
free(p);
p = NULL;
Error 2 — Copiar structs con punteros internos (shallow copy)
typedef struct { char *nombre; } Persona;
Persona original;
original.nombre = malloc(50);
strcpy(original.nombre, "Julian");
/* Copia superficial: ambas comparten el mismo puntero nombre */
Persona copia = original;
free(original.nombre); /* libera también el nombre de copia */
printf("%s\n", copia.nombre); /* USE AFTER FREE */
/* Copia profunda: duplica el string */
Persona copia;
copia.nombre = malloc(strlen(original.nombre) + 1);
strcpy(copia.nombre, original.nombre);
Error 3 — Olvidar liberar campos dinámicos antes de liberar la struct
/* Memory leak: el campo nombre no se libera */
free(usuario);
/* Libera los campos primero, luego la struct */
free(usuario->nombre);
free(usuario->emails);
free(usuario);
Resumen
| Concepto | Sintaxis |
|---|---|
| Declarar struct | typedef struct { ... } Nombre; |
| Acceso por valor | p.campo |
| Acceso por puntero | p->campo o (*p).campo |
| Struct dinámica | Nombre *p = malloc(sizeof(Nombre)); |
| Liberar | free(campos_dinámicos); free(p); |
| enum | typedef enum { A, B, C } Tipo; |
| union | typedef union { int i; float f; } U; |
Próximos pasos
- Listas dobles y circulares: extensiones naturales de la lista enlazada
- Árbol binario de búsqueda: structs recursivas con dos punteros hijo
- Tabla hash: combina arrays de punteros a listas enlazadas para acceso O(1)
- C para sistemas: sockets, señales, procesos con
forkyexec