Programación En C: Estructuras

Este artículo es parte de una serie – Cómo programar cualquier cosa: C Programación

Prefacio

Hasta ahora hemos estado tratando con datos separados entre sí. Podemos declarar un int en una variable, un char en otra variable y matrices de muchos tipos diferentes de variables. Pero, ¿qué sucede cuando los datos están relacionados entre sí? Por ejemplo, digamos que tenemos un programa que realiza un seguimiento de una lista de empleados. Necesitamos saber la tarifa por hora de cada empleado y la cantidad de horas que han trabajado este período de pago. Podríamos configurar algo como lo siguiente:

Luego, cuando queríamos acceder a un empleado en particular, podíamos usar un número de índice específico. Entonces, por ejemplo, si quisiéramos saber sobre el empleado número 23, escribiríamos:

Puede parecer que funciona, pero este enfoque tiene importantes inconvenientes. En primer lugar, aunque todavía no lo hemos discutido, cada una de estas variables tendría que ser una variable global, si no quisiéramos pasar las tres a ninguna función que pueda operar con estos datos. En segundo lugar, cada conjunto está separado uno del otro, ¿qué ocurre si hay un error ortográfico en el código o algún otro error, y parte de la información del empleado del empleado 23 se lee accidentalmente o se escribe al empleado 25? Además de esto, si queremos cambiar el número de empleados posible en el programa, tenemos que editar tres lugares en el código para aumentar el número de empleado. Y, por último, digamos que usamos un bucle for con direccionamiento de puntero. Para poder acceder a todos los datos de un empleado en particular, tendríamos que actualizar tres punteros por separado en sus respectivas matrices. Todos estos problemas son problemáticos y torpes, y afortunadamente hay una mejor manera.

Estructuras

Existe una manera de combinar, por así decirlo, toda la información pertinente para un empleado dado en un tipo de datos. ¿Cómo? Por el uso de una estructura. Una estructura es un tipo de datos agregados, lo que significa que agrega o recopila datos dispares y los coloca bajo un encabezado. En nuestro caso, podemos reunir todos los elementos pertinentes de un empleado, como el nombre, la frecuencia y las horas, y ponerlos en un solo lugar como un tipo de datos. Puede parecerse a esto:

Ahora nuestro programa tendría un nuevo tipo de datos llamado empleado y de hecho podría crear nuevas variables usando este tipo de datos (en un momento). Este fragmento de código muestra una declaración de estructura. Tenga en cuenta aquí que en realidad no creamos ningún dato nuevo, simplemente especificamos campos de datos particulares en una plantilla que puede usarse para crear nuevas variables y datos. Esta declaración de estructura es, por lo tanto, solo una plantilla. Creamos o instanciamos registros de empleados particulares al declarar variables que son del tipo de la estructura. Aquí, por ejemplo, creamos una variable de tipo de empleado llamada robert:

Ahora tenemos una variable llamada robert que contiene una matriz de nombres, una tasa y un número de horas. Al igual que una matriz, cuando se crea Robert, el compilador configura automáticamente la memoria para que la estructura tenga todo el espacio que necesita para existir. Crea en el espacio contiguo la matriz de caracteres de 25 elementos, la variable de flotación y la variable entera. Puedes imaginarlo en la memoria así:

Tenga en cuenta que si tuviéramos que hacer otra variable de empleado, digamos john tendría su propia asignación de memoria. El campo de nombre en john no sería el mismo campo de nombre en Robert. Tanto John como Robert tienen su propia memoria y sus propios valores de campo.

En realidad, puede declarar las variables que contienen una estructura correcta en la propia declaración struct:

En este caso, robert, john y andrea serían todos variables independientes que contienen una estructura en la plantilla de empleado. Como puede ver, la declaración de estructura generalizada es la siguiente:

Una cosa buena de las estructuras es que puedes asignarlas entre sí (a condición de que las estructuras sean del mismo tipo) y todos los datos se transfieren de una estructura a otra. Por ejemplo, en nuestro ejemplo de estructura de empleados, podría escribir:

Y todos los datos que se mantienen en la variable de estructura john se copiarán en la variable de estructura de Robert. De esta manera, no tengo que copiar cada campo de la estructura a Robert, es solo un gran avance. ¿Pero cómo accedería a los campos de la estructura si dijera, solo quería la variable de nombre?

Accediendo a los campos de estructura

Al igual que los punteros, que tenían sus operadores * y &, las estructuras tienen sus propios operadores:. (punto) y -> (flecha). Primero examinaremos el operador de punto.

Cuando se tiene una variable que es de un tipo de estructura, se puede acceder a los campos de la estructura agregando un punto (.) Al nombre de la variable y luego especificando el nombre del campo dado en la estructura. En nuestro ejemplo de empleado, digamos que quería hacer referencia a la tasa por hora de nuestra variable de Robert. Yo escribiría algo como esto:

Esto accedería a la variable de velocidad de Robert. Recuerde que john, andrea y robert ocupan diferentes ranuras de memoria, y cada uno tiene sus propios valores para los campos definidos en la estructura. La plantilla generalizada para acceder a un campo de una variable de una estructura particular es:

Sin embargo, ¿qué ocurre si nuestra variable para una estructura particular es un puntero a una estructura? (Cubrimos los punteros en un artículo anterior). Para declarar un puntero a una estructura, coloque un asterisco después del tipo de datos y antes de la variable al igual que cuando se crean punteros para tipos de datos básicos. Digamos que tenemos lo siguiente en nuestro programa:

En este ejemplo, empPointer es un puntero a employeeVariable. Todavía podemos acceder a los elementos de la estructura a la que apunta el apuntador, sin tener que resolver el puntero ni usar el employeeVariable original. Esto se hace usando el operador de flecha (->). Eso es un guion seguido de un signo mayor que. El operador de flecha se usa igual que el operador de punto de arriba, excepto que se usa en punteros a estructuras. Entonces, por ejemplo, digamos que queríamos acceder a la tarifa por hora para el registro de empleado apuntado por empPointer. Escribimos algo como esto:

De esta manera, los punteros a las estructuras se pueden pasar hacia y desde las funciones a nuestro programa y aún podemos acceder a los campos. Si tuviéramos que resolver cada puntero antes de acceder a los campos de una estructura, podríamos encontrar algunas situaciones en las que no podríamos acceder a la estructura.

Arrays de Campo vs Arrays de Estructura

Es perfectamente válido definir una matriz de variables de estructura, pero debemos asegurarnos de saber qué estamos indexando. Tomemos nuestro ejemplo de empleado más allá y supongamos que queríamos una matriz (discutimos las matrices en un artículo anterior). Podríamos escribir algo como lo siguiente:

Aquí definimos una matriz titulada empleados como una secuencia larga de cientos de elementos de estructuras de empleados. Ahora, la estructura de empleados de arriba tiene una matriz dentro de ella en forma de nombre (una matriz de caracteres de 25 elementos). Digamos, en nuestra matriz, queremos acceder al número de empleado 25, o incluso a la tarifa por hora del número de empleado 25, que escribiríamos:

Tenga en cuenta que estamos indexando la matriz de elementos de estructura primero. Supongamos entonces que queremos acceder a la sexta letra del nombre del empleado número 15 en la matriz. De esta forma, indexamos primero el elemento de estructura y luego el segundo conjunto de campos:

Una matriz de estructuras no cambia las matrices que puedan estar contenidas dentro de la estructura, simplemente necesita acceder a cada matriz desde el exterior hacia adentro. Es decir, la matriz externa de la matriz de estructura, y luego la matriz interna después del punto operador.

Estructura dentro de las estructuras

De hecho, es posible construir estructuras que utilicen otras estructuras dentro de su plantilla. Esto a menudo se conoce como una estructura anidada. Digamos, por ejemplo, que queríamos definir una estructura de dirección y luego usar esa estructura de dirección en cada registro de nuestra estructura de empleados. Podríamos considerar escribir lo siguiente:

Aquí tenemos una estructura de direcciones ‘integrada’ o incluida en una estructura de empleado. En cuanto a la distribución de la memoria, cada estructura respeta su diseño, pero la estructura del empleado crea suficiente espacio en sus instancias para acomodar la estructura de direcciones. La estructura de direcciones actúa y funciona igual que una estructura regular en asignaciones y otras operaciones. La única notación especial en la que incurre este tipo de situación es una notación de puntos extra al acceder a los campos. En este sentido, tenemos que acceder a la estructura más externa y pasar a la estructura incrustada “más interna”. Un fragmento de código ilustra:

Puede ver que para acceder a la matriz zip de la estructura de direcciones incluida en la estructura del empleado tuvimos que usar el operador punto dos veces. Tenga en cuenta que cuando se trata de punteros a estructuras, podemos usar el operador de flecha donde normalmente aparece el operador de puntos.

Miembros de la Estructura de matriz flexible C99

C99, una de las versiones posteriores del lenguaje C, le permite especificar una matriz sin tamaño como el último miembro de una estructura. Recuerde que debe ser el último elemento, de lo contrario el compilador no estaría seguro de dónde colocar el resto de los elementos, ya que no sabría qué tan grande era la matriz. Esta matriz sin tamaño se conoce como un miembro de matriz flexible.

Definirías un campo como ese:

Esto puede causar varias cosas que aún no hemos cubierto para operar de manera diferente, particularmente el operador sizeof y la forma en que asignamos memoria para la estructura de forma dinámica. Esas advertencias se abordarán a medida que surjan.

Inicializadores designados C99

Con C99 también puede, al igual que con las matrices, designar campos de una estructura para tener un valor cuando se especifica la variable del tipo de datos de estructura. Lo haces con la siguiente sintaxis:

Esto se inserta en un paréntesis establecido después de la declaración de la variable, por ejemplo:

Esto inicializa los valores de la estructura a los valores dados tan pronto como se declara la variable. Recuerde, sin embargo, esta es solo una característica de C99.

Conclusión

Las estructuras son formas de encapsular datos relacionados, presumiblemente, bajo un tipo de datos. Esto nos permite organizar datos que están relacionados entre sí en un solo lugar. De lo contrario, todos nuestros datos tendrían que existir en variables separadas, y como programamos, tendríamos que recordar cómo se conectan. Esto es realmente propenso a un error drástico, y la memoria es enrevesada. Particularmente cuando llegamos a las funciones, o bien tenemos que pasar un número innumerable de variables todo el tiempo, o tenemos muchas variables globales. Las estructuras nos permiten juntar datos similares. Una dirección puede contener campos de dirección, una estructura de empleado puede contener información pertinente para un empleado. Las estructuras se vuelven aún más importantes cuando entramos en las estructuras de datos, ya que nos permiten adjuntar campos adicionales a los datos, como punteros de estructura siguiente o anterior.

Este artículo es parte de una serie – Cómo programar cualquier cosa: Programación C

Si usted aprecia este artículo usted puede ser que considere el apoyar de mi Patreon.

Pero si un compromiso mensual es un poco más, lo entiendo, podría considerar comprarme un café.

photo credit: Kᵉⁿ Lᵃⁿᵉ Ray and Maria Stata Center at MIT (Cambridge MA) via photopin (license)

También te podría gustar...

Deja un comentario

A %d blogueros les gusta esto: