Português
preview
Del básico al intermedio: Herencia

Del básico al intermedio: Herencia

MetaTrader 5Ejemplos |
20 0
CODE X
CODE X

Introducción

En el artículo anterior, «Del básico al intermedio: Estructuras (VII)», empezamos a trabajar con una programación estructural que, aunque parezca bastante compleja y difícil durante el primer contacto, resulta muy divertida e interesante. Lo sé porque al principio me costó mucho comprender por qué algunos códigos funcionaban y otros no. Fueron años de lucha y persistencia hasta que aprendí definitivamente los conceptos que intento transmitir en estos artículos. Muchos imaginan que pueden aprender a programar haciendo un curso o simplemente estudiando el código de otros programadores.

Sin embargo, lamento informarte de que para aprender a programar de verdad necesitas practicar. Y solo con el tiempo y resolviendo problema tras problema te convertirás en un buen profesional. Y esto lleva tiempo. Pero, si puedo ayudar a acelerar un poco las cosas, ¿por qué no compartir parte de este conocimiento con quien realmente quiere aprender? Pues bien, hemos llegado a un punto clave

en el que las cosas se volverán aún más interesantes y divertidas. Es muy importante que hayas logrado establecer una buena base y que hayas entendido lo que se explicó en los artículos anteriores. De ahora en adelante, si no has estudiado y practicado lo visto anteriormente, te resultará muy difícil seguir este y los próximos artículos.


Herencia simple

Una de las ideas erróneas más comunes es que la herencia solo existe en el código de clases o, como se le suele llamar, programación orientada a objetos. También conocida como herencia. ¿Y qué es la herencia? Bien, de forma bastante simplificada, consiste en crear un tipo de dato simple y, a partir de este, crear otros más complejos sin necesidad de crear elementos, funciones y procedimientos que ya hayan sido establecidos previamente en ese tipo de dato más simple.

Para entenderlo mejor, piensa en lo siguiente: En la naturaleza existen diversos tipos de animales, plantas y minerales. Supongamos que necesitas crear un código para organizar toda la información de entrada en estos tipos presentes en la naturaleza, que en este caso resumimos como animales, plantas y minerales. ¿Cómo lo harías? Lo más probable es que crearías tres estructuras diferentes para poder separar la información de entrada en uno de estos tres tipos previamente definidos.

Al crear estas estructuras, te darás cuenta de que puedes hacer que el código sea más fácil de manipular y entender si mantendrá una programación totalmente estructurada. Esto se vio en los artículos anteriores. Sin embargo, los animales y las plantas tienen algo en común. Ambos son organismos vivos, a diferencia de los minerales. Por lo que tanto las plantas como los animales tienen elementos estructurales que son iguales entre sí.

Estos elementos se pueden eliminar y colocar en una nueva estructura base. De este modo, tendríamos algo en común con los seres vivos, lo que evitaría la duplicación de código dentro de la propia estructura de datos y facilitaría el mantenimiento, la implementación y la mejora del código.

Normalmente, la mayor parte del código orientado a trabajar y operar en el mercado financiero, para lo que se usa MetaTrader 5 y, por consiguiente, MQL5, no necesita el nivel de abstracción que introduzco en este artículo. Tampoco es necesario utilizar programación estructural u orientada a objetos para este propósito.

Sin embargo, debido a la propia naturaleza de la programación y al tipo de problemas a los que podemos enfrentarnos, es muy útil y necesario que tú, querido lector, comprendas los mecanismos y conceptos que muestro aquí. No se trata de que los vayas a necesitar, sino de que, para crear algo con menor esfuerzo, sí será necesario entender cómo funcionan ciertas cosas. Para ello, entender estos conceptos, que pueden parecer complicados y sin sentido, hará que todo sea mucho más sencillo en el futuro.

De nuevo, no es necesario entender programación estructural para crear un Asesor Experto ni un indicador, ya que podemos hacerlo mediante programación convencional. Si dudas de lo que acabo de decir, consulta mi primer artículo: «Desarrollo de un EA de trading desde cero». En él, sin utilizar absolutamente nada de programación estructural ni orientada a objetos, mostré cómo crear un Asesor Experto sencillo que nos permite enviar órdenes directamente en el gráfico.

Lo mismo se aplica a los indicadores. En el artículo «Del básico al intermedio: Indicador (IV)», mostré cómo trazar un indicador en el gráfico. Todo esto es sencillo y no requiere muchos conocimientos. Solo se necesita sentido común y entender la documentación de MQL5 para crear la aplicación deseada.

Una vez establecida esta distinción entre lo que es y no es necesario saber para crear cosas, podemos volver a nuestro ejemplo hipotético. Como sabemos que existen elementos comunes entre seres vivos y no vivos, podemos separar las cosas de una manera lógica y coherente, tanto desde el punto de vista de la implementación como desde el de la usabilidad y el mantenimiento de funciones y procedimientos.

Para que esto se entienda debidamente, volvamos nuestra atención a uno de los códigos vistos en el artículo anterior. El mismo puede verse replicado 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 01

Cuando ejecutamos este código 01, tendremos como resultado lo que se muestra en la siguiente imagen.

Imagen 01

Ahora viene la parte a la que quiero llegar. Del mismo modo que los animales y las plantas tienen elementos en común, en el código 01 tenemos k_value como elemento común para st_Reg y st_Bio. Sin embargo, estas estructuras no se comunican entre sí, y son completamente distintas. A nivel visual, esto se muestra en la imagen siguiente.

Imagen 02

Es decir, dos entidades distintas, pero con cosas en común. Esa duplicación de código se daría cuando se compartieran elementos con la misma forma de manipulación y acceso, ya que no sería necesario que estuvieran separados. Esto solo dificulta y complica el código de manera totalmente innecesaria.

Para que esto quede claro, imagina que decides mejorar la función Get_K, presente en la estructura st_Reg, por cualquier motivo. Al hacerlo, te das cuenta de que el código general ha mejorado mucho, pero no haces lo mismo en st_Bio. Cuando utilizas ambas estructuras al mismo tiempo, te das cuenta de que st_Bio, a pesar de usar Get_K, no tiene el mismo comportamiento que st_Reg.

Y es en este punto donde comienzan los problemas. Se copia el código de Get_K, presente en la estructura st_Reg, a la estructura st_Bio y, después de un tiempo, se necesita modificar nuevamente el código Get_K, lo que implica hacerlo en ambas estructuras.

Además de suponer una gran pérdida de tiempo, este tipo de cosas dificulta enormemente la mejora del código. Y aquí solo estamos usando dos estructuras. En un código real, puede haber decenas o incluso cientos de estructuras que tengan algo en común. ¿Te imaginas tener que corregir y hacer el mantenimiento de este tipo de código? Sería un trabajo infernal. Sin embargo, existe una forma sencilla y muy práctica de simplificar todo este trabajo. Esta forma consiste precisamente en usar herencia.

Una herencia no es más que tomar esta parte común en ambas estructuras y colocarla en otra estructura. Y luego, de alguna forma, hacer que la estructura pueda usar el código que está heredando. Dicho así, parece complicado. Pero, en la práctica, verás que es mucho más simple hacer esto de lo que probablemente estés imaginando.

Entonces, pongamos esto en práctica, haciendo que el código 01 pase a hacer uso de herencia. Para lograrlo, el primer paso que hay que dar se muestra en el fragmento de código que se ve justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private:
07.         uint KeyValue;
08.     public:
09.     //+----------------+
10.         void SetKey(const uint arg) { KeyValue = arg; }
11.     //+----------------+
12.         uint GetKey(void) { return KeyValue; }
13. };
14. //+------------------------------------------------------------------+
15. struct st_Reg
16. {
17.     private:
18.         string  Value;
19.     public:
20.     //+----------------+
21.         void SetValue(const uint arg1, const string arg2)
22.         {
23.             Value = arg2;
24.         }
25.     //+----------------+
26.         string GetValue(void)  { return Value; }
27. };
28. //+------------------------------------------------------------------+
29. struct st_Bio
30. {
31.     private:
32.         string  Value[2];
33.     public:
34.     //+----------------+
35.         void SetValue(const uint arg1, const string arg2, const string arg3)
36.         {
37.             Value[0] = arg2;
38.             Value[1] = arg3;
39.         }
40.     //+----------------+
41.         bool GetValue(string &arg1, string &arg2)
42.         {
43.             arg1 = Value[0];
44.             arg2 = Value[1];
45. 
46.             return true;
47.         }
48.     //+----------------+
49. };
50. //+------------------------------------------------------------------+
                   .
                   .
                   .

Código 02

En este fragmento del código 02, podemos ver que se ha creado una nueva estructura. Se llama st_Base. Ahora presta atención, querido lector. Aunque he mejorado el tema de las variables vistas en este fragmento (el código 02), aún contiene el mismo número y tipo de variables que el código 01. Sin embargo, está mejor organizada. Pero lo más importante aquí es, de hecho, esta estructura st_Base. Hecho esto, lo que tenemos ahora se muestra justo a continuación.

Imagen 03

Observa que se parece bastante a la imagen 02. Pero esta imagen 03 no hace uso de la herencia. Esto se debe a que el fragmento del código 02 tampoco la utiliza. Por tanto, si intentas compilar el código 01 usando este fragmento del código 02, el compilador te mostrará diversos errores. Para solucionarlo, necesitamos transformar esta imagen 03 en la imagen 04, que se muestra a continuación.

Imagen 04

En esta imagen 04, las flechas indican cómo se hará la herencia. Es decir, vamos a incluir el contenido de la estructura st_Base dentro de las estructuras st_Bio y st_Reg, sin modificar demasiado el código. Para ello, basta con cambiar el código 02 por el que se ve justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private:
07.         uint KeyValue;
08.     public:
09.     //+----------------+
10.         void SetKey(const uint arg) { KeyValue = arg; }
11.     //+----------------+
12.         uint GetKey(void) { return KeyValue; }
13. };
14. //+------------------------------------------------------------------+
15. struct st_Reg : public st_Base
16. {
17.     private:
18.         string  Value;
19.     public:
20.     //+----------------+
21.         void SetValue(const uint arg1, const string arg2)
22.         {
23.             Value = arg2;
24.         }
25.     //+----------------+
26.         string GetValue(void)  { return Value; }
27. };
28. //+------------------------------------------------------------------+
29. struct st_Bio : public st_Base
30. {
31.     private:
32.         string  Value[2];
33.     public:
34.     //+----------------+
35.         void SetValue(const uint arg1, const string arg2, const string arg3)
36.         {
37.             Value[0] = arg2;
38.             Value[1] = arg3;
39.         }
40.     //+----------------+
41.         bool GetValue(string &arg1, string &arg2)
42.         {
43.             arg1 = Value[0];
44.             arg2 = Value[1];
45. 
46.             return true;
47.         }
48.     //+----------------+
49. };
50. //+------------------------------------------------------------------+
                   .
                   .
                   .

Código 03

Vaya, mira lo complicado que fue implementar la herencia en el código. Sin embargo, puedes ver que hay referencias a valores que aún no se han resuelto en este fragmento del código 03. Esto se debe a que en este fragmento solo implementamos la herencia. Aún no la estamos utilizando en la práctica de forma que permita aplicar la información sobre KeyValue. El objetivo es permitir que st_Data pueda establecer un vínculo entre los datos para construir lo que se ve en la imagen 01.

Para resolver tales vínculos, necesitamos modificar una vez más el código. Y, con esto, quedará como se muestra justo a continuación.

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

Código 04

Este código 04 tiene el mismo efecto que el código 01. Sin embargo, en este código 04 estamos haciendo uso de la herencia entre estructuras. Por tanto, cualquier mejora que realices en la estructura st_Base beneficiará automáticamente a todo el código. Es decir, cualquier cambio, por pequeño que sea, realizado en la estructura st_Base, será accesible automáticamente para st_Reg y st_Bio.

Siempre que el procedimiento, la función o la variable se coloquen dentro de la cláusula pública, claro. Si se realiza una adición dentro de la cláusula privada de la estructura st_Base, ni st_Reg ni st_Bio se enterarán de dicha adición, y esta quedará restringida a la clase st_Base.

Un punto que no mencioné en el código 04 y que puede llegar a ser importante es el hecho de que en las líneas 15 y 30 la herencia se está realizando de forma pública. Esto permite que cualquier variable del tipo de una de las dos estructuras también tenga acceso a cualquier elemento público de la estructura st_Base. Si en lugar de las palabras reservadas public, que pueden verse en las líneas mencionadas, estuviéramos usando la palabra private, cualquier variable que utilizara la estructura, ya fuera st_Bio o st_Reg, no tendría acceso a lo que hay en la estructura st_Base. Intenta hacer esto después con el archivo que estará en el anexo para entender mejor este tipo de cosas, querido lector.

Como el tema principal de este artículo es la herencia, ¿qué os parece si jugamos un poco con él? Pero de una forma un poco más divertida. Para ello, pasemos a un nuevo tema. No quiero que te asustes y pienses que voy a mostrar algo que solo un profesional altamente cualificado podría hacer. No es así, de hecho. Cualquiera de ustedes que esté estudiando y siguiendo esta serie de artículos que estoy publicando podría hacer algo parecido.

