C nos permite crear nuestro propio tipo de datos, esto lo logramos usando el keyword struct. Las estructuras son bastante similares a los arrays, ya que los podemos usar para almacenar y contener datos, pero la diferencia entre los dos, es que podemos crear distintos tipos de datos dentro de la misma estructura.
La sintaxis que seguimos para declarar una estructura es la siguiente:
struct nombre_estructura
{
/* el contenido de la estructura va aqui */
};
Lenguaje del código: CSS (css)
Como puedes ver, usamos el keyword struct seguido por el nombre de la estructura que estamos creando, luego abrimos un bloque de código y dentro de él, definimos los miembros de la estructura.
Para definir los miembros de una estructuras, solo hace falta definirlos como si estuvieramos creando variables normales. Por ejemplo, si quisieramos declarar una estructura que representa una fecha (y que contenga un día, mes y año), podríamos crear una estructura así:
struct fecha
{
int dia;
int mes;
int ano;
};
Si quisieramos representar esa fecha, necesitaríamos declarar tres variables diferentes, una por cada uno de los miembros de la estructura:
/* usando variables */
int dia = 1
int mes = 12;
int ano = 2024;
Lenguaje del código: C++ (cpp)
Una vez hayamos creado nuestra propia estructura, podemos crear instancias de ella usando struct <nombre_estructura> como el tipo de la variable, en nuestro caso sería struct fecha:
struct fecha hoy = { 0 };
Lenguaje del código: C++ (cpp)
Como puedes ver, la inicialización es como si estuvieramos definiendo un array, en este caso estamos asignando todos sus miembros a cero.
El tamaño de nuestra variable hoy es la suma de los tamaños de todos los miembros de la estructura, en este caso, si suponemos que el tamaño de un int son 4 bytes, si hicieramos sizeof (hoy) devolvería 12.
Si quisieramos acceder a un miembro de nuestra variable hoy, usaríamos el operador member selection (.) así:
hoy.dia = 1;
hoy.mes = 12;
hoy.ano = 2024;
En ese snippet estamos asignando los miembros dia, mes y ano de la variable hoy a los valores que representan la fecha de hoy. Y podemos acceder a cada uno de los miembros usando el operador member of, y ese valor lo podemos usar en expresiones, para hacer comparaciones, y básicamente, lo podemos tratar como si fuera una variable común y corriente (porque lo es), algunos ejemplos:
int siglo = hoy.ano / 100;
if (hoy.ano == 2025)
printf ("Como vuela el tiempo!");
Lenguaje del código: C++ (cpp)
Declarando una estructura
Hay varias formas en las que podemos declarar una estructura en C. podemos declararla como lo hicimos en el ejemplo anterior:
struct fecha
{
int dia;
int mes;
int ano;
};
Lenguaje del código: C++ (cpp)
En esa forma de declarar la estructura, estamos usando el keyword struct, seguido por el nombre de la estructura y finalmente sus miembros.
También podemos crear una estructura e inmediatamente definir variables de este tipo, podemos hacer esto simplemente agregando los nombres de las variables después del corchete que cierra el bloque de código:
struct fecha
{
/* contenidos de fecha */
} ayer, hoy, manana;
Lenguaje del código: C++ (cpp)
Siguiendo el ejemploa nterior, ahora tenemos 4 cosas nuevas en nuestro programa: una estructura fecha, y tres instancias de este tipo llamadas ayer, hoy y manana.
La otra forma en la que podemos definir una estructura, es llamada estructura anónima. Este tipo de estructuras son definidas únicamente para declarar una variable, vamos a ver en código para que así sea más fácil de entender:
struct {
int dia, mes, ano;
} hoy;
En este caso estamos declarando una variable llamada hoy, que es una estructura que contiene tres miembros: dia, mes y ano. Incluso si tenemos una variable de este tipo, no tenemos una estructura para referenciar, es decir, no podemos crear más instancias de este tipo, y la única que existirá en el programa es hoy.
Inicializando Estructuras
Como se mencionó anteriormente, la forma en la que inicializamos una estructura es bastante similar a la forma en que inicializamos un array, si quisieramos definir nuestra estructura fecha, podríamos hacerlo de la siguiente forma:
struct fecha hoy = { 1, 12, 2024 };
Estamos definiendo una variable de tipo struct fecha llamada hoy y su dia es 1, su mes es 12, y su ano es 2024. Esto es bastante útil, porque de otra forma tendríamos que escribir tres líneas de código solamente para inicializar los miembros de esta estructura.
Hay una desventaja en inicializar las estructuras de esta forma, y es que necesitamos inicializar los elementos en orden, si hubiéramos hecho { 12, 1, 2024 } eso hace el día 12, el mes 1 y el ano 2024. Hay otra forma en la que podemos inicializar una estructura, y es especificando los nombres de sus miembros de la siguiente forma:
struct fecha hoy = { .dia = 1, .mes = 12, .ano = 2024 };
Esa inicialización nos da el mismo resultado que la de la forma pasada, pero de esta manera no tenemos que preocuparnos por el orden de los miembros de la estructura fecha, entonces podemos hacer esto:
struct fecha hoy = { .mes = 12, .ano = 2024, .dia = 1 };
Incluso si cambiamos el orden de los elementos que estamos inicializando, ya que estamos indicando a cuál le estamos dando un valor, podemos hacerlo en el orden que querramos.
Hay otra forma de asignarle valores a una variable, y es por medio de un compound literal:
hoy = (struct fecha){ 1, 12, 2024 };
Arrays de estructuras
Como con todos los tipos de datos que tenemos, podemos declarar arrays de estructuras. Vamos a suponer que queremos almacenar todos los días del mes. En lugar de crear 30 variables podemos simplemente crear un array de struct fecha:
struct fecha dias[30] = { 0 };
Lenguaje del código: C++ (cpp)
Y estas funcionan como si fueran estructuras normales una vez obtengamos una en específico usando su índice:
dias[0] = (struct fecha){ 1, 12, 2024 };
Ahí estamos inicializando el primer elemento del array dias con la fecha de especificada. Si quisieramos inicializar todos (o algunos) de los elementos del array, podemos hacerlo como si estuvieramos inicializando un array multidimensional:
struct fecha fechas_importantes[2] = {
{ 9, 5, 2005 },
{ 1, 1, 1970 }
};
Ahí estamos inicializando ambos elementos de fechas_importantes. Y naturalmente, podemos inicilizar estructuras en las tres formas que vimos anteriormente:
struct fecha fechas_random[10] = {
{ 9, 5, 2005 },
{ .dia = 1, .mes = 1, .ano = 1977 },
[5] = { 5, 1, 1969 }
};
Lenguaje del código: C++ (cpp)
Estructuras anidadas
También podemos tener estructuras dentro de estructuras. Siguiendo los ejemplos mostrados anteriormente, vamos a crear una estructura llamada date, otra llamada time y una última llamada datetime:
struct time
{
int hour;
int minute;
int second;
};
struct date
{
int day;
int month;
int year;
};
struct datetime
{
struct date;
struct time;
};
Lenguaje del código: C++ (cpp)
Y podemos acceder a cada una de las estructuras anidadas como si fueran miembros normales usando el operador member of:
struct datetime datetime = { 0 };
datetime.date.day = 16;
datetime.time.hour = 17;
Lenguaje del código: C++ (cpp)
Y de nuevo, podemos inicializar las estructuras de la mismas tres formas que vimos antes, la única diferencia es que aquí es como si fueran arrays multidimensionales:
struct datetime datetime = {
{ 1, 12, 2024 },
{ 17, 32, 00 }
};
Estructuras y pointers
En cuánto a Pointers y estructuras, podemos tener tanto pointers a estructuras y pointers como miembros de una estructura.
Pointers a una estructura
Podemos declarar pointers a una estructura, como lo haríamos con cualquier otro datatipo. Por ejemplo:
struct fecha *pfecha = &hoy;
Lenguaje del código: C++ (cpp)
Ahora, pfecha es un pointer que apunta a la variable hoy.
¿Por qué querríamos tener pointers a una estructura? Hay variables razones, pero la que considero más importante es: como ya sabemos, C usa pass by value cuando le pasamos un argumento a una función, lo que significa que cuando pasamos una variable a una función, en realidad estamos copiando su valor a la variable local representada por el argumento, y lo mismo pasa con las estructuras.
Si tenemos una estructura grande, y la pasamos normal (sin pointers) tendría que copiar cada uno de sus miembros a la estructura local a la función representada por el argumento. Sin embargo, cuando pasamos un pointer a una estructura como un argumento de una función, C va a pasar su valor, pero en este caso tiene que pasar solamente una dirección en lugar de una estructura entera.
Cuando tenemos un pointer a una estructura, como con todos los pointers, para acceder a sus contenidos tenemos que dereferenciarlos primero, lo podemos hacer de la siguiente forma:
(*pfecha).dia = 1;
Lenguaje del código: C++ (cpp)
Nótese como estamos encerrando la dereferencia de pfecha dentro de paréntesis, y lo hacemos, porque el operador member of tiene una precedencia más alta que el operador de dereferencia entonces necesitamos encerrarlo en paréntesis (ya que todo lo que está entre paréntesis será evaluado de primero, independientemente de la precedencia)
Dereferenciar una estructura cuando queremos acceder a alguno de sus miembros puede ser incómodo debido a su confusa sintaxis. Es por eso que tenemos un “atajo” en C que es el member of through a pointer (->), usar ese nuevo operador es lo mismo que dereferenciar una estructura y luego acceder a alguno de sus miembros, así que los dos siguientes estamentos son iguales:
(*pfecha).dia = 16;
pfecha->dia = 16;
Lenguaje del código: C++ (cpp)
Como es evidente, usar el operador member of through a pointer (->) es mucho más fácil, simple y rápdio que hacerlo de la primer forma.
Pointer como miembro
En una estructura también podemos tener pointers como miembros:
struct spointers
{
int *p1, *p2;
};
En la estructura spointers, podemos acceder a sus miembros p1 y p2 usando el operador member of (.) en lugar de usar -> ya que spointers no es un pointer, sino que sus elementos lo son.
También podemos usar arrays como elementos de una estructura:
struct nombres
{
char nombre[32];
char apellido[32];
};
Ambas estructuras son perfectamente válidas, por lo que podemos hacer lo siguiente:
struct nombres nombres = { "Lain", "Iwakura" };
Lenguaje del código: C++ (cpp)
Retornando estructuras de una función
Otra forma de probar que podemos tratar estructuras como cualquier datatipo en C, es que podemos retornar estructuras en funciones. Podemos hacer esto al especificar el tipo de la función y, claramente, retornando una estructura de ese tipo:
struct datetime
get_datetime (void)
{
return (struct datetime){ 0 };
}
Lenguaje del código: C++ (cpp)
También podemos retornar pointers de una función, solo agregando el operador * después del nombre de la estructura (igual como si definieramos el pointer de una estructura). Recuerda que si quieres retornar un pointer de una función, necesitas almacenar su memoria en el heap. Si haces lo siguiente, el programa podría fallar:
struct date *
pget_date (void)
{
struct date date = { 16, 12, 2023 };
return &date;
}
Lenguaje del código: C++ (cpp)
Eso podría llevar a errores, puesto que estamos devolviendo la dirección de una variable que fue creada en el stack, entonces cuando intentemos dereferenciarla fuera de la función pget_date, ese espacio en memoria no va a existir más.