Estructuras y Uniones en C/C++. En programación, especialmente en C y C++, cuando trabajamos con datos complejos o relacionados entre sí, necesitamos algo más que variables sueltas o arrays. Aquí es donde entran en juego las estructuras (struct
) y las uniones (union
), dos herramientas para organizar y manipular conjuntos de datos heterogéneos (es decir, de diferentes tipos) de manera ágil y sencilla.
Este capítulo se centra en presentar estas herramientas, mostrar cómo se declaran, acceden y manipulan, y qué ventajas ofrecen en el diseño de programas más estructurados y legibles.
Veremos también cómo el uso de typedef
para crear alias de tipos personalizados, cómo funcionan las estructuras anidadas (estructuras dentro de estructuras), y cómo aprovechar los tipos enumeraciones (enum
). Empezamos!!!!
7.1 Definición y uso de estructuras (struct
)
¿Qué es una estructura?
Una estructura en C/C++ (struct
) es una forma de agrupar variables de distintos tipos bajo un mismo nombre. Esto nos permite representar una entidad con múltiples atributos, por ejemplo, una entidad persona que tiene nombre, edad, y altura, todo en una única variable estructurada.
Esto es especialmente útil cuando los datos están relacionados pero no son del mismo tipo. Las estructuras permiten un diseño más limpio y modular.
Sintaxis general
struct NombreDeLaEstructura {
tipo_dato1 campo1;
tipo_dato2 campo2;
...
};
Una vez definida la estructura, hay que declarar la variables o variables del tipo de esa estructura:
struct NombreDeLaEstructura variable1;
struct NombreDeLaEstructura variable2;
...
struct NombreDeLaEstructura variableN;
Veamos un ejemplo, supongamos que queremos modelar un alumno con los siguientes datos: nombre, edad y nota media.
#include <stdio.h>
struct Alumno {
char nombre[50];
int edad;
float notaMedia;
};
int main() {
struct Alumno alumno1;
// Asignación de campos
strcpy(alumno1.nombre, "Laura");
alumno1.edad = 20;
alumno1.notaMedia = 8.5;
// Mostrar los datos
printf("Nombre: %s\n", alumno1.nombre);
printf("Edad: %d\n", alumno1.edad);
printf("Nota media: %.2f\n", alumno1.notaMedia);
return 0;
}
Explicación:
- Hemos definido una estructura llamada
Alumno
con tres campos. - Declaramos una variable de tipo
struct Alumno
llamadaalumno1
. - Asignamos valores a cada campo de
alumno1
. - Usamos
strcpy()
para copiar la cadena en el camponombre
(porque no podemos hacer asignación directa de strings en C). - Finalmente, mostramos los valores almacenados.
Inicialización de estructuras
También podemos inicializar una estructura al momento de declararla:
struct Alumno alumno2 = {"Carlos", 22, 9.1};
Acceso a los miembros de una estructura
Usamos el operador .
para acceder a los campos individuales de una variable estructurada:
alumno2.edad = 23;
printf("Nombre: %s\n", alumno2.nombre);
Arreglos de estructuras
Podemos declarar arrays de estructuras para manejar múltiples elementos similares:
struct Alumno clase[2] = {
{"Ana", 21, 7.8},
{"Luis", 22, 9.0}
};
for(int i = 0; i < 2; i++) {
printf("Alumno %d: %s - Edad: %d - Nota: %.1f\n", i+1,
clase[i].nombre, clase[i].edad, clase[i].notaMedia);
}
Esto nos permite recorrer una colección de objetos complejos con facilidad.
Paso de estructuras a funciones
A ser variables, también se pueden pasar estructuras como argumento a una función, por valor o por referencia:
void mostrarAlumno(struct Alumno a) {
printf("Nombre: %s, Edad: %d, Nota: %.2f\n", a.nombre, a.edad, a.notaMedia);
}
Es importante destacar que si necesitamos modificar los valores dentro de la función, debemos pasar un puntero de esa variable:
void modificarEdad(struct Alumno *a) {
a->edad += 1; // usamos -> para acceder a campos mediante puntero
}
7.2 Acceso a miembros de una estructura
Una vez que hemos definido una estructura, el siguiente paso lógico es acceder a los datos almacenados en sus miembros. Este proceso es muy sencillo, pero varía dependiendo de cómo estemos trabajando con la estructura: si estamos accediendo directamente a una variable estructurada o si lo hacemos a través de un puntero a estructura. Veamos como utilizarlo en ambos casos:
Acceso mediante el operador punto (.
)
El operador punto (.
) se utiliza cuando tienes una variable de estructura directa (es decir, no es un puntero). Con este operador puedes acceder y modificar los valores de sus campos.
Sintaxis
estructura.miembro
Ejemplo:
#include <stdio.h>
#include <string.h>
struct Persona {
char nombre[50];
int edad;
};
int main() {
struct Persona p1;
// Asignamos valores
strcpy(p1.nombre, "Andrés");
p1.edad = 30;
// Accedemos a los miembros
printf("Nombre: %s\n", p1.nombre);
printf("Edad: %d\n", p1.edad);
return 0;
}
Explicación:
p1.nombre
yp1.edad
son los campos a los que accedemos con el operador punto.- Usamos
strcpy
porque el camponombre
es un arreglo de caracteres. - Modificamos y leemos los valores fácilmente con este operador.
Acceso mediante punteros: operador flecha (->
)
Cuando trabajamos con un puntero a una estructura, ya no podemos usar el operador punto (.
) directamente, porque el puntero no es la estructura en sí, sino una referencia a la dirección de memoria donde se aloja esa estructura. Para esto, usamos el operador flecha (->
), que es una forma abreviada de desreferenciar el puntero y acceder a su miembro.
Sintaxis
punteroEstructura->miembro
Esto equivale a:
(*punteroEstructura).miembro
Ejemplo:
#include <stdio.h>
#include <string.h>
struct Persona {
char nombre[50];
int edad;
};
int main() {
struct Persona p2;
struct Persona *ptr = &p2;
// Asignamos valores usando puntero
strcpy(ptr->nombre, "Lucía");
ptr->edad = 25;
// Mostramos valores usando puntero
printf("Nombre: %s\n", ptr->nombre);
printf("Edad: %d\n", ptr->edad);
return 0;
}
Explicación:
ptr
es un puntero a una estructura de tipoPersona
.ptr->nombre
accede al camponombre
del objeto apuntado porptr
.- Si lo hiciéramos con
(*ptr).nombre
también funcionaría, pero es más largo y menos legible.
Veamos un ejemplo combinando ambos casos:
struct Persona persona;
struct Persona *ptr = &persona;
strcpy(persona.nombre, "David");
persona.edad = 40;
strcpy(ptr->nombre, "María");
ptr->edad = 35;
Acceso dentro de funciones
Es muy común pasar estructuras o punteros a estructuras a funciones para modificar o leer su contenido.
Pasanda por valor (usamos .
):
void mostrar(struct Persona p) {
printf("%s tiene %d años\n", p.nombre, p.edad);
}
Y si paso por referencia (usamos ->
):
void modificarEdad(struct Persona *p) {
p->edad += 1;
}
7.3 Estructuras Anidadas
¿Qué son las estructuras anidadas?
Una estructura anidada es una estructura que contiene otra estructura como uno de sus miembros. Es una forma de organizar datos complejos jerárquicamente, muy útil cuando un objeto está compuesto por varios subcomponentes.
Este concepto es similar a cómo en la vida real una dirección puede estar compuesta por calle, número, ciudad y código postal: todos esos datos pueden agruparse en una estructura Direccion
, que a su vez puede formar parte de una estructura Persona
.
Sintaxis
struct Interna {
// campos de la estructura interna
};
struct Externa {
// otros campos
struct Interna miembro;
};
Veamoslo con un ejemplo
#include <stdio.h>
#include <string.h>
struct Direccion {
char calle[50];
int numero;
char ciudad[50];
};
struct Persona {
char nombre[50];
int edad;
struct Direccion direccion; // estructura anidada
};
int main() {
struct Persona p;
strcpy(p.nombre, "Carlos");
p.edad = 28;
// Asignar campos de la estructura anidada
strcpy(p.direccion.calle, "Avenida Central");
p.direccion.numero = 123;
strcpy(p.direccion.ciudad, "Madrid");
// Imprimir todos los datos
printf("Nombre: %s\n", p.nombre);
printf("Edad: %d\n", p.edad);
printf("Direccion: %s, %d - %s\n", p.direccion.calle, p.direccion.numero, p.direccion.ciudad);
return 0;
}
Explicación
struct Direccion
define una dirección con tres campos.struct Persona
incluye un campodireccion
de tipostruct Direccion
.- Se accede a los miembros internos usando
p.direccion.calle
,p.direccion.numero
, etc. - Este sistema permite una mejor organización y agrupación lógica de los datos.
Anidación profunda
Es posible anidar estructuras dentro de otras estructuras varias veces.
struct Fecha {
int dia, mes, anio;
};
struct Libro {
char titulo[100];
struct Fecha publicacion;
};
¿Y cómo lo usamos?
struct Libro l;
strcpy(l.titulo, "Cien años de soledad");
l.publicacion.dia = 5;
l.publicacion.mes = 6;
l.publicacion.anio = 1967;
7.4 Uniones (union
) en C/C++
¿Qué es una unión?
Una unión (union) es un tipo de estructura de datos que permite almacenar diferentes tipos de datos en la misma ubicación de memoria. A diferencia de una struct
, donde cada miembro tiene su propio espacio de memoria, todos los miembros de una unión comparten la misma dirección de memoria, lo que significa que solo uno de los miembros puede contener un valor válido a la vez.
Sintaxis
union NombreUnion {
tipo1 miembro1;
tipo2 miembro2;
...
};
Ejemplo:
#include <stdio.h>
union Dato {
int i;
float f;
char c;
};
int main() {
union Dato d;
d.i = 42;
printf("Entero: %d\n", d.i);
d.f = 3.14;
printf("Float: %.2f\n", d.f);
d.c = 'A';
printf("Caracter: %c\n", d.c);
// Al final, solo el último valor asignado es válido
printf("\nEstado final de los miembros:\n");
printf("i: %d\n", d.i); // Resultado no predecible
printf("f: %f\n", d.f); // Resultado no predecible
printf("c: %c\n", d.c); // Correcto, último valor asignado
return 0;
}
Explicación
union Dato
tiene tres miembros:i
,f
yc
.- Cada uno ocupa el mismo espacio de memoria.
- Solo el valor del último miembro asignado se considera válido.
- Las otras variables pueden contener datos corruptos o irrelevantes.
¿Por qué usar uniones?
Las uniones son útiles cuando:
- Solo necesitamos almacenar un valor de varios posibles.
- Quieres ahorrar memoria, especialmente en sistemas embebidos y de pocos recursos de memoria.
- Necesitas interpretar los mismos datos de formas distintas.
Ejemplo práctico: sensor con diferentes tipos de lectura
#include <stdio.h>
#include <string.h>
union ValorSensor {
int entero;
float decimal;
char texto[10];
};
struct Sensor {
char tipo[10];
union ValorSensor valor;
};
int main() {
struct Sensor s;
strcpy(s.tipo, "Temperatura");
s.valor.decimal = 23.5;
printf("Sensor tipo: %s\n", s.tipo);
printf("Valor: %.1f\n", s.valor.decimal);
return 0;
}
Aquí, el Sensor
puede usar la unión para contener un número entero, un flotante o una cadena de texto, dependiendo del tipo de sensor.
Veamos mediante una tabla cual es la diferencia entre struct
y union
7.5 Tipos definidos por el usuario (typedef
, enum
)
En C/C++, además de los tipos de datos básicos (como int
, float
, char
), el lenguaje permite crear tipos de datos personalizados que pueden hacer el código más legible, modular y fácil de mantener. Para ello, se utilizan:
typedef
→ para crear alias de tipos existentes.enum
→ para definir conjuntos de constantes enteras con nombre.
1. typedef
: Alias de tipo
El typedef
(abreviación de “type definition”) permite darle un nuevo nombre a un tipo de datos existente. No crea un nuevo tipo, sino que facilita la lectura del código, sobre todo cuando trabajamos con tipos complejos como punteros o estructuras.
Sintaxis:
typedef tipo_original nuevo_nombre;
Ejemplo:
typedef unsigned int uint; uint edad = 25;
Aquí,uint
es simplemente otro nombre paraunsigned int
.
Ejemplo con estructuras:
#include <stdio.h>
typedef struct {
char nombre[50];
int edad;
} Persona;
int main() {
Persona p1 = {"Carlos", 30};
printf("Nombre: %s\n", p1.nombre);
printf("Edad: %d\n", p1.edad);
return 0;
}
Sin typedef
, tendríamos que declarar la variable con el comando struct delante, así:
struct Persona p1;
Con typedef
, podemos usar simplemente Persona
.
Persona p1;
Ventajas de typedef
:
- Hace el código más limpio y fácil de entender.
- Permite abstraer tipos complejos.
- Muy útil cuando se usan punteros a funciones, estructuras grandes o estructuras anidadas.
2. enum
: Enumeraciones
Una enumeración (enum
) es un tipo definido por el usuario que consiste en un conjunto de constantes enteras con nombre. Es ideal para representar estados, opciones o valores que pertenecen a una categoría cerrada.
Sintaxis:
enum NombreEnum {
VALOR1,
VALOR2,
VALOR3
};
Por defecto, a cada constante se le asigna un valor entero secuencial, comenzando por 0 (a menos que se especifique otro valor).
Ejemplo:
#include <stdio.h>
enum DiaSemana { LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO };
int main() {
enum DiaSemana hoy = MIERCOLES;
if (hoy == MIERCOLES) {
printf("Hoy es miércoles.\n");
}
return 0;
}
Según hemos comentado, MIERCOLES
vale 2, ya que empieza desde 0.
Y para asignar valores manualmente se hace a la hora de definir el tipo enumerado:
enum Estado { APAGADO = 0, ENCENDIDO = 1, EN_ESPERA = 5 };
Ventajas de enum
:
- Mejora la claridad del código al reemplazar números mágicos por nombres con sentido.
- Facilita el uso de estados o constantes categorizadas.
- Se puede usar con
switch
,if
, arrays, etc.
Combinar enum
con typedef
:
typedef enum {
ROJO,
VERDE,
AZUL
} Color;
Color c1 = VERDE;
Esto hace que Color
sea un nuevo tipo de dato más legible.
Comparación entre typedef
y enum
:
Característica | typedef | enum |
---|---|---|
¿Qué hace? | Crea un alias de tipo | Crea un nuevo tipo con constantes |
Tipo de datos | Cualquiera | Enteros únicamente |
Uso principal | Legibilidad, simplificación de tipos | Representación de categorías/estados |
Puede combinarse con | struct , union , tipos complejos | Sí, para mayor legibilidad |
Veamos un ejemplo práctico combinando los dos:
#include <stdio.h>
typedef struct {
char nombre[30];
int edad;
} Persona;
typedef enum {
INACTIVO,
ACTIVO,
BLOQUEADO
} EstadoCuenta;
int main() {
Persona cliente = {"Laura", 28};
EstadoCuenta estado = ACTIVO;
printf("Cliente: %s\n", cliente.nombre);
printf("Estado de la cuenta: %d\n", estado);
return 0;
}
Recapituloando
En este capítulo hemos vistgo como crear y manejar tipos de datos personalizados. Comenzamos con las estructuras (struct
), que permiten agrupar múltiples variables (de distintos tipos) bajo un mismo nombre, facilitando así la organización y el manejo de datos relacionados. Vimos cómo se accede a sus miembros, tanto directamente como a través de punteros, y exploramos también estructuras anidadas, que permiten componer estructuras dentro de otras.
También conocimos las uniones (union
), similares a las estructuras pero compartiendo el mismo espacio de memoria entre sus miembros, muy útiles en contextos donde se necesita optimizar el uso de memoria.
Finalmente, profundizamos en los tipos definidos por el usuario, como typedef
, que nos permite crear alias de tipos para mejorar la legibilidad del código, y enum
, que nos ayuda a representar conjuntos de constantes enteras con nombres significativos.
Vayamos ahora con la parte práctica…
Ejercicio 1: Registro de estudiantes
Enunciado:
Define una estructura llamada Estudiante
que almacene el nombre, edad y promedio de un alumno. Crea un array de 3 estudiantes, pide al usuario que introduzca sus datos y luego muestra en pantalla el listado.
Enunciado:
Define una estructura llamada
Estudiante
que almacene el nombre, edad y promedio de un alumno. Crea un array de 3 estudiantes, pide al usuario que introduzca sus datos y luego muestra en pantalla el listado.Código:
#include <stdio.h>
#define NUM_ESTUDIANTES 3
struct Estudiante {
char nombre[50];
int edad;
float promedio;
};
int main() {
struct Estudiante alumnos[NUM_ESTUDIANTES];
for (int i = 0; i < NUM_ESTUDIANTES; i++) {
printf("\nIntroduce datos del estudiante %d:\n", i + 1);
printf("Nombre: ");
scanf(" %[^\n]", alumnos[i].nombre); // Leer con espacios
printf("Edad: ");
scanf("%d", &alumnos[i].edad);
printf("Promedio: ");
scanf("%f", &alumnos[i].promedio);
}
printf("\n--- Lista de Estudiantes ---\n");
for (int i = 0; i < NUM_ESTUDIANTES; i++) {
printf("Estudiante %d:\n", i + 1);
printf("Nombre: %s\n", alumnos[i].nombre);
printf("Edad: %d\n", alumnos[i].edad);
printf("Promedio: %.2f\n\n", alumnos[i].promedio);
}
return 0;
}
Explicación:
Este programa define una estructura para representar a un estudiante y luego crea un arreglo de 3 elementos. Mediante un bucle for
, se pide al usuario introducir los datos de cada alumno y luego se muestran ordenadamente. Se utiliza scanf(" %[^\n]", ...)
para permitir leer cadenas con espacios.
Ejercicio 2: Uso de una unión para representar un valor numérico
Enunciado:
Crea una union
llamada Numero
que pueda almacenar un int
o un float
. Introduce ambos valores y muestra qué ocurre al imprimirlos después de haber sobrescrito uno con otro.
Enunciado:
Crea una
union
llamada Numero
que pueda almacenar un int
o un float
. Introduce ambos valores y muestra qué ocurre al imprimirlos después de haber sobrescrito uno con otro.Código:
cCopyEdit#include <stdio.h>
union Numero {
int entero;
float decimal;
};
int main() {
union Numero valor;
valor.entero = 42;
printf("Valor entero: %d\n", valor.entero);
valor.decimal = 3.14;
printf("Valor decimal: %.2f\n", valor.decimal);
// Ahora intentamos leer el valor entero otra vez
printf("Valor entero (después de sobrescribir con decimal): %d\n", valor.entero);
return 0;
}
Explicación:
Este ejemplo ilustra el comportamiento de una unión: al asignar primero un valor entero y luego un decimal, el segundo valor sobrescribe el anterior. Por eso, al intentar imprimir el entero después de haber guardado un decimal, obtenemos un valor incorrecto (basura). Esto demuestra que todos los miembros de una unión comparten el mismo espacio de memoria.
🧪 Ejercicio 3: Usando typedef
y enum
para representar el estado de un pedido
Enunciado:
Usa enum
para representar el estado de un pedido (PENDIENTE
, ENVIADO
, ENTREGADO
). Utiliza typedef
para definir un tipo Pedido
con id, nombre del cliente y estado del pedido. Imprime el estado en texto.
Código:
#include <stdio.h>
typedef enum {
PENDIENTE,
ENVIADO,
ENTREGADO
} EstadoPedido;
typedef struct {
int id;
char cliente[50];
EstadoPedido estado;
} Pedido;
void mostrarEstado(EstadoPedido estado) {
switch (estado) {
case PENDIENTE: printf("PENDIENTE"); break;
case ENVIADO: printf("ENVIADO"); break;
case ENTREGADO: printf("ENTREGADO"); break;
default: printf("DESCONOCIDO"); break;
}
}
int main() {
Pedido p1 = {1001, "Juan Pérez", ENVIADO};
printf("ID del pedido: %d\n", p1.id);
printf("Cliente: %s\n", p1.cliente);
printf("Estado: ");
mostrarEstado(p1.estado);
printf("\n");
return 0;
}
Explicación:
Aquí usamos typedef
para simplificar los nombres de struct
y enum
, haciendo el código más limpio. El enumerado EstadoPedido
define tres valores posibles, y la función mostrarEstado()
convierte estos valores en texto comprensible para el usuario. Este enfoque es muy útil cuando se trabaja con estados o categorías predefinidas.
Ejercicio 3: Usando typedef
y enum
para representar el estado de un pedido
Enunciado:
Usa enum
para representar el estado de un pedido (PENDIENTE
, ENVIADO
, ENTREGADO
). Utiliza typedef
para definir un tipo Pedido
con id, nombre del cliente y estado del pedido. Imprime el estado en texto.
typedef
y enum
para representar el estado de un pedidoEnunciado:
Usa
enum
para representar el estado de un pedido (PENDIENTE
, ENVIADO
, ENTREGADO
). Utiliza typedef
para definir un tipo Pedido
con id, nombre del cliente y estado del pedido. Imprime el estado en texto.Código:
#include <stdio.h>
typedef enum {
PENDIENTE,
ENVIADO,
ENTREGADO
} EstadoPedido;
typedef struct {
int id;
char cliente[50];
EstadoPedido estado;
} Pedido;
void mostrarEstado(EstadoPedido estado) {
switch (estado) {
case PENDIENTE: printf("PENDIENTE"); break;
case ENVIADO: printf("ENVIADO"); break;
case ENTREGADO: printf("ENTREGADO"); break;
default: printf("DESCONOCIDO"); break;
}
}
int main() {
Pedido p1 = {1001, "Juan Pérez", ENVIADO};
printf("ID del pedido: %d\n", p1.id);
printf("Cliente: %s\n", p1.cliente);
printf("Estado: ");
mostrarEstado(p1.estado);
printf("\n");
return 0;
}
Explicación:
Aquí usamos typedef
para simplificar los nombres de struct
y enum
, haciendo el código más limpio. El enumerado EstadoPedido
define tres valores posibles, y la función mostrarEstado()
convierte estos valores en texto comprensible para el usuario. Este enfoque es muy útil cuando se trabaja con estados o categorías predefinidas.
Bibliografía del tutorial de C/C++.
- C/C++. Curso de programación. Autor: Miguel Angel Acera (Editorial: Anaya Multimedia)
- C/C++. Curso de programación. Autor: Francisco José Ceballos (Editorial: RA-MA)
- Un recorrido por C++. Autor Bjarne Stroustrup (Editorial: Anaya Multimedia)
- 115 Ejercicios resueltos de programación C++. Autor Jorge Fernando Betancourt e Inma Yolanda Polanco (Editorial: RA-MA)