Português
preview
Del básico al intermedio: Estructuras (VII)

Del básico al intermedio: Estructuras (VII)

MetaTrader 5Ejemplos |
15 0
CODE X
CODE X

Introducción

En el artículo anterior, Del básico al intermedio: Estructuras (VI), vimos cómo comenzar a crear una implementación genérica de una estructura de datos simple. A pesar de parecer un tanto poco convencional, este tipo de modelado se utiliza mucho más de lo que la mayoría piensa. Esto se debe a que las grandes bases de datos utilizan un principio muy similar para organizar y buscar sus propios datos.

Sé que este tema, que se está tratando en estos artículos, puede parecer innecesario a muchos. Sin embargo, conviene recordar que mi intención es explicar las cosas de tal manera que, en el futuro, no sea necesario explicar ciertos detalles que, en mi opinión, son triviales. No obstante, como la mayoría de ustedes utiliza estos artículos para aprender de la experiencia y el conocimiento de otros programadores, autores de los artículos, todo lo que pueda transmitirte al principio te será de gran ayuda en el futuro, querido lector.

No basta con ver un código funcionando. Es necesario entender por qué funciona y, en caso necesario, saber adaptar la implementación realizada por otro programador a tus necesidades. Para ello, es necesario conocer conceptos y entender cómo pueden aplicarse.

Como en el artículo anterior empezamos a implementar algo bastante interesante con el objetivo de entender cómo se pueden usar las estructuras dentro de otras estructuras, es posible que te hayas quedado intrigado con el resultado del último código. Esto se debe a que utilizamos una estructura creada para un objetivo y, al final, conseguimos usarla para otro. En este caso, los datos eran inicialmente del tipo double y, al final, pudimos usar datos de cualquier tipo.

Sin embargo, lo que se mostró no es a dónde quiero llegar realmente. Para ello, todavía necesitamos avanzar un poco más. No obstante, todo este esfuerzo valdrá la pena y será recompensado en el futuro. Te lo garantizo, querido lector. Bien, entonces retomemos el tema donde lo dejamos en el artículo anterior.


Estructuras de estructuras, parte dos, el regreso

Para comenzar, en el artículo anterior terminamos con un código que se muestra justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. struct st_Data
06. {
07.     //+----------------+
08.     private:
09.     //+----------------+
10.         struct st_Reg
11.         {
12.             T       h_value;
13.             uint    k_value;
14.         }Values[];
15.     //+----------------+
16.         string ConvertToString(T arg)
17.         {
18.             if ((typename(T) == "double") || (typename(T) == "float")) return DoubleToString(arg, 2);
19.             if (typename(T) == "string") return arg;
20. 
21.             return IntegerToString(arg);
22.         }
23.     //+----------------+
24.     public:
25.     //+----------------+
26.         bool Set(const uint &arg1[], const T &arg2[])
27.         {
28.             if (arg1.Size() != arg2.Size())
29.                 return false;
30.             
31.             ArrayResize(Values, arg1.Size());
32.             for (uint c = 0; c < arg1.Size(); c++)
33.             {
34.                 Values[c].k_value = arg1[c];
35.                 Values[c].h_value = arg2[c];
36.             }
37. 
38.             return true;
39.         }
40.     //+----------------+
41.         string Get(const uint index)
42.         {
43.             for (uint c = 0; c < Values.Size(); c++)
44.                 if (Values[c].k_value == index)
45.                     return ConvertToString(Values[c].h_value);
46. 
47.             return "-nan";
48.         }
49.     //+----------------+
50. };
51. //+------------------------------------------------------------------+
52. #define PrintX(X) Print(#X, " => ", X)
53. //+------------------------------------------------------------------+
54. void OnStart(void)
55. {
56.     const string T = "possible loss of data due to type conversion";
57.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
58. 
59.     st_Data <string> info;
60.     string H[];
61. 
62.     StringSplit(T, ' ', H);
63.     info.Set(K, H);
64.     PrintX(info.Get(3));
65. }
66. //+------------------------------------------------------------------+

Código 01

A pesar de que este código 01 funciona, resulta un tanto molesto. Esto se debe a que, cuando lo compilemos, obtendremos una respuesta similar a la que se muestra a continuación.

Imagen 01

Este tipo de cosas que podemos ver en la imagen 01 resultan un tanto incómodas. Esto se debe a que, dependiendo del tipo de implementación, los mensajes de advertencia del compilador pueden provocar fallos futuros en el código. No se debe a que este código sea incorrecto, sino a que el compilador puede estar alertándonos sobre una posible falla crítica. Al ignorar los mensajes de error, podemos terminar ignorando un mensaje realmente importante.