Para ello, sin embargo, sería necesario entender y aplicar ciertos conceptos que se mostraron y explicaron en los artículos anteriores. Así que vamos a la fiesta de pijamas, porque si te ha parecido divertido lo que se ha hecho hasta ahora, te maravillarás con lo que veremos ahora.


Estructuras, plantillas y herencia: Una combinación del ruido

Muchas personas no son conscientes de lo que puede hacer MQL5, aunque no tenga todas las capacidades de C y C++. Y es que, en comparación con C y C++, programar en MQL5 es mucho más sencillo. Si estos artículos trataran sobre C y C++, creo que muchos de ustedes ya habrían desistido y habrían buscado hacer otra cosa en la vida. Pero como aquí me centro solo en cómo programar en MQL5, los artículos estarán compuestos principalmente por cosas muy simples. Pero, por no haberse explorado mucho, terminan convirtiéndose en algo casi místico.

Una de estas cosas es la combinación de todo lo visto hasta ahora. Es decir, ¿podemos combinar una programación estructural con la posibilidad de sobrecarga de estructuras y, al mismo tiempo, utilizar la herencia en un único código? Sí, querido lector. Y, aunque esto pueda parecer material avanzado, para mí sigue siendo básico, solo que un poco más elaborado que todo lo visto hasta ahora.

Sin embargo, si estudias y reflexionas sobre los conceptos que hemos visto hasta ahora, acabarás pensando en cómo unir todas estas cosas en un código realmente interesante, ya que podemos utilizar algo que, para muchos, supondría escribir mucho código. Pero lo haremos con un código muy sencillo y dejaremos todo el trabajo para que lo resuelva el compilador.

Como no quiero asustarte mostrando cómo quedaría el código del tema anterior al usar todos estos recursos a la vez, empezaremos poco a poco. Así, te irás acostumbrando a la idea y percibirás la importancia de entender los conceptos en lugar de intentar memorizar y copiar códigos al azar para que funcionen después.

Bien, nuestro código va a comenzar de la siguiente forma, como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+

Código 05

Bien, creo que todos pueden entender este código 05. Entonces podemos pasar a lo que sería el siguiente paso en la implementación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. }
40. //+------------------------------------------------------------------+

Código 06

Genial, ahora ya tenemos implementada la parte referente a la herencia. Tenemos dos estructuras muy simples y bastante básicas. Ahora es el momento del siguiente paso. En este momento, vamos a implementar lo que podría considerarse una lista. Esto también es muy simple y puede verse justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. struct st_List
42. {
43.     private :
44.         st_Dev01    List[];
45.         uint        nElements;
46.     public  :
47.     //+----------------+
48.         void Clear(void)
49.         {
50.             ArrayFree(List);
51.             nElements = 0;
52.         }
53.     //+----------------+
54.         bool AddList(const st_Dev01 &arg)
55.         {
56.             nElements += (nElements == 0 ? 2 : 1);
57.             ArrayResize(List, nElements);
58.             List[nElements - 1] = arg;
59. 
60.             return true;
61. 
62.         }
63.     //+----------------+
64.         st_Dev01 SearchKey(const uint arg)
65.         {
66.             for (uint c = 1; c < nElements; c++)
67.                 if (List[c].GetKey() == arg)
68.                     return List[c];
69. 
70.             return List[0];
71.         }
72.     //+----------------+
73. };
74. //+------------------------------------------------------------------+

Código 07

Como puedes ver, no hemos hecho nada diferente de lo que se hizo antes. Todo sigue igual de simple y didáctico. Ahora vamos a crear una pequeña lista de información para verificar que todo funciona correctamente. Esto se hará en el código que puedes ver a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     const string Names[] = 
11.         {
12.             "Daniel Jose",
13.             "Edimarcos Alcantra",
14.             "Carlos Almeida",
15.             "Yara Alves"
16.         };
17. 
18.     st_List list;
19. 
20.     for (uint c = 0; c < Names.Size(); c++)
21.     {
22.         st_Dev01 info;
23. 
24.         info.SetKeyInfo(c, Names[c]);
25.         list.AddList(info);
26.     }
27. 
28.     PrintX(list.SearchKey(2).GetInfo());
29. }
30. //+------------------------------------------------------------------+

Código 08

Fíjate que todo ese código, que se veía en el código 07, en realidad era un archivo de encabezado. Se agrega al código 08 en la línea cuatro. En la línea 10, creamos un pequeño array que contiene algunos nombres. En la línea 18 tenemos la estructura de datos. En la línea 25, creamos la estructura propiamente dicha. En la línea 28, usando una clave, buscamos qué información pertenece a esa clave específica. Como resultado de la ejecución del código, obtenemos lo que se muestra a continuación.

Imagen 05

Bien, nada del otro mundo. Todo tal y como se esperaba, y tal y como se ha mostrado y estudiado hasta ahora. Pero ahora viene la novedad y, junto con ella, un verdadero parque de atracciones. La novedad es que ahora vamos a incluir la sobrecarga en este sistema de lista. Para que las cosas resulten lo menos intimidantes posible, empezaremos de atrás hacia adelante. De esta forma, el código del archivo de encabezado se modificará para que quede como se muestra a continuación. Recordemos que el código del archivo de encabezado era el 07 originalmente.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. template <typename T>
42. struct st_List
43. {
44.     private :
45.         T    List[];
46.         uint nElements;
47.     public  :
48.     //+----------------+
49.         void Clear(void)
50.         {
51.             ArrayFree(List);
52.             nElements = 0;
53.         }
54.     //+----------------+
55.         bool AddList(const T &arg)
56.         {
57.             nElements += (nElements == 0 ? 2 : 1);
58.             ArrayResize(List, nElements);
59.             List[nElements - 1] = arg;
60. 
61.             return true;
62. 
63.         }
64.     //+----------------+
65.         T SearchKey(const uint arg)
66.         {
67.             for (uint c = 1; c < nElements; c++)
68.                 if (List[c].GetKey() == arg)
69.                     return List[c];
70. 
71.             return List[0];
72.         }
73.     //+----------------+
74. };
75. //+------------------------------------------------------------------+

Código 09

Y, para que el código 08 siga funcionando, ahora necesitamos actualizarlo a lo que se ve justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     const string Names[] = 
11.         {
12.             "Daniel Jose",
13.             "Edimarcos Alcantra",
14.             "Carlos Almeida",
15.             "Yara Alves"
16.         };
17. 
18.     st_List <st_Dev01> list;
19. 
20.     for (uint c = 0; c < Names.Size(); c++)
21.     {
22.         st_Dev01 info;
23.         
24.         info.SetKeyInfo(c, Names[c]);
25.         list.AddList(info);
26.     }
27. 
28.     PrintX(list.SearchKey(2).GetInfo());
29. }
30. //+------------------------------------------------------------------+

Código 10

Observa que, en este código 10, cambiamos única y exclusivamente la línea 18, comparándolo con lo que se encontraba originalmente en el código 08.

Perfecto. Ahora tenemos una pequeña sobrecarga con respecto al tipo de lista que podemos crear. Dado que este tipo de cosas es exactamente lo que mostramos recientemente en los artículos, no hay motivo para entrar en pánico. El siguiente paso es un poco más complicado de entender. Sin embargo, como no existe una forma sencilla de explicar cómo se hará paso a paso, tendremos que verlo de una sola vez. O, mejor dicho, en dos veces. El primer golpe se dará en el archivo de encabezado y el segundo, en el archivo principal. Así que no te asustes, querido lector. Solo procura no distraerte para entender lo que se hará.

Primero, el archivo de encabezado. Este se ve ahora en el código mostrado a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename KEY>
05. struct st_Base
06. {
07.     private :
08.         KEY KeyValue;
09.     public  :
10.     //+----------------+
11.         void SetKey(const KEY arg)
12.         {
13.             KeyValue = arg;
14.         }
15.     //+----------------+
16.         KEY GetKey(void)
17.         {
18.             return KeyValue;
19.         }
20.     //+----------------+
21. };
22. //+------------------------------------------------------------------+
23. template <typename KEY>
24. struct st_Dev01 : public st_Base <KEY>
25. {
26.     private :
27.         string ValueInfo;
28.     public  :
29.     //+----------------+
30.         void SetKeyInfo(KEY arg1, string arg2)
31.         {
32.             SetKey(arg1);
33.             ValueInfo = arg2;
34.         }
35.     //+----------------+
36.         string GetInfo(void)
37.         {
38.             return ValueInfo;
39.         }
40.     //+----------------+
41. };
42. //+------------------------------------------------------------------+
43. template <typename T, typename KEY>
44. struct st_List
45. {
46.     private :
47.         T    List[];
48.         uint nElements;
49.     public  :
50.     //+----------------+
51.         void Clear(void)
52.         {
53.             ArrayFree(List);
54.             nElements = 0;
55.         }
56.     //+----------------+
57.         bool AddList(const T &arg)
58.         {
59.             nElements += (nElements == 0 ? 2 : 1);
60.             ArrayResize(List, nElements);
61.             List[nElements - 1] = arg;
62. 
63.             return true;
64. 
65.         }
66.     //+----------------+
67.         T SearchKey(const KEY arg)
68.         {
69.             for (uint c = 1; c < nElements; c++)
70.                 if (List[c].GetKey() == arg)
71.                     return List[c];
72. 
73.             return List[0];
74.         }
75.     //+----------------+
76. };
77. //+------------------------------------------------------------------+

Código 11

Observa lo que ocurrió en el archivo de encabezado de este código 11 y compáralo con los archivos anteriores que has visto en este artículo. Verás que el código 11 es mucho más interesante y divertido que los anteriores. Pero no hay motivo para asustarse. No hay motivo para ello, al menos no de la forma en que vengo explicando las cosas. Cuando aprendí a hacer esto hace algunos años en los lenguajes C y C++, fue bastante difícil, ya que era complicado encontrar a alguien que me explicara cómo funcionaba. Pero aquí entenderás cómo funciona, mi querido lector.

Observa lo siguiente: en la línea cuatro, el compilador realizará una sobrecarga. Esto se debe precisamente a que estamos definiendo una plantilla para un código estructural. Como ya sabrás, el compilador creará una versión para cada tipo de dato que se cree o que sea necesario crear. Esta es la parte fácil. Ahora viene la parte confusa, al menos cuando intenté aprender a crear esto.

Como st_Dev01 hereda la estructura st_Base, que es una plantilla, st_Dev01 TAMBIÉN DEBERÁ definirse como una estructura plantilla. Es decir, da igual lo que pienses: el simple hecho de que una estructura de datos herede una plantilla de estructura de datos obliga a que la estructura que la hereda también sea una plantilla. Esto no es muy complicado, ya que todo lo que necesitamos hacer es definir la línea 23.

Sin embargo, y aquí está la parte complicada, necesitamos pasar este tipo, que se define en la línea 23, a la estructura base. Es decir, st_Base necesita recibir este valor que se definirá posteriormente en el código. Parece sencillo, pero créeme, tardé mucho tiempo en darme cuenta de que tenía que cambiar la declaración de la línea 22, que se ve en el código 09, por la de la línea 24, que se ve en el código 11.

No tienen idea de cuánto sufrí hasta aprender a hacer esto, ya que en aquella época NADIE enseñaba cómo hacerlo. Supongo que siempre imaginas que tendrías que definir primero lo que se ve en la línea 23 y, a continuación, definir lo que se ve en la línea 30. Pero, si no se corrige la definición de la línea 24, el código NO SE COMPILA DE NINGUNA MANERA.

Ahora, como nuestra lista utilizará una estructura de datos que se sobrecargará dependiendo de cada caso específico, también debemos modificar el código de declaración de la estructura st_List. Pero es fácil de resolver, ya que solo hay que cambiar la línea 43 y, a continuación, ajustar la línea 57. Y bien. Nuestro archivo de encabezado está casi listo. La única cosa que aún lo ata a un tipo de dato es la declaración de la línea 27. Pero eso lo cambiaremos después. Ahora veamos cómo quedó el archivo principal. Podemos verlo justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     #define def_TypeKey uint
11. 
12.     const string Names[] = 
13.         {
14.             "Daniel Jose",
15.             "Edimarcos Alcantra",
16.             "Carlos Almeida",
17.             "Yara Alves"
18.         };
19. 
20.     st_List <st_Dev01 <def_TypeKey>, def_TypeKey> list;
21. 
22.     for (uint c = 0; c < Names.Size(); c++)
23.     {
24.         st_Dev01 <def_TypeKey> info;
25. 
26.         info.SetKeyInfo(c, Names[c]);
27.         list.AddList(info);
28.     }
29. 
30.     PrintX(list.SearchKey(2).GetInfo());
31. 
32.     #undef def_TypeKey
33. }
34. //+------------------------------------------------------------------+

Código 12

Santa ignorancia. Misericordia, y que Dios tenga piedad de todos nosotros. Este tipo está completamente fuera de lugar. ¿Cómo se atreve a venir y presentarnos un código como este, que se ve justo arriba? ¿No nos tienes cariño a los simples lectores que estamos empezando en la programación? ¿Quieres matarnos definitivamente solo con mirar tus códigos locos?

Cálmate, querido lector. Cálmate. Este código 12 es tan simple que resulta casi insulso, a pesar de ser bastante divertido. Pero presta atención. Como necesitamos repetir ciertas cosas en diversos puntos del código, utilizo la directiva de la línea 10 para crear una definición que permita realizar un cambio rápido y seguro en todos ellos. En este código 12, solo necesitamos cambiar dos puntos. El primero es la línea 20. Compara la declaración de esta variable con la declaración de la misma variable en el código 10.

Como la sobrecarga también afecta a la estructura de datos que estamos creando, también debemos cambiar la declaración de la línea 24, donde declaramos la estructura que se almacenará en la lista. El resto del código permanece inalterado. De hecho, la salida será la misma que se muestra en la imagen 05.

Sin embargo, aún no hemos terminado con este tema. Como todavía tenemos margen para introducir una mejora más, la haremos ahora. Esta hará que la lista estructural sea casi perfecta. Digo «casi», porque existe un pequeño problema que no es posible resolver sin hacer uso de clases, es decir, programación orientada a objetos. Pero esto se verá más adelante, cuando muestre en qué consiste el problema y cómo lo soluciona la programación orientada a objetos.

De cualquier forma, respira hondo, porque vamos a sumergirnos aún más. Entonces, volviendo al código del archivo de encabezado, lo modificaremos nuevamente a lo que se ve justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename KEY>
05. struct st_Base
06. {
07.     private :
08.         KEY KeyValue;
09.     public  :
10.     //+----------------+
11.         void SetKey(const KEY arg)
12.         {
13.             KeyValue = arg;
14.         }
15.     //+----------------+
16.         KEY GetKey(void)
17.         {
18.             return KeyValue;
19.         }
20.     //+----------------+
21. };
22. //+------------------------------------------------------------------+
23. template <typename KEY, typename INFO>
24. struct st_Dev01 : public st_Base <KEY>
25. {
26.     private :
27.         INFO ValueInfo;
28.     public  :
29.     //+----------------+
30.         void SetKeyInfo(KEY arg1, INFO arg2)
31.         {
32.             SetKey(arg1);
33.             ValueInfo = arg2;
34.         }
35.     //+----------------+
36.         INFO GetInfo(void)
37.         {
38.             return ValueInfo;
39.         }
40.     //+----------------+
41. };
42. //+------------------------------------------------------------------+
43. template <typename T, typename KEY>
44. struct st_List
45. {
46.     private :
47.         T    List[];
48.         uint nElements;
49.     public  :
50.     //+----------------+
51.         void Clear(void)
52.         {
53.             ArrayFree(List);
54.             nElements = 0;
55.         }
56.     //+----------------+
57.         bool AddList(const T &arg)
58.         {
59.             nElements += (nElements == 0 ? 2 : 1);
60.             ArrayResize(List, nElements);
61.             List[nElements - 1] = arg;
62. 
63.             return true;
64. 
65.         }
66.     //+----------------+
67.         T SearchKey(const KEY arg)
68.         {
69.             for (uint c = 1; c < nElements; c++)
70.                 if (List[c].GetKey() == arg)
71.                     return List[c];
72. 
73.             return List[0];
74.         }
75.     //+----------------+
76. };
77. //+------------------------------------------------------------------+

Código 13

Como puedes ver, lo único que cambié en este código 13 fue la línea 23. Solo eso. Ahora podemos usar cualquier tipo de dato como clave y también como información vinculada a la clave. Entendiendo esto, podemos ver ahora cómo quedó el código principal. Se muestra justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     #define def_TypeKey     string
11.     #define def_TypeInfo    string
12. 
13.     const string Names[][2] = 
14.         {
15.             "Daniel Jose"       , "Chief Programmer",
16.             "Edimarcos Alcantra", "Programmer",
17.             "Carlos Almeida"    , "Junior Programmer",
18.             "Yara Alves"        , "Accounting"
19.         };
20. 
21.     st_List <st_Dev01 <def_TypeKey, def_TypeInfo>, def_TypeKey> list;
22. 
23.     for (int c = 0; c < ArrayRange(Names, 0); c++)
24.     {
25.         st_Dev01 <def_TypeKey, def_TypeInfo> info;
26. 
27.         info.SetKeyInfo(Names[c][1], Names[c][0]);
28.         list.AddList(info);
29.     }
30. 
31.     PrintX(list.SearchKey("Chief Programmer").GetInfo());
32.     PrintX(list.SearchKey("Guest").GetInfo());
33.     
34.     #undef def_TypeKey
35.     #undef def_TypeInfo
36. }
37. //+------------------------------------------------------------------+

