Un array contiene muchos valores de un tipo. Una struct hace lo contrario: contiene unos pocos valores de tipos distintos y los trata como una sola cosa. Un punto es una x y una y. Un empleado es un nombre, un identificador y un salario. Sin estructuras llevarías eso de un lado a otro como tres variables sueltas y esperarías no mezclar nunca de quién era cada salario.
Declarar un tipo estructura
La declaración de una estructura nombra una etiqueta y lista los miembros entre llaves:
struct Point {
int x;
int y;
};
El punto y coma tras la llave de cierre es obligatorio; olvidarlo produce uno de los mensajes de error más confusos con los que se topará un principiante de C, porque el compilador culpa a la línea siguiente.
Esto declara un tipo, no una variable. En C el nombre completo del tipo es struct Point, con la palabra clave incluida:
Los miembros se leen y se escriben con el operador punto, p.x. Una variable de tipo estructura es una variable corriente: vive en la pila, se puede asignar a otra estructura del mismo tipo con =, y desaparece al final de su ámbito.
Escribir struct Point en todas partes cansa, y por eso casi todo el código C real envuelve la declaración en un typedef para que el tipo sea simplemente Point.
Inicializar una estructura
Puedes llenar una estructura en el momento de declararla. La forma posicional lista los valores en orden de declaración:
struct Point p = {3, 4}; // x = 3, y = 4
El inicializador designado nombra cada miembro en su lugar, lo que es más claro y más seguro:
Tres cosas que vale la pena notar:
- La forma designada puede listar los miembros en cualquier orden, así que agregar o reordenar un miembro después no desplaza valores en silencio a las casillas equivocadas.
- Cualquier miembro que omitas queda inicializado a cero:
{0}es la forma idiomática de poner a cero una estructura entera. - Un miembro
char name[32]es un array real dentro de la estructura, así que la cadena se guarda en la estructura misma y no en otro sitio. Eso también significa quea.name = "Ada";después es ilegal; necesitasstrcpy(mira funciones de cadenas).
Una estructura sin inicializador queda sin inicializar, y leer sus miembros antes de escribirlos es comportamiento indefinido. = {0} no cuesta nada y elimina toda esa categoría de errores.
Asignar y comparar
La asignación de estructuras copia cada miembro:
a queda intacta: b recibió su propia copia. Pero fíjate en lo que C no te da: no hay == para estructuras. if (a == b) no compila. Comparar significa comparar miembros:
if (a.x == b.x && a.y == b.y) { /* iguales */ }
No recurras a memcmp como atajo. Los compiladores insertan bytes invisibles de relleno entre los miembros por alineación, y esos bytes pueden contener basura, así que dos estructuras con miembros idénticos pueden resultar distintas byte a byte.
Arrays de estructuras
Como una estructura es un tipo como cualquier otro, puedes hacer un array de ellas: la forma estándar de tener una tabla de registros.
staff[i].salary se lee como "el elemento i, y luego su miembro salary": la indexación se asocia más fuerte que el punto, así que no hacen falta paréntesis.
Las estructuras se copian al entrar en una función
Esta es la regla que atrapa a la gente. Cuando pasas una estructura a una función, C la copia. La función trabaja sobre su propia copia privada:
p sigue siendo (3, 4). Leer una estructura por valor, como hace distance_from_origin, está perfectamente bien y suele ser la opción más clara para estructuras pequeñas. Pero si la función debe cambiar la estructura de quien llama —o si la estructura es lo bastante grande como para que copiarla sea un derroche— pasa un puntero. Ese es el tema de estructuras y punteros.
Devolver una estructura por valor funciona igual y es completamente legal:
struct Point make_point(int x, int y) {
struct Point p = {x, y};
return p;
}
Estructuras anidadas
Un miembro de una estructura puede ser a su vez una estructura. Encadena los puntos para llegar dentro:
b.published.year entra un nivel a la vez. El anidamiento es como construyes modelos de datos reales en C, y la estructura interna se guarda dentro de la externa, no se apunta a ella, así que el Book entero es un único bloque contiguo de memoria.
Tamaño, relleno y orden de los miembros
sizeof te dice cuán grande es una estructura, y la respuesta suele ser mayor que la suma de sus miembros:
En una máquina típica Wasteful ocupa 12 bytes y Tidy ocupa 8, aunque ambas contienen los mismos tres miembros. El compilador inserta relleno para que cada miembro quede en una dirección que le guste a su tipo: un int normalmente quiere un múltiplo de 4. Agrupar los miembros grandes antes que los pequeños tiende a empaquetar mejor.
Esto importa pocas veces (un array de un millón de elementos, la disposición de un paquete de red) y nunca para la corrección de tu propio código. Lo que sí explica es por qué sizeof te sorprende, y por qué usar memcmp sobre estructuras es mala idea.
Preguntas frecuentes
¿Cómo se declara una struct en C?
Escribe struct seguido de un nombre de etiqueta y una lista de miembros entre llaves: struct Point { int x; int y; };. Eso declara un tipo, no una variable. Para crear una variable escribes luego struct Point p;: en C la palabra struct es parte del nombre del tipo, que es por lo que casi todo el código la combina con un typedef.
¿Cuál es la diferencia entre . y -> en las estructuras?
. y -> en las estructuras?Usa . cuando tienes la estructura en sí (p.x) y -> cuando tienes un puntero a ella (ptr->x). La flecha es una abreviatura de (*ptr).x. Mira estructuras y punteros para los detalles.
¿Las estructuras se pasan por valor o por referencia en C?
Por valor, siempre. Pasar una estructura a una función copia cada miembro, así que los cambios dentro de la función no afectan a la variable de quien llama. Para mutar el original —o para evitar copiar una estructura grande— pasa un puntero.
¿Cómo inicializo una struct en C?
O bien por posición, struct Point p = {3, 4};, o bien con inicializadores designados, struct Point p = {.y = 4, .x = 3};. La forma designada nombra cada miembro, así que sobrevive a que alguien reordene la estructura después y deja a cero los miembros sin nombrar.