De cualquier forma, cuando este código 01 se ejecuta, obtendremos como respuesta lo que puede verse en la imagen justo debajo.

Imagen 02

Es interesante, ¿verdad? Pero antes de continuar, vamos a resolver la cuestión que se muestra en la imagen 01. Hay varias formas de resolverlo. Sin embargo, todas terminan de la misma manera: usando una conversión explícita. Por tanto, la forma de llegar al punto de la conversión explícita no importa. Lo que importa es que se realice. Para lograrlo, basta con cambiar el código 01 por algo parecido a lo que se muestra a continuación.

                   .
                   .
                   .
15.     //+----------------+
16.         string ConvertToString(T arg)
17.         {
18.             if (typename(T) == "double") return DoubleToString((double)arg, 2);
19.             if (typename(T) == "float") return DoubleToString((float)arg, 2);
20.             if (typename(T) == "string") return (string)arg;
21.             return IntegerToString((long)arg);
22.         }
23.     //+----------------+
24.     public:
25.     //+----------------+
                   .
                   .
                   .

Código 02

Probablemente estés un poco decepcionado al ver que la solución al problema de la imagen 01 es este fragmento de código 02. Muchos quizá esperaban que se creara un conjunto completo de instrucciones y funciones. Para que, al final, todo lo que necesitemos hacer sea lo que se muestra en este código 02. Es parte del proceso. El tamaño de la frustración es proporcional al nivel de expectativa que teníamos respecto a algo. Bromas aparte, volvamos a la cuestión del código con el fin de entender cómo hacerlo más extensible para poder manejar datos diferentes sin grandes cambios.

Tal vez estés pensando que hacer esto sería muy complicado. Sin embargo, antes de explicar cómo se haría, necesito que hayas comprendido realmente la idea de cómo y por qué vincular un elemento de un array con otro elemento de otro array.

El concepto en sí es muy sencillo. Se trata simplemente de crear una traducción. Es decir, dado un elemento de un array, que sería la clave, tendríamos una respuesta que sería básicamente el elemento de otro array. Eso es exactamente lo que estamos haciendo con este código 01. Entonces, ¿por qué no usar un array multidimensional para esto? El motivo es que, aunque el array multidimensional es la mejor propuesta en este caso, no es adecuado porque lo estamos utilizando para demostrar algo. En otras palabras, se trata de mostrar cómo trabajar con un determinado tipo de recurso del lenguaje.

Bien, ahora que creo haber dejado bastante claro lo que estamos haciendo, quiero que vuelvas a mirar el código 01 y me digas qué es lo que le impide usarse no solo en la traducción de valores, sino también en otros contextos parecidos.

Piénsalo, no tengas prisa por llegar a una conclusión. Ahora mostraré una de las muchas maneras de liberar el código para que podamos colocar cualquier cosa dentro de él. Sin embargo, cuando intentes expandir las cosas, notarás que el problema está en la estructura declarada en la línea diez. Ese es el problema, pero no de la forma en que quizá estés pensando.

El problema de la estructura de la línea diez es que impide un uso más genérico de la estructura principal. Como sé que esto es difícil de entender y requiere tiempo, continuaré con el artículo. Pero te pido, querido lector, que leas y releas este artículo y los anteriores para comprender una cosa.

Las estructuras, al igual que otros tipos de construcciones, son formas de representación de datos más o menos complejos. La forma en que necesitarás abordar un problema depende de lo complejo que sea realmente. Algunos problemas se pueden resolver con tipos discretos y simples, como las representaciones en punto flotante o los valores enteros.

Cuando el problema se vuelve más complejo, los tipos discretos pueden no ser suficientes. En este caso, necesitamos tipos de datos más complejos, como arrays y uniones. Si aun así el problema sigue siendo complejo, necesitamos un tipo igualmente complejo: las estructuras.

Así, la estructura que declaramos en la línea diez del código 01 no es más que un tipo complejo. Es necesario que lo entiendas para poder comprender lo que vamos a hacer.

En primer lugar, debemos sacar esta estructura de la línea diez de la estructura definida en la línea cuatro, manteniendo aún el código 01. Sin embargo, al hacerlo, crearemos un enredo de información que te dejará más perdido que un perro que cayó del camión de mudanza si no has entendido o no has logrado entender los artículos anteriores. Esto se debe a que no hay forma de promover el cambio que se hará poco a poco. Debe hacerse en un número muy pequeño de pasos; el primer paso se muestra justo debajo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Reg
05. {
06.     string  h_value;
07.     uint    k_value;
08. };
09. //+----------------+
10. struct st_Data
11. {
12.     //+----------------+
13.     private:
14.     //+----------------+
15.         st_Reg Values[];
16.     //+----------------+
17.     public:
18.     //+----------------+
19.         bool Set(const uint &arg1[], const string &arg2[])
20.         {
21.             if (arg1.Size() != arg2.Size())
22.                 return false;
23.             
24.             ArrayResize(Values, arg1.Size());
25.             for (uint c = 0; c < arg1.Size(); c++)
26.             {
27.                 Values[c].k_value = arg1[c];
28.                 Values[c].h_value = arg2[c];
29.             }
30. 
31.             return true;
32.         }
33.     //+----------------+
34.         string Get(const uint index)
35.         {
36.             for (uint c = 0; c < Values.Size(); c++)
37.                 if (Values[c].k_value == index)
38.                     return Values[c].h_value;
39. 
40.             return NULL;
41.         }
42.     //+----------------+
43. };
44. //+------------------------------------------------------------------+
45. #define PrintX(X) Print(#X, " => ", X)
46. //+------------------------------------------------------------------+
47. void OnStart(void)
48. {
49.     const string T = "possible loss of data due to type conversion";
50.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
51. 
52.     st_Data info;
53.     string H[];
54. 
55.     StringSplit(T, ' ', H);
56.     info.Set(K, H);
57.     PrintX(info.Get(3));
58. }
59. //+------------------------------------------------------------------+

Código 03

Presta atención, querido lector: para que el código y lo que vamos a hacer sean lo más simples y didácticos posible, di un paso atrás y utilicé el tipo string. Observa que ahora ya no existe la sobrecarga de estructura que se estaba creando y que el resultado de la ejecución sigue siendo el que se ve en la imagen 02.

Quizá estés mirando este código 03 y pensando: «Está bien, dijiste que era complicado, pero hasta ahora todo sigue igual de simple, ¿dónde está la complicación?». Bien, entonces vamos a comenzar. El segundo punto que debe modificarse es que tanto la función Set como la función Get, que ves en las líneas 19 y 34, respectivamente, NO SON FUNCIONES de st_Data, sino de st_Reg.

Ahora te pregunto: ¿qué están haciendo en st_Data? Respuesta: Están dificultando que la estructura funcione de una manera más genérica, pudiendo así manejar cualquier tipo de dato. Por lo tanto, necesitamos eliminar estas funciones de la estructura st_Data que aparece en el código 03. Para simplificar las cosas, observa cómo quedaría el fragmento corregido. Se muestra justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Reg
05. {
06.     //+----------------+
07.     private:
08.     //+----------------+
09.         string  h_value;
10.         uint    k_value;
11.     //+----------------+
12.     public:
13.     //+----------------+
14.         void Set(const uint arg1, const string arg2)
15.         {
16.             k_value = arg1;
17.             h_value = arg2;
18.         }
19.     //+----------------+
20.         uint Get_K(void)    { return k_value; }
21.     //+----------------+
22.         string Get_H(void)  { return h_value; }
23.     //+----------------+
24. };
25. //+----------------+
26. struct st_Data
27. {
28.     //+----------------+
29.     private:
30.     //+----------------+
31.         st_Reg Values[];
32.     //+----------------+
33.     public:
34.     //+----------------+
35.         bool Set(const st_Reg &arg)
36.         {
37.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
38.                 return false;
39. 
40.             Values[Values.Size() - 1] = arg;
41. 
42.             return true;
43.         }
44.     //+----------------+
45.         st_Reg Get(const uint index)
46.         {
47.             for (uint c = 0; c < Values.Size(); c++)
48.                 if (Values[c].Get_K() == index)
49.                     return Values[c];
50. 
51.             return Values[0];
52.         }
53.     //+----------------+
54. };
55. //+------------------------------------------------------------------+
                   .
                   .
                   .

Código 04

«¿Qué locura es esta?». Calma, querido lector, que esto apenas está empezando a ponerse interesante. Ahora tenemos no una, sino dos estructuras con el fin de poder consolidar los datos dentro de la nueva estructura st_Data. Pero antes de avanzar al código principal, es decir, al que estará en OnStart, necesitamos entender qué está ocurriendo aquí.

De hecho, aunque este fragmento de código funciona de la misma manera que el código 03, aquí tenemos diversas cosas que son completamente diferentes de aquello a lo que estás acostumbrado. Aun así, en mi opinión, esto sigue siendo un código de nivel básico, ya que aquí estamos manejando cosas muy simples.

Para empezar, debes tener en cuenta que st_Reg es una estructura pensada para trabajar con un código estructural. Es decir, no entraremos en contacto directo con las variables, sino que crearemos un contexto en el que podamos manejarlas.

Básicamente, st_Reg funciona como antes de que empezáramos a hablar de estructuras anidadas. Ahora viene la parte que complica la vida a muchos principiantes, sobre todo si no tienen claros los conceptos básicos sobre qué es una variable y cómo gestionar los tipos de datos. Así que vamos a entender qué está ocurriendo en esta segunda estructura, que es st_Data. Como st_Reg es un tipo de dato complejo, en la línea 31 podemos declarar una variable, que en este caso es un array, para contener este tipo de dato, que es st_Reg. Hasta ahí, creo que no estás viendo ningún problema, ¿verdad, querido lector?

Sin embargo, cuando miramos la línea 35, algo empieza a no tener mucho sentido, sobre todo si observamos la línea 37. ¡Misericordia, qué cosa tan extraña se está haciendo ahí! En la línea 37, simplemente estamos intentando asignar espacio en memoria, querido lector. No obstante, hay un pequeño detalle que hace que las cosas sean un poco confusas. Ese detalle es que, en MQL5, NO PODEMOS DEVOLVER PUNTEROS. Por esta razón, la función de la línea 45 tiene un pequeño fallo que intentamos resolver en la línea 37.

Así que presta atención. Cuando vayamos a buscar un valor que fue insertado en la estructura st_Data y NO LO ENCONTREMOS, tenemos que devolver un dato inválido. En lenguajes como C y C++, hacemos esto devolviendo normalmente un valor nulo. Sin embargo, aquí, en MQL5, NO PODEMOS HACER ESTO. Necesitamos un subterfugio. Por esta razón, cuando la línea 37 intente asignar el primer bloque para añadir un dato del tipo st_Reg, asignaremos dos posiciones de memoria. La primera quedará vacía. La segunda, en cambio, tendrá un valor que se asignará debido a la línea 40.

Al ser este el primer elemento, se asignarán dos posiciones. A partir de la siguiente llamada, la línea 37 percibirá que ya tenemos al menos dos elementos asignados y asignará solo uno más. Y así sucederá en cada nueva llamada a la función Set de la estructura st_Data. Observa que esto no tiene nada que ver con el procedimiento Set de la estructura st_Reg, y hay un motivo para ello, que se verá más adelante.

Muy bien, como la función Get de la estructura st_Data está directamente relacionada con la explicación de la estructura Set, todo lo que necesitas entender aquí es que el valor recibido como argumento por la función Get no nos indica cuál es el índice buscado en el array Values, sino cuál es el valor que está físicamente almacenado en ese elemento específico. Por esta razón, necesitamos un bucle para buscar el valor específico dentro de la estructura st_Reg. Este es un problema del que hablaremos más adelante. Básicamente, es esto.

Ahora podemos ver el código principal. Recordemos que este código, que hemos visto en el fragmento mostrado en el código 04, podría colocarse perfectamente en un archivo de cabecera y funcionaría sin problemas. Pero antes de mostrar cómo hacer esto, de modo que podamos generalizar el código, necesitamos ver el resto del mismo. Se muestra justo a continuación.

                   .
                   .
                   .
55. //+------------------------------------------------------------------+
56. #define PrintX(X) Print(#X, " => [", X, "]")
57. //+------------------------------------------------------------------+
58. void OnStart(void)
59. {
60.     const string T = "possible loss of data due to type conversion";
61.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
62. 
63.     st_Data info;
64.     string H[];
65. 
66.     StringSplit(T, ' ', H);
67.     for (uint c = 0; c < H.Size(); c++)
68.     {
69.         st_Reg  reg;
70. 
71.         reg.Set(K[c], H[c]);
72.         info.Set(reg);
73.     }
74. 
75.     PrintX(info.Get(3).Get_H());
76.     PrintX(info.Get(13).Get_H());
77. }
78. //+------------------------------------------------------------------+

Código 05

Y aquí llega, querido lector, la parte más divertida y animada hasta ahora. Como gran parte de este código ya debe de haber sido comprendido por ti, ya que se vio anteriormente, vamos a la parte que es novedad aquí. Se trata precisamente del bucle de la línea 67 y de las líneas 75 y 76.

Comencemos por el bucle. Como cada elemento que debe colocarse en la estructura st_Data es de tipo st_Reg, necesitamos que la línea 71 cree la estructura st_Reg para poder almacenar después el elemento de tipo st_Reg en la estructura st_Data usando la línea 72. Ahora, la pregunta es: ¿por qué no estoy haciendo esto directamente en la estructura st_Data? Respuesta: porque, si el registro de los elementos de tipo st_Reg se realizara directamente en la estructura st_Data, no podríamos sobrecargar la estructura más adelante. Pero ten calma, pronto lo entenderás mejor.

Una vez registrados todos los elementos en la estructura st_Data, podemos usar las líneas 75 y 76 para probar dicha estructura. Para ello, en la línea 75 buscamos la cadena cuyo índice sea igual a tres. Y, observando las líneas 60 y 61, podemos ver qué valor tiene ese índice. En cambio, la línea 76 buscará un elemento cuyo índice sea igual a trece. Como dicho índice no está definido en la línea 61, el valor devuelto será el que esté definido en la línea 51 del código 04.

Con este conocimiento previo, ya puedes ejecutar el código. Al hacerlo, verás que el resultado es el que se observa en la imagen de abajo.

Imagen 03

Es decir, funciona perfectamente. Entonces, ahora podemos generalizar buena parte del código. Pero para hacerlo, iremos a un nuevo tema. Así podrás estudiar con calma lo que se ha hecho hasta ahora y, después, intentarás entender lo que se va a hacer para generalizar.


Estructuras de estructuras, parte tres, la venganza

Si te pareció confuso lo que se hizo en el tema anterior, prepárate, porque ahora la cosa se pondrá aún más interesante. Aquí es donde el hijo llora y la madre no ve. Así que presta atención al siguiente hecho. En el código 04, visto en el tema anterior, la estructura st_Data solo puede recibir un tipo de dato, es decir, el tipo st_Reg, pero esto no la hace genérica. Solo limita la estructura st_Data a lo que se define dentro de la estructura st_Reg, incluso si no tienen nada que ver.

Sin embargo, si modificamos ligeramente el código de la estructura st_Data, lograremos liberarla para que pueda trabajar con distintos tipos de datos. Presta atención a lo que acabo de decir. Cuando la estructura st_Data se convierta en un plantilla, no quedará totalmente libre de la estructura st_Reg. Esto se debe a la función Get, que seguirá vinculada al tipo st_Reg. Aun así, merece la pena el esfuerzo.

Como se indicó anteriormente, todo lo que necesitamos es transformar la estructura st_Data en un plantilla y, al mismo tiempo, corregir el código principal para que se adapte a esta plantilla, que deberá ser creada por el compilador. Esto se hace utilizando el código que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Reg
05. {
06.     //+----------------+
07.     private:
08.     //+----------------+
09.         string  h_value;
10.         uint    k_value;
11.     //+----------------+
12.     public:
13.     //+----------------+
14.         void Set(const uint arg1, const string arg2)
15.         {
16.             k_value = arg1;
17.             h_value = arg2;
18.         }
19.     //+----------------+
20.         uint Get_K(void)    { return k_value; }
21.     //+----------------+
22.         string Get_H(void)  { return h_value; }
23.     //+----------------+
24. };
25. //+----------------+
26. template <typename T>
27. struct st_Data
28. {
29.     //+----------------+
30.     private:
31.     //+----------------+
32.         T Values[];
33.     //+----------------+
34.     public:
35.     //+----------------+
36.         bool Set(const T &arg)
37.         {
38.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
39.                 return false;
40. 
41.             Values[Values.Size() - 1] = arg;
42. 
43.             return true;
44.         }
45.     //+----------------+
46.         T Get(const uint index)
47.         {
48.             for (uint c = 0; c < Values.Size(); c++)
49.                 if (Values[c].Get_K() == index)
50.                     return Values[c];
51. 
52.             return Values[0];
53.         }
54.     //+----------------+
55. };
56. //+------------------------------------------------------------------+
57. #define PrintX(X) Print(#X, " => [", X, "]")
58. //+------------------------------------------------------------------+
59. void OnStart(void)
60. {
61.     const string T = "possible loss of data due to type conversion";
62.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
63. 
64.     st_Data <st_Reg> Info_1;
65.     string H[];
66. 
67.     StringSplit(T, ' ', H);
68.     for (uint c = 0; c < H.Size(); c++)
69.     {
70.         st_Reg  reg;
71. 
72.         reg.Set(K[c], H[c]);
73.         Info_1.Set(reg);
74.     }
75. 
76.     PrintX(Info_1.Get(3).Get_H());
77.     PrintX(Info_1.Get(13).Get_H());
78. }
79. //+------------------------------------------------------------------+

Código 06

En teoría, este código 06 puede albergar cualquier tipo de estructura de datos o cualquier tipo de dato dentro de la estructura st_Data. Esto, en teoría. El motivo es precisamente la función Get, presente en la línea 46 del código 06. Para hacer el código más genérico, solo tuvimos que añadir la línea 26 y reemplazar las referencias a st_Reg en las líneas 32, 36 y 46 por el tipo T. Una vez hecho esto, debemos modificar la declaración de la línea 64 para que el compilador pueda generar la estructura adecuada.

El resultado de ejecutar este código 06 es el mismo que el de ejecutar el código 05, es decir, la imagen 03. Sin embargo, todavía estamos, en cierto modo, ligados a la estructura st_Reg, aunque esta vinculación, que al mismo tiempo es un problema, también puede aportar algún beneficio. Todo depende de las necesidades, el nivel de creatividad y los conocimientos sobre ciertos conceptos de cada lector. Observa lo siguiente. La única conexión que queda entre las estructuras st_Data y st_Reg es la llamada Get_K, presente en la línea 49. Esa es la única conexión que todavía persiste. Pues bien, dado que la estructura st_Reg también puede sobrecargarse, podemos utilizar tipos distintos en ese punto.

Pero, como este tipo de cosas puede generar detalles complicados de entender en este tramo final del artículo, no hablaré de ello ahora.

Sin embargo, aun así, podemos hacer otra cosa que, en mi opinión, es igualmente interesante y que, además, se puede explicar en el espacio restante de este artículo. Se trata precisamente de la línea 49 del código 06. Ahora piensa en lo siguiente, querido lector: en el artículo Del básico al intermedio: Sobrecarga, hablamos de una forma muy sencilla e interesante de utilizar la sobrecarga de funciones y procedimientos. En otros artículos de esta serie, también he mostrado que podemos hacerlo utilizando nombres idénticos a los de la biblioteca estándar.

Ahora viene la parte interesante: ¿qué pasaría si, en este código 06, construyéramos una forma de sobrecargar la función Get_K vista en la línea 49? ¿Qué puertas se abrirían con esto? Como dije hace un momento, es aquí donde la creatividad y la aplicación de conceptos hacen que las cosas se vuelvan muy interesantes. Esto se debe a que, si observas con atención, notarás que la estructura st_Reg, vista en el código 06, solo tiene dos variables. Sin embargo, podemos sobrecargar esta estructura st_Reg con el fin de tener otra estructura con más variables o incluso con tipos de datos muy diferentes dentro de la estructura.

No obstante, al hacer esto, no necesariamente tendríamos que destruir la estructura st_Reg original ni modificar la estructura st_Data, ya creada en el código 06. Como no estoy seguro de que estés entendiendo hacia dónde quiero llegar, veamos un código en el que podamos observar lo que pretendo mostrar. Este código se encuentra justo a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Reg
005. {
006.     //+----------------+
007.     private:
008.     //+----------------+
009.         string  h_value;
010.         uint    k_value;
011.     //+----------------+
012.     public:
013.     //+----------------+
014.         void Set(const uint arg1, const string arg2)
015.         {
016.             k_value = arg1;
017.             h_value = arg2;
018.         }
019.     //+----------------+
020.         uint Get_K(void)    { return k_value; }
021.     //+----------------+
022.         string Get_H(void)  { return h_value; }
023.     //+----------------+
024. };
025. //+------------------------------------------------------------------+
026. struct st_Bio
027. {
028.     //+----------------+
029.     private:
030.     //+----------------+
031.         string  h_value;
032.         string  b_value;
033.         uint    k_value;
034.     //+----------------+
035.     public:
036.     //+----------------+
037.         void Set(const uint arg1, const string arg2, const string arg3)
038.         {
039.             k_value = arg1;
040.             h_value = arg2;
041.             b_value = arg3;
042.         }
043.     //+----------------+
044.         uint Get_K(void)    { return k_value; }
045.     //+----------------+
046.         bool Get_Bio(string &arg1, string &arg2)
047.         {
048.             arg1 = h_value;
049.             arg2 = b_value;
050. 
051.             return true;
052.         }
053.     //+----------------+
054. };
055. //+------------------------------------------------------------------+
056. template <typename T>
057. struct st_Data
058. {
059.     //+----------------+
060.     private:
061.     //+----------------+
062.         T Values[];
063.     //+----------------+
064.     public:
065.     //+----------------+
066.         bool Set(const T &arg)
067.         {
068.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
069.                 return false;
070. 
071.             Values[Values.Size() - 1] = arg;
072. 
073.             return true;
074.         }
075.     //+----------------+
076.         T Get(const uint index)
077.         {
078.             for (uint c = 0; c < Values.Size(); c++)
079.                 if (Values[c].Get_K() == index)
080.                     return Values[c];
081. 
082.             return Values[0];
083.         }
084.     //+----------------+
085. };
086. //+------------------------------------------------------------------+
087. #define PrintX(X) Print(#X, " => [", X, "]")
088. //+------------------------------------------------------------------+
089. void CheckBio(st_Data <st_Bio> &arg)
090. {
091.     string sz[2];
092. 
093.     Print("Checking data in the structure...");
094.     for (uint i = 7; i < 11; i += 3)
095.     {
096.         Print("Index: ", i, " Result: ");
097.         if (arg.Get(i).Get_Bio(sz[0], sz[1]))
098.             ArrayPrint(sz);
099.         else
100.             Print("Failed.");
101.     }
102. }
103. //+------------------------------------------------------------------+
104. void OnStart(void)
105. {
106.     const string T = "possible loss of data due to type conversion";
107.     const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"};
108.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
109. 
110.     st_Data <st_Reg> Info_1;
111.     st_Data <st_Bio> Info_2;
112. 
113.     string  H[];
114. 
115.     StringSplit(T, ' ', H);
116.     for (uint c = 0; c < H.Size(); c++)
117.     {
118.         st_Reg  reg;
119.         st_Bio  bio;
120. 
121.         reg.Set(K[c], H[c]);
122.         bio.Set(K[c], M[c], H[c]);
123. 
124.         Info_1.Set(reg);
125.         Info_2.Set(bio);
126.     }
127. 
128.     PrintX(Info_1.Get(3).Get_H());
129.     PrintX(Info_1.Get(13).Get_H());
130.     CheckBio(Info_2);
131. }
132. //+------------------------------------------------------------------+

Código 07

Cuando se ejecute este código 07, se verá en el terminal algo muy parecido a lo que se muestra en la imagen siguiente.

Imagen 05

Misericordia. ¿Qué cosa tan insana y loca has creado? Calma, mi querido lector, lo que nos interesa aquí son precisamente las informaciones que se destacan en esta imagen 05, que fueron creadas mediante el procedimiento de la línea 89, en el que prestaré mayor atención en la explicación, ya que gran parte del código es sencillo de entender. Sin embargo, comprender este procedimiento de la línea 89 es extremadamente importante para entender algo que se verá en el próximo artículo.

Pero antes de explicar qué está ocurriendo en este procedimiento de la línea 89, necesitamos entender algunos detalles más. Comenzando por el hecho de que, en la línea 26, se está creando una nueva estructura. Observa que tiene una base similar a la estructura st_Reg, pero las cosas se quedan solo en ese punto, ya que la estructura st_Bio tiene más variables y un conjunto diferente de operaciones dentro de su contexto. Lo que es importante para nosotros es precisamente la función de la línea 44. Esto se debe a que se parece a la función de la línea 20, ya que tiene el mismo propósito y objetivo. Y esto es importante para lo que haremos a continuación.

Observa también que la estructura st_Data, vista en este código 07, es exactamente la misma que la del código 06, así como la estructura st_Reg. El único cambio real es la presencia de la estructura st_Bio. Tras haber observado esto, podemos pasar al procedimiento principal, OnStart, que es el punto de entrada de nuestro código. Percibe que aquí incluimos algunas cosas extra que no existían en el código 06. Estas cosas tienen precisamente como objetivo mostrar cómo la estructura st_Data puede volverse extremadamente flexible si se piensa bien durante la fase de implementación del código.

Básicamente, añadimos las líneas 107 y 111. Ahora, querido lector, presta muchísima atención. Observa que la diferencia entre las líneas 110 y 111 es precisamente el tipo de dato que colocaremos en la estructura st_Data. Por esta razón, dentro del bucle de la línea 116, añadimos una nueva variable en la línea 119, que recibirá valores en la línea 122 y, en la línea 125, asignamos o sumamos un nuevo dato a la estructura. En este caso, el tipo de dato será diferente al de la línea 124. Hasta este momento, todo ocurrió tal y como se preveía en el código 06.

Sin embargo, es aquí donde la cosa empieza a complicarse y, al mismo tiempo, a volverse más divertida: tenemos la línea 130, que hará que se ejecute el procedimiento de la línea 89. Ahora vamos a la parte divertida. Observa lo siguiente. aunque estemos dentro de un bucle en la línea 94, la parte que realmente nos interesa es la línea 97, que debes comparar con la línea 128. ¿Qué diferencia hay entre ambas líneas? Prácticamente nada, excepto por el hecho de que, como la línea 76 devuelve un tipo de estructura referente al tipo de dato de la variable declarada en la línea 62, no hay diferencias entre ambas líneas.

