Arrays dinámicos
Los arrays dinámicos pueden cambiar de tamaño durante la ejecución del programa a petición del programador. Recordemos que para describir un array dinámico, debe dejar vacío el primer par de corchetes después del identificador del array. MQL5 requiere que todas las dimensiones subsiguientes (si hay más de una) tengan un tamaño fijo especificado con una constante.
Es imposible aumentar dinámicamente el número de elementos para cualquier dimensión «más antigua» que la primera. Además, debido a la estricta descripción del tamaño, los arrays tienen una forma «cuadrada», es decir, por ejemplo, es imposible construir un array bidimensional con columnas o filas de longitudes diferentes. Si alguna de estas restricciones es crítica para la implementación del algoritmo, no debería usar arrays MQL5 estándar, sino sus propias estructuras o clases escritas en MQL5.
Tenga en cuenta que si un array no tiene un tamaño en la primera dimensión, pero sí tiene una lista de inicialización que le permite determinar el tamaño, entonces dicho array es un array de tamaño fijo, no dinámico.
Por ejemplo, en la sección anterior, utilizamos el array array1D:
int array1D[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; |
Debido a la lista de inicialización, su tamaño es conocido por el compilador, y por lo tanto el array es fijo.
A diferencia de este sencillo ejemplo, no siempre es fácil determinar si un array concreto de un programa real es dinámico. En particular, un array puede pasarse como parámetro a una función. Sin embargo, puede ser importante saber si un array es dinámico porque la memoria se puede asignar manualmente llamando a ArrayResize sólo para este tipo de arrays.
En estos casos, la función ArrayIsDynamic le permite determinar el tipo de array.
Veamos algunas descripciones técnicas de funciones para trabajar con arrays dinámicos y, a continuación, probémoslas utilizando el script ArrayDynamic.mq5.
bool ArrayIsDynamic(const void &array[])
La función comprueba si el array pasado es dinámico. Un array puede tener cualquier dimensión permitida de 1 a 4. Los elementos del array pueden ser de cualquier tipo.
La función devuelve true para un array dinámico, o false en otros casos (array fijo, o array con series temporales controlado por el propio terminal o por el indicador).
int ArrayResize(void &array[], int size, int reserve = 0)
La función establece el nuevo size en la primera dimensión del array dinámico. Un array puede tener cualquier dimensión permitida de 1 a 4. Los elementos del array pueden ser de cualquier tipo.
Si el parámetro reserve es mayor que cero, se asigna memoria para el array con una reserva para el número de elementos especificado. Esto hace que pueda aumentar la velocidad del programa que tiene muchas llamadas a funciones consecutivas. Hasta que el nuevo tamaño solicitado del array no supere el actual teniendo en cuenta la reserva, no habrá reasignación de memoria física y los nuevos elementos se tomarán de la reserva.
La función devuelve el nuevo tamaño del array si su modificación se ha realizado correctamente, o -1 en caso de error.
Si la función se aplica a un array o serie temporal fija, su tamaño no cambia. En estos casos, si el tamaño solicitado es menor o igual que el tamaño actual del array, la función devolverá el valor del parámetro size; en caso contrario, devolverá -1.
Al aumentar el tamaño de un array ya existente se conservan todos los datos de sus elementos. Los elementos añadidos no se inicializan con nada y pueden contener datos arbitrarios incorrectos («basura»).
Estableciendo el tamaño del array a 0, ArrayResize(array, 0), no libera la memoria realmente asignada para ello, incluida una posible reserva. Esta llamada sólo restablecerá los metadatos del array. Esto se hace con el fin de optimizar futuras operaciones con el array. Para forzar la liberación de la memoria, utilice ArrayFree (véase más abajo).
Es importante entender que el parámetro reserve no se utiliza cada vez que se llama a la función, sino sólo en aquellos momentos en los que realmente se realiza la reasignación de memoria, es decir, cuando el tamaño solicitado supera la capacidad actual del array incluyendo la reserva. Para mostrar visualmente cómo funciona esto, crearemos una copia incompleta del objeto array interno e implementaremos la función gemela ArrayResize para él, y también los análogos ArrayFree y ArraySize, a fin de tener un conjunto de herramientas completo.
template<typename T>
|
Una ventaja de la función DynArrayResize frente a ArrayResize integrada es que aquí insertamos una impresión de depuración para aquellas situaciones en las que se reasigna la capacidad interna del array.
Ahora podemos tomar el ejemplo estándar para la función ArrayResize de la documentación MQL5 y reemplazar las llamadas a la función integrada por análogas «hechas a sí mismas» con el prefijo «Dyn». El resultado modificado se presenta en el script ArrayCapacity.mq5.
void OnStart()
|
La única diferencia significativa es la siguiente: en la versión lenta, la llamada ArrayResize(a, i) se sustituye por la más moderada DynArrayResize(a, i, 1000), es decir, la redistribución se solicita no en cada iteración, sino cada 1000 (de lo contrario, el registro se llenaría en exceso de mensajes).
Después de ejecutar el script, veremos los siguientes tiempos en el registro (los intervalos de tiempo absolutos dependen de su ordenador, pero nos interesa la diferencia entre las variantes de rendimiento con y sin la reserva):
--- Test Fast: ArrayResize(arr,100000,100000)
|
La ganancia de tiempo es significativa. Además, vemos en qué iteraciones y cómo se modifica la capacidad real del array (reserva).
void ArrayFree(void &array[])
La función libera toda la memoria del array dinámico pasado (incluida la posible reserva establecida mediante el tercer parámetro de la función ArrayResize) y pone a cero el tamaño de su primera dimensión.
En teoría, los arrays en MQL5 liberan memoria automáticamente cuando finaliza la ejecución del algoritmo en el bloque actual. No importa si un array está definido localmente (dentro de funciones) o globalmente, si es fijo o dinámico, ya que el sistema liberará la memoria por sí mismo en cualquier caso, sin requerir acciones explícitas del programador.
Por lo tanto, no es necesario llamar a esta función. No obstante, hay situaciones en las que un array se utiliza en un algoritmo para volver a rellenarse con algo desde cero, es decir, es necesario liberarlo antes cada vez que se vuelva a rellenar. Esta función puede así resultarle útil.
Tenga en cuenta que si los elementos del array contienen punteros a objetos asignados dinámicamente, la función no los borra: el programador debe llamar a delete para ellos (véase más adelante).
Vamos a probar las funciones comentadas anteriormente: ArrayIsDynamic, ArrayResize, ArrayFree.
En el script ArrayDynamic.mq5 se escribe la función ArrayExtend, que incrementa el tamaño del array dinámico en 1 y escribe el valor pasado en el nuevo elemento.
template<typename T>
|
La función ArrayIsDynamic se utiliza para asegurarse de que el array sólo se actualiza si es dinámico. Esto se hace en una sentencia condicional. La función ArrayResize le permite cambiar el tamaño del array, y la función ArraySize se utiliza para averiguar el tamaño actual (esto se abordará en la siguiente sección).
En la función principal del script, aplicaremos ArrayExtend para arrays de diferentes categorías: dinámicos y fijos.
void OnStart()
|
En las líneas de código que llaman a las funciones que no pueden utilizarse para arrays fijos, el compilador genera un aviso de «no puede utilizarse para arrays asignados estáticamente». Es importante señalar que no hay avisos de este tipo dentro de la función ArrayExtend porque se puede pasar a la función un array de cualquier categoría. Por eso lo comprobamos en ArrayIsDynamic.
Después de un bucle en OnStart,, el array dynamic se expandirá hasta 10 y obtendrá los elementos iguales a los índices al cuadrado. El array fixed permanecerá lleno de ceros y no cambiará de tamaño.
Liberar un array fijo con ArrayFree no tendrá ningún efecto, y el array dinámico de hecho se borrará. En este caso, el último intento de imprimirlo no producirá ninguna línea en el registro.
Veamos el resultado de la ejecución del script.
ArrayResize(fixed,0)=0
|
De particular interés son los arrays dinámicos con punteros a objetos. Definamos una clase ficticia sencilla Dummy y creemos un array de punteros a dichos objetos.
class Dummy
|
Después de extender el array dummy con un nuevo puntero, lo liberamos con ArrayFree, pero hay entradas en el registro del terminal que indican que el objeto se quedó en memoria.
1 undeleted objects left
|
El hecho es que la función sólo gestiona la memoria asignada al array. En este caso, esta memoria contenía un puntero, pero lo que señala no pertenece al array. En otras palabras: si el array contiene punteros a objetos «externos», tendrá que ocuparte de ellos usted mismo. Por ejemplo:
for(int i = 0; i < ArraySize(dummies); ++i)
|
Este borrado debe iniciarse antes de llamar a ArrayFree.
Para abreviar la entrada, puede utilizar las siguientes macros (bucle sobre los elementos, llamada a delete para cada uno de ellos):
#define FORALL(A) for(int _iterator_ = 0; _iterator_ < ArraySize(A); ++_iterator_)
|
El borrado de punteros se simplifica entonces a la siguiente notación:
...
|
Como solución alternativa, puede utilizar una clase envolvente de punteros como AutoPtr, de la que hablamos en la sección Plantillas de tipos de objeto. El array debe entonces declararse con el tipo AutoPtr. Dado que el array almacenará objetos envoltorio, no punteros, cuando se vacíe el array se llamará automáticamente a los destructores de cada «envoltorio» y se liberará a su vez la memoria de punteros de los mismos.