Código 14

Observa que el código no ha cambiado prácticamente nada. Sin embargo, estas pocas diferencias son suficientes para que tengamos un código muy simple de usar y con resultados interesantes, tanto desde el punto de vista práctico como desde el de las posibilidades. Como los cambios son muy sencillos, te daré la oportunidad de intentar comprender cómo funciona este código, mi querido lector. De todas formas, te mostraré el resultado que presenta. Esto se puede ver en la imagen siguiente.

Imagen 06

Qué cosa más linda. Esta imagen 06 es una verdadera delicia, ya que muestra cómo cosas aparentemente complicadas pueden volverse súper simples, siempre que procures estudiar y practicar lo que se está mostrando en los artículos.


Consideraciones finales

Este es, sin duda, un artículo al que deberás dedicar varios minutos para entender cómo y por qué funcionan las cosas que se muestran aquí. Esto se debe a que todo lo que se ha visto y mostrado aquí está originalmente dirigido a la programación orientada a objetos. Muchos consideran poco probable crear algo que se divulga solo como un tipo de programación, pero que, en realidad, tiene como base y principios una programación completamente estructural.

Como dije, antes de explicar qué es la programación orientada a objetos, vamos a navegar y sumergirnos en lo que sería la base de origen de este modelo de programación, ya que surgió para resolver una dificultad de la programación estructural. Sin embargo, muchas de las cosas que se dicen que son exclusivas de la programación orientada a objetos no se hacen en ella, sino en la programación estructural.

Entonces, mi querido lector, procura practicar y estudiar cada punto que se ha mostrado en estos artículos. Usa los códigos del anexo como un medio para lograr aprender y entender, de forma correcta, cómo programar de verdad. Y, en el próximo artículo, la diversión continuará. Así que nos vemos pronto.

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

Archivos adjuntos |
Anexo.zip (2.52 KB)
Simulación de mercado (Parte 20): Iniciando el SQL (III) Simulación de mercado (Parte 20): Iniciando el SQL (III)
Aunque podemos hacer cosas con una base de datos de unas 10 entradas, esto se asimila mucho mejor cuando trabajamos con un archivo que tenga más de 15 mil registros. Es decir, si tú intentaras crear eso manualmente, sería una tarea enorme. Sin embargo, es difícil encontrar una base de datos, incluso con fines didácticos, disponible para descargar. Pero, en realidad, no necesitamos recurrir a eso. Podemos usar MetaTrader 5 para crear una base de datos para nosotros. En este artículo, veremos cómo hacerlo.
Redes neuronales en el trading: Optimización de LSTM para la predicción de series temporales multivariadas (Final) Redes neuronales en el trading: Optimización de LSTM para la predicción de series temporales multivariadas (Final)
Continuamos implementando el framework DA-CG-LSTM, que ofrece métodos innovadores para el análisis y pronóstico de series temporales. El uso de CG-LSTM y atención dual permite una detección más precisa de las dependencias de largo y corto plazo en los datos, lo cual resulta particularmente útil para trabajar con los mercados financieros.
Creación de interfaces gráficas dinámicas MQL5 mediante el escalado de imágenes basado en recursos con interpolación bicúbica en gráficos de trading Creación de interfaces gráficas dinámicas MQL5 mediante el escalado de imágenes basado en recursos con interpolación bicúbica en gráficos de trading
En este artículo exploramos las interfaces gráficas dinámicas MQL5, utilizando interpolación bicúbica para un escalado de imágenes de alta calidad en los gráficos de trading. Detallamos opciones de posicionamiento flexibles que permiten el centrado dinámico o el anclaje en esquina con desplazamientos personalizados.
Automatización de estrategias de trading en MQL5 (Parte 16): Ruptura del rango de medianoche con BoS (Break of Structure) basada en la acción del precio Automatización de estrategias de trading en MQL5 (Parte 16): Ruptura del rango de medianoche con BoS (Break of Structure) basada en la acción del precio
En este artículo, automatizamos la estrategia de ruptura de rango de medianoche con ruptura de estructura en MQL5 y detallamos el código para la detección de ruptura y la ejecución de operaciones. Definimos parámetros de riesgo precisos para entradas, stops y ganancias. Se incluyen pruebas retrospectivas y optimización para el trading práctico.