En el artículo «Del básico al intermedio: Variables (III)», expliqué que una función es un tipo de variable especial. Ese concepto se aplica aquí de una manera muy interesante, ya que, debido a la respuesta que obtenemos de la función de la línea 76, podemos estar apuntando a un tipo de dato u otro, en este caso, una estructura. Sin hacer uso de una programación estructural, sería casi imposible, si no extremadamente difícil, crear lo que se está haciendo aquí. Esto se debe a que, en la línea 97, se está apuntando a la estructura st_Bio. En la línea 128, apuntamos a la estructura st_Reg, y como ambas están dentro de la estructura st_Data, logramos manejar distintos tipos de información o conjuntos de datos de la misma forma.

Debes practicar y experimentar para entender lo que realmente está ocurriendo aquí. No es algo que vayas a conseguir entender solo con mirar el código y tener poca experiencia en programación. Y recuerda: aquí todavía estamos trabajando con material que, en mi opinión, es de nivel básico.


Consideraciones finales

En este artículo se muestra cómo podemos abordar los problemas para estructurar las cosas y crear una solución más sencilla y atractiva. A pesar de que el contenido que se ha visto aquí está orientado a ser didáctico y, por lo tanto, no representa un código real, es necesario asimilar muy bien los conceptos y conocimientos que se han visto aquí. De esta manera, en el futuro, podrás seguir los códigos que iremos mostrando, ya que, conforme avancemos, podré mostrar detalles más profundos e interesantes sobre programación, cosa que no sería posible si tuviera que explicar cada línea de código.

Así que aprovéchalo y estudia a fondo el contenido del anexo, sin prisa. Estudia y practica estas enseñanzas, ya que en el próximo artículo continuaremos desde donde este se detuvo. Aún no hemos terminado de hablar sobre programación estructurada y sobre cómo podemos sacarle provecho antes de empezar con la programación orientada a objetos.

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/15912

Archivos adjuntos |
Anexo.zip (3.41 KB)
Simulación de mercado (Parte 18): Iniciando SQL (I) Simulación de mercado (Parte 18): Iniciando SQL (I)
Da igual si vamos a usar uno u otro programa de SQL, ya sea MySQL, SQL Server, SQLite, OpenSQL o cualquier otro. Todos tienen algo en común. Ese algo en común es el lenguaje SQL. Aunque no vayas a usar una WorkBench, podrás manipular o trabajar con una base de datos directamente en MetaEditor o a través de MQL5 para hacer cosas en MetaTrader 5, pero necesitarás tener conocimientos de SQL. Así que aquí aprenderemos, al menos, lo básico.
Redes neuronales en el trading: Optimización LSTM para la previsión de series temporales multivariantes (DA-CG-LSTM) Redes neuronales en el trading: Optimización LSTM para la previsión de series temporales multivariantes (DA-CG-LSTM)
En este artículo presentamos el algoritmo DA-CG-LSTM, que ofrece nuevos enfoques para el análisis y la previsión de series temporales. En él aprenderemos cómo los innovadores mecanismos de atención y la flexibilidad de los modelos mejoran la precisión de las predicciones.
Automatización de estrategias de trading en MQL5 (Parte 15): Patrón armónico Cypher de acción del precio con visualización Automatización de estrategias de trading en MQL5 (Parte 15): Patrón armónico Cypher de acción del precio con visualización
En este artículo, exploramos la automatización del patrón armónico Cypher en MQL5, detallando su detección y visualización en los gráficos de MetaTrader 5. Implementamos un Asesor Experto que identifica puntos de oscilación, valida patrones basados en Fibonacci y ejecuta operaciones con anotaciones gráficas claras. El artículo concluye con una guía sobre cómo realizar pruebas retrospectivas y optimizar el programa para lograr un trading efectivo.
Simulación de mercado (Parte 17): Sockets (X) Simulación de mercado (Parte 17): Sockets (X)
Implementar la parte que se ejecutará aquí en MetaTrader 5 no es complicado. Pero hay diversos aspectos a los que hay que prestar atención. Esto es para que tú, querido lector, consigas hacer que el sistema funcione de verdad. Recuerda una cosa: no se ejecutará un único programa. En realidad, estarás ejecutando tres programas a la vez. Es importante que cada uno se implemente y se construya de forma que trabajen y se comuniquen entre sí. Es crucial que cada uno sepa qué está intentando o deseando hacer el otro.