¡Bienvenidas y bienvenidos a una nueva clase del curso de Programación Básica, el avance que has conseguido al llegar aquí es sensacional!
Introducción a los Arreglos
En esta ocasión nos enfocaremos a entender el concepto de arreglos de variables. Comencemos definiendo el concepto de arreglo: un arreglo es un conjunto ordenado de variables del mismo tipo, las variables comparten un nombre en común, pero son identificadas unas con otras con un número.
Si necesitas manipular muchas variables del mismo tipo que representan cosas semejantes, por ejemplo, las estaturas de un grupo de N personas, con lo que sabemos hasta ahora tendríamos que declarar cada una de las N variables que almacenarán los datos. ¡Imagina que N=1000! ¿Te imaginas todo lo que tendrías que escribir para declarar 1000 variables? Y peor, ahora piensa en la línea de código que tendrías que escribir para sumar esas 1000 variables. ¿Verdad que resulta completamente impráctico?
No te preocupes, para eso nos van a servir los arreglos. En vez de declarar 1000 variables como double estatura1, estatura2, ..., estatura1000;, lo que hacemos es que declaramos un solo conjunto de variables que se llame estatura y le decimos con un número cuántos elementos existirán en el conjunto: double estatura[1000];
Arreglos Estáticos vs. Arreglos Dinámicos
En una declaración de un arreglo, la cantidad debe ser una constante, no podemos poner dentro de los corchetes una variable, por eso reciben el nombre de arreglos estáticos. Durante toda la corrida del programa, el arreglo tendrá la cantidad indicada de datos y cada vez que se corra el programa, el arreglo tendrá el mismo tamaño siempre. Un array estático no puede ser redimensionado nunca.
Lea también: IVA 21% Excel
Sin embargo, es posible manejar otro tipo de arreglos que se llaman arreglos dinámicos, donde, dependiendo de condiciones de la corrida, el tamaño del arreglo puede ser determinado en el momento y no ser siempre el mismo. Las LISTAS, o arrays dinámicos, son estructuras de datos similares a los ARRAYS, pero que tienen tamaño variable. A diferencia de los arrays, en una lista podemos añadir o eliminar elementos según sea necesario.
En su momento definimos array como un conjunto de variables que tienen el mismo nombre y se diferencian a través de uno o varios localizadores. Un array dinámico se define como aquel que es declarado con un número de elementos componente indefinido. Un array dinámico tiene que ser redimensionado antes de poder ser utilizado mediante la instrucción Redimensionar. Dicha instrucción puede aparecer en cualquier parte del código donde la variable sea accesible.
Un array dinámico habrá de redimensionarse al menos una vez (para poder ser usado), pero podrá modificarse tantas veces como se estime necesario, aunque el número de localizadores siempre tendrá que ser el mismo.
Si la instrucción Redimensionar define un array hasta ese momento indefinido, los valores de las variables del array son cero o vacío. Además, el primer uso de redimensionar supondrá fijar el número de localizadores para el array, que ya no podrá variarse durante el programa. Se puede reducir el número de elementos del array; en este caso, las variables que dejan de formar parte del array desaparecen y se consideran no declaradas. No pueden invocarse ni recuperarse su valor, ni siquiera volviendo a ampliar el número de elementos del array.
Definición y Uso de Estructuras
Las estructuras nos permiten agrupar datos de diferentes tipos bajo un mismo nombre. Por ejemplo, podemos declarar una estructura llamada persona que contiene dos miembros. El código anterior declara dos variables del tipo de estructura persona (la variable alumno y la variable profesor).
Lea también: Guía IVA reducido
Para tener acceso a los miembros de una estructura, podemos utilizar el operador punto. También podemos crear tipos de datos basados en estructuras utilizando la palabra reservada typedef.
Implementación de Arreglos Dinámicos Genéricos de Estructuras en C
En esta entrada vamos a crear un Array Genérico en C con memoria dinámica que servirá para almacenar cualquier tipo de datos en su interior. Esto es particularmente útil cuando trabajamos con estructuras, ya que nos permite tener un arreglo cuyo tamaño puede cambiar dinámicamente y almacenar diferentes tipos de estructuras si se maneja adecuadamente con punteros void.
Un componente clave para este tipo de arreglo es void** elementos: se trata del array que almacenará nuestros elementos. Fijaos que es un puntero de tipo void, y le ponemos doble puntero para especificar que se trata de un array de punteros void.
Para gestionar el tamaño del arreglo, necesitamos variables como:
- numeroElementos: contendrá el número de elementos que tiene nuestro Array Genérico.
- capacidad: Contendrá la capacidad actual de nuestro Array.
Para inicializar un Array Genérico, una función podría recibir por parámetro de entrada un puntero de tipo struct ArrayGenerico*. Sobre este, reserva memoria para almacenar los punteros de tipo void*. Si ha podido reservar memoria, inicializa el número de elementos a 0 y la capacidad a una constante CAPACIDAD_INICIAL, que podría ser igual a 20, por ejemplo.
Lea también: ¿Cómo localizar tus XML del SAT?
Básicamente, si hemos llegado al máximo número de elementos que podemos albergar, reservamos memoria para almacenar el doble de elementos y copiamos todos los punteros sobre la nueva asignación de memoria. Este proceso asegura que el array dinámico pueda crecer según sea necesario.
Operaciones Fundamentales en Arreglos Dinámicos
Los arrays dinámicos, o listas, incorporan la funcionalidad adicional de agregar y eliminar elementos. Esto las hace mucho más potentes y versátiles que los arreglos estáticos.
Agregar Elementos
Para agregar elementos a una lista, se verifica si hay capacidad disponible. Si la lista está llena, se realiza una operación de redimensionamiento, que implica reservar más memoria y copiar los elementos existentes. Añadir un elemento al final puede ser O(1) si la lista tiene capacidad disponible. Si justo da la casualidad de que está lleno, la lista tendrá que ampliarse, lo cual es una operación O(n).
Eliminar Elementos
La eliminación de elementos también es una operación importante en arreglos dinámicos. Eliminar un elemento al final de la colección es O(1), ya que únicamente tenemos que reducir el contador de elementos. Sin embargo, para otros casos, la lógica es más compleja:
- Si el número de elementos es mayor que 1 y el elemento que queremos borrar es el último, simplemente ponemos el valor del último puntero ocupado a NULL.
- Si el elemento no es el último, trasladamos el valor del último puntero del array a la posición que queremos borrar y asignamos NULL al último puntero del Array.
- Si solo hay un elemento, lo borramos asignando su puntero a NULL.
Una vez que los elementos ya no son necesarios, es crucial liberar la memoria asignada. Simplemente eliminamos la memoria asignada a nuestros elementos y al propio struct para evitar fugas de memoria.
Eficiencia de las Listas (Arreglos Dinámicos)
Las LISTAS comparten los parámetros de eficiencia con sus hermanos los ARRAYS, porque, internamente, generalmente se implementan como un array subyacente.
| Operación | Eficiencia (Lista) |
|---|---|
| Acceso secuencial | 🟢 |
| Acceso aleatorio | 🟢 |
| Añadir al principio | 🔴 |
| Eliminar al principio | 🔴 |
| Añadir al final | 🟡 |
| Eliminar al final | 🟢 |
| Inserción aleatoria | 🔴 |
| Eliminar aleatoria | 🔴 |
| Búsqueda | 🔴 |
Añadir o eliminar un elemento al principio o en medio de la colección es O(n), porque la lista tiene que desplazar todos los elementos para ubicar o quitar el nuevo elemento.
