Descargar MetaTrader 5

Estructuras, clases e interfaces

Estructuras

Una estructura es un conjunto de elementos del tipo libre, salvo el tipo void. De esa manera, la estructura une los datos de diferentes tipos que están vinculados de forma lógica.

Declaración de estructura

El tipo de datos estructural se define de la siguiente manera:

struct nombre_de_estructura 
  {
   descripción_de_elementos
  };

No se puede usar el nombre de la estructura en calidad del identificador (nombre de la variable o función). Hay que tener en cuenta que en MQL5 los elementos de una estructura siguen directamente uno detrás del otro sin que sean alineados. En el lenguaje C++ este comando se proporciona al compilador mediante la instrucción

#pragma pack(1)

Si hace falta hacer otra alineación dentro de la estructura, es necesario utilizar los elementos "de relleno" adicionales de tamaño necesario.

Ejemplo:

struct trade_settings
  {
   uchar  slippage;     // valor del deslizamiento permitido — tamaño 1 byte
   char   reserved1;    // 1 byte de permiso
   short  reserved2;    // 2 bytes de permiso
   int    reserved4;    // otros 4 bytes de permiso. Aseguramos la alineación al margen de 8 bytes
   double take;         // valor del precio de fijación del beneficio
   double stop;         // valor del precio del stop de protección
  };

Esta descripción de alineación de las estructuras es necesaria unicamente para la transmisión a las funciones dll importadas.

Atención: este ejemplo refleja los datos proyectados de una manera errónea. Sería mejor declarar al principio los datos take y stop de mayor tamaño que el tipo double, y luego declarar el elemento slippage del tipo uchar. En este caso, la presentación interna de los datos siempre va a ser igual independientemente del valor indicado en #pragma pack().

Si la estructura contiene las variables del tipo string y/o el objeto del array dinámico, entonces para esta estructura el compilador asigna un constructor implícito donde se efectúa la anulación de todos los elementos del tipo string y la inicialización correcta para el objeto del array dinámico.

Estructuras simples

Las estructuras que no contengan cadenas, objetos de clase, punteros y objetos de array dinámico se llaman estructuras simples. Las variables de las estructuras sencillas, así como sus arrays se pueden transmitir como parámetros a las funciones importadas de DLL.

El copiado de estructuras sencillas está permitido solo en dos casos:

  • si los objetos pertenecen a un tipo de estructura
  • si los objetos están relacionados entre sí por una línea de herencia, es decir, una estructura es heredera de la otra.

Mostraremos esto con ejemplos y crearemos la estructura de usuario CustomMqlTick, idéntica en su composición a la estructura incorporada MqlTick. El compilador no permitirá intentos de copiar el valor del objeto MqlTick en un objeto del tipo CustomMqlTick. La conversión directa al tipo necesario también provocará el error de compilación:

      //--- copiar estructuras simples de tipos diferentes está prohibido
      my_tick1=last_tick;               // el compilador aquí dará error
     
      //--- tampoco está permitido convertir estructuras de diferente tipo
      my_tick1=(CustomMqlTick)last_tick;// el compilador aquí dará error

Por eso, solo queda una opción: copiar los valores de los miembros de la estructura elemento a elemento. Pero, en este caso, está permitido copiar los valores de los objetos de un mismo tipo CustomMqlTick.

      CustomMqlTick my_tick1,my_tick2;
      //--- así, sí está permitido copiar objetos de una misma estructura CustomMqlTick
      my_tick2=my_tick1;
     
      //--- vamos a crear un array de objetos de la estructura simple CustomMqlTick y registraremos en ella los valores
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

Como comprobación, se llama la función ArrayPrint() para mostrar el diario de cambios del array arr[].

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- creamos una estructura igual que la MqlTick incorporada
   struct CustomMqlTick
     {
      datetime          time;          // Hora de la última actualización de precio
      double            bid;           // Precio Bid actual
      double            ask;           // Precio Ask actual
      double            last;          // Precio actual de la última transacción (Last)
      ulong             volume;        // Volumen actual para el último precio Last
      long              time_msc;      // Hora de la última actualización en milisegundos
      uint              flags;         // Banderas de los ticks     
     };
   //--- obtenemos los valores del último tick
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- tratamos de copiar los datos de MqlTick a CustomMqlTick
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- está prohibido copiar estructuras simples no relacionadas
      //1. my_tick1=last_tick;               // el compilador aquí dará error
     
      //--- tampoco está permitido convertir estructuras no relacionadas entre sí
      //2. my_tick1=(CustomMqlTick)last_tick;// el compilador aquí dará error
     
      //--- por eso, copiamos los miembros de la estructura elemento a elemento     
      my_tick1.time=last_tick.time;
      my_tick1.bid=last_tick.bid;
      my_tick1.ask=last_tick.ask;
      my_tick1.volume=last_tick.volume;
      my_tick1.time_msc=last_tick.time_msc;
      my_tick1.flags=last_tick.flags;
     
      //--- así, sí está permitido copiar objetos de una misma estructura CustomMqlTick
      my_tick2=my_tick1;
     
      //--- vamos a crear un array de objetos de la estructura simple CustomMqlTick y registraremos en ella los valores
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
//--- ejemplo de muestra de los valores de un array que contiene objetos del tipo CustomMqlTick
      /*
                       [time]   [bid]   [ask]   [last] [volume]    [time_msc] [flags]
      [0] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2
      [1] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2           
      */
     }
   else
      Print("SymbolInfoTick() failed, error = ",GetLastError());
  }

El segundo ejemplo muestra las posibilidades de copiado de estructuras simples según la línea de herencia. Tenemos la estructura básica Animal, de la cual se generan mediante herencia las estructuras Cat y Dog. Podemos copiar entre sí los objetos Animal y Cat, Animal y Dog, pero no podemos copiar entre sí Cat y Dog, aunque ambos sean descendientes de la estructura Animal.

//--- estructura para la descripción de perros
struct Dog: Animal
  {
   bool              hunting;       // raza cazadora
  };
//--- estructura para la descripción de gatos
struct Cat: Animal
  {
   bool              home;          // raza doméstica
  };
//--- creamos los objetos de las subclases
   Dog dog;
   Cat cat;
//--- podemos copiar del padre al descendiente (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // los perros saben nadar
//--- no se pueden copiar objetos de las subestructuras (Dog != Cat)
   cat=dog;        // el compilador aquí dará error

Código completo del ejemplo:

//--- estructura básica para la descripción de animales
struct Animal
  {
   int               head;          // número de cabezas
   int               legs;          // número de patas
   int               wings;         // número de alas
   bool              tail;          // existencia de cola
   bool              fly;           // vuela
   bool              swim;          // nada  
   bool              run;           // corre
  };
//--- estructura para la descripción de perros
struct Dog: Animal
  {
   bool              hunting;       // raza cazadora
  };
//--- estructura para la descripción de gatos
struct Cat: Animal
  {
   bool              home;          // raza doméstica
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- creamos un objeto del tipo básico Animal y lo describimos
   Animal some_animal;
   some_animal.head=1;
   some_animal.legs=4;
   some_animal.wings=0;
   some_animal.tail=true;
   some_animal.fly=false;
   some_animal.swim=false;
   some_animal.run=true;
//--- creamos objetos de los subtipos
   Dog dog;
   Cat cat;
//--- podemos copiar del padre al descendiente (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // los perros saben nadar
//--- no se pueden copiar objetos de las subestructuras (Dog != Cat)
   //cat=dog;        // el compilador aquí dará error
//--- por eso solo se puede copiar elemento a elemento
   cat.head=dog.head;
   cat.legs=dog.legs;
   cat.wings=dog.wings;
   cat.tail=dog.tail;
   cat.fly=dog.fly;
   cat.swim=false;   // los gatos no saben nadar
//--- se pueden copiar los valores del padre al descendiente
   Animal elephant;
   elephant=cat;
   elephant.run=false;// los elefantes no pueden correr
   elephant.swim=true;// los elefantes nadan
//--- creamos un array
   Animal animals[4];
   animals[0]=some_animal;
   animals[1]=dog;  
   animals[2]=cat;
   animals[3]=elephant;
//--- realizamos la impresión
   ArrayPrint(animals);
//--- resultado de la ejecución
/*
       [head] [legs] [wings] [tail] [fly] [swim] [run]
   [0]      1      4       0   true false  false  true
   [1]      1      4       0   true false   true  true
   [2]      1      4       0   true false  false false
   [3]      1      4       0   true false   true false
*/  
  }

Otro método para copiar tipos simples es el uso de las uniones, para ello, los objetos de estas estructuras deberán ser miembros de una misma unión, vea los ejemplos en union.

Acceso a los elementos de la estructura

El nombre de la estructura es un tipo de datos nuevo y permite declarar las variables de este tipo. Se puede declarar la estructura sólo una vez dentro de un proyecto. El acceso a los elementos de las estructuras se realiza mediante operación punto (.).

Ejemplo:

struct trade_settings
  {
   double take;         // valor del precio de fijación del beneficio
   double stop;         // valor del precio del stop de protección
   uchar  slippage;     // valor del deslizamiento permitido
  };
//--- creamos e inicializamos la variable del tipo trade_settings
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

Modificador final

La existencia del modificador final al declarar la estructura, prohíbe la posterior herencia a partir de ella. Si la estructura es tal que no haya necesidad de introducir cambios posteriormente, o los cambios no están permitidos por motivos de seguridad, declárela con el modificador final. Además, todos los miembros de la estructura también se considerarán implícitamente como "final".

struct settings final
  {
  //--- cuerpo de la estructura
  };
 
struct trade_settings : public settings
  {
  //--- cuerpo de la estructura
  };

Al intentar heredar de una estructura con el modificador final, como se muestra en el ejemplo de más arriba, el compilador dará error:

cannot inherit from 'settings' as it has been declared as 'final'
see declaration of 'settings'

Clases

Las clases llevan una serie de diferencia de las estructuras:

  • en la declaración se utiliza la palabra clave class;
  • si no se indica lo contrario todos los elementos de las clase por defecto tienen el especificador de acceso private. Los elementos-datos de la estructura por defecto tienen el tipo de acceso public, si no se indica lo contrario;
  • los objetos de las clases siempre tienen una tabla de funciones virtuales, incluso si en la clase ninguna función virtual esté declarada. Las estructuras no pueden tener funciones virtuales;
  • para los objetos de la clase se puede aplicar el operador new, para las estructuras no se puede aplicar este operador;
  • las clases pueden ser heredadas unicamente de las clases, y las estructuras sólo de las estructuras.

Las clases y las estructuras pueden tener el constructor y destructor explícitos. En el caso, si el constructor está determinado de una manera explícita, la inicialización de variable del tipo de la estructura o clase con la ayuda de la sucesión inicializadora es imposible.

Ejemplo:

struct trade_settings
  {
   double take;         // valor del precio de fijación del beneficio
   double stop;         // valor del precio del stop de protección
   uchar  slippage;     // valor del deslizamiento permitido
   //--- constructor
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- destructor
         ~trade_settings() { Print("Es el final"); } 
  };
//--- compilador mostrará el error con el mensaje sobre la imposibilidad de inicialización
trade_settings my_set={0.0,0.0,5};  

Constructores y destructores

El constructor es una función especial que se llama automáticamente cuando se crea un objeto de estructura o clase, y normalmente se utiliza para la inicialización de los miembros de la clase. A continuación vamos a hablar sólo de las clases, pero todo lo dicho también se refiere a las estructuras, si no se especifica lo otro. El nombre del constructor debe coincidir con el de la clase. El constructor no tiene el tipo devuelto (se puede indicar el tipo void).

Algunos miembros definidos de la clase, tales como — cadenas, arrays dinámicos y objetos que requieren la inicialización — de cualquier manera serán inicializados, independientemente de la presencia del constructor.

Cada clase puede tener varios constructores que se diferencian por el número de parámetros y listas de inicialización. Un constructor que se requiere la especificación de parámetros se llama el constructor paramétrico.

Un constructor que no tiene parámetros se llama un constructor por defecto. Si en la clase no está declarado ningún constructor, entonces durante la compilación el compilador creará un constructor por defecto.

//+------------------------------------------------------------------+
//|  clase para trabajar con la fecha                                        |
//+------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // año
   int               m_month;         // mes
   int               m_day;           // día del mes
   int               m_hour;          // hora del día
   int               m_minute;        // minutos
   int               m_second;        // segundos
public:
   //--- constructor por defecto
                     MyDateClass(void);
   //--- constructor paramétrico
                     MyDateClass(int h,int m,int s);
  };

 

Se puede declarar el constructor en la descripción de la clase y luego definir su cuerpo. Por ejemplo, así se puede definir dos constructores de la clase MyDateClass:

//+------------------------------------------------------------------+
//| constructor por defecto                                         |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(void)
  {
//---
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=mdt.hour;
   m_minute=mdt.min;
   m_second=mdt.sec;
   Print(__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| constructor paramétrico                                        |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(int h,int m,int s)
  {
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=h;
   m_minute=m;
   m_second=s;
   Print(__FUNCTION__);
  }

En el constructor por defecto se llenan todos los miembros de la clase por medio de la función TimeCurrent(), en el constructor paramétrico se llenan sólo los valores de la hora. Los demás miembros de la clase (m_year, m_month y m_day) serán inicializados automáticamente con la fecha en curso.

El constructor por defecto tiene un propósito especial cuando se inicializa un array de objetos de su clase. El constructor cuyos parámetros tienen los valores por defecto, no es constructor por defecto. Ejemplificaremos esto:

//+------------------------------------------------------------------+
//|  clase con un constructor por defecto                              |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // hora de la última llamada al objeto
public:
   //--- un constructor con el parámetro que tiene el valor predefinido no es un constructor por defecto
                     CFoo(datetime t=0){m_call_time=t;};
   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   CFoo foo; // Esta opción se puede utilizar - se llamará a un constructor con parámetros predefinidos
//--- posibles opciones de creación del objeto CFoo
   CFoo foo1(TimeCurrent());     // la primera opción de creación automática del objeto
   CFoo foo2();                  // la segunda opción de creación automática del objeto
   CFoo foo3=TimeTradeServer();  // la tercera opción de creación automática del objeto
//--- posibles opciones de creación de punteros CFoo por medio del operador new
   CFoo *foo4=new CFoo();
   CFoo *foo5=new CFoo(TimeTradeServer());
   CFoo *foo6=GetPointer(foo5);  // ahora foo5 y foo6 apuntan al mismo objeto
   CFoo *foo7,*foo8;
   foo7=new CFoo(TimeCurrent());
   foo8=GetPointer(foo7);        // foo7 y foo8 apuntan al mismo objeto
   //CFoo foo_array[3];     // esta opción no se puede utilizar - el constructor por defecto no está establecido
   //CFoo foo_dyn_array[];  // esta opción no se puede utilizar - el constructor por defecto no está establecido
//--- mostramos los valores m_call_time
   Print("foo.m_call_time=",foo1.ToString());
   Print("foo1.m_call_time=",foo1.ToString());
   Print("foo2.m_call_time=",foo2.ToString());
   Print("foo3.m_call_time=",foo3.ToString());
   Print("foo4.m_call_time=",foo4.ToString());
   Print("foo5.m_call_time=",foo5.ToString());
   Print("foo6.m_call_time=",foo6.ToString());
   Print("foo7.m_call_time=",foo7.ToString());
   Print("foo8.m_call_time=",foo8.ToString());
//--- eliminaremos los objetos creados dinámicamente
   delete foo4;
   delete foo5;
   delete foo7;
  }

Si añadimos comentarios a estas cadenas en este ejemplo

  //CFoo foo_array[3];     // esta opción no se puede utilizar - el constructor por defecto no está establecido

o

  //CFoo foo_dyn_array[];  // esta opción no se puede utilizar - el constructor por defecto no está establecido

el compilador devolverá el error para ellas "default constructor is not defined".

Si la clase tiene un constructor declarado por el usuario, entonces el compilador no generará el constructor por defecto. Esto quiere decir que si en una clase está declarado un constructor paramétrico pero no está declarado un constructor por defecto, entonces no se puede declarar los arrays de los objetos de esta clase. Pues, para este script el compilador devolverá el error:

//+------------------------------------------------------------------+
//| clase sin constructor por defecto                              |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- durante la compilación obtenemos el error "default constructor is not defined"
   CFoo badFoo[5];
  }

En este ejemplo la clase CFoo tiene declarado un constructor paramétrico — en este caso durante la compilación el compilador no crea automáticamente el constructor por defecto. Al mismo tiempo, cuando se declara un array de objetos se supone que todos los objetos tienen que ser creados e inicializados automáticamente. Durante la inicialización automática del objeto, es necesario llamar a un constructor por defecto, pero debido a que el constructor por defecto no está declarado explicitamente y no ha sido generado automáticamente por el compilador, entonces resulta imposible crear este objeto. Precisamente por esta razón el compilador muestra el error aún en la fase de compilación.

Existe una sintaxis especial para la inicialización del objeto mediante el constructor. Los inicializadores del constructor (construcciones especiales para la inicialización) para los miembros de una estructura o clase se puede especificar en la lista de inicialización.

La lista de inicialización es una lista de inicializadores separados por comas que sigue tras dos puntos después de la lista de parámetros del constructor y precede el cuerpo (va antes de la llave que abre). Existen algunas exigencias:

  • las listas de inicialización se puede utilizar sólo en los constructores;
  • no se puede inicializar los miembros de los padres en la lista de inicialización;
  • tras las lista de inicialización debe ir la definición (implementación) de la función.

Vamos a mostrar algunos ejemplos de constructores para la inicialización de los miembros de la clase.

//+------------------------------------------------------------------+
//| clase para almacenar los apellidos y el nombre de una persona                     |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // nombre 
   string            m_second_name;    // apellido
public:
   //--- constructor por defecto vacío
                     CPerson() {Print(__FUNCTION__);};
   //--- constructor paramétrico
                     CPerson(string full_name);
   //--- constructor con la lista de inicialización
                     CPerson(string surname,string name): m_second_name(surname), m_first_name(name) {};
   void PrintName(){PrintFormat("Name=%s Surname=%s",m_first_name,m_second_name);};
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPerson::CPerson(string full_name)
  {
   int pos=StringFind(full_name," ");
   if(pos>=0)
     {
      m_first_name=StringSubstr(full_name,0,pos);
      m_second_name=StringSubstr(full_name,pos+1);
     }
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- obtenemos el error "default constructor is not defined"
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // Tom Sawyer
   CPerson Huck("Huckleberry","Finn");             // Huckleberry Finn
   CPerson *Pooh = new CPerson("Whinnie","Pooh");  // Winnie the Pooh
   //--- mostraremos los valores
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();
   
   //--- eliminaremos el objeto creado dinámicamente
   delete Pooh;
  }

En este caso, la clase CPerson tiene tres constructores:

  1. un constructor por defecto explícito que permite crear un array de los objetos de esta clase;
  2. un constructor con un parámetro que obtiene el nombre completo como parámetro, y lo divide en el nombre y el apellido según el espacio encontrado;
  3. un constructor con dos parámetros que contiene la lista de inicialización. Los inicializadores — m_second_name(surname) y m_first_name(name).

Fíjese cómo la inicialización reemplazó la asignación utilizando la lista. Los miembros individuales deben inicializarse como sigue:

 miembro_de_la_clase (lista de expresiones)

Los miembros pueden seguir cualquier orden en la lista de inicialización, pero todos los miembros de la clase van a inicializarse según el orden de su declaración. Esto significa que en el tercer constructor primero será inicializado el miembro m_first_name porque va declarado primero, y sólo después de él será inicializado el miembro m_second_name. Esto hay que tener en cuenta cuando la inicialización de unos miembros de la clase depende de los valores en otros miembros de la clase.

Si en la clase base no está declarado el constructor por defecto pero al mismo tiempo está declarado uno o varios constructores paramétricos, habrá que llamar sí o sí a uno de los constructores de la clase base en la lista de inicialización. Éste va tras la coma, como los demás miembros de la lista, y será llamado en primer lugar durante la inicialización del objeto independientemente de su ubicación en la lista de inicialización.

//+------------------------------------------------------------------+
//|  clase base                                                   |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- constructor con la lista de inicialización
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+------------------------------------------------------------------+
//|  descendiente de la clase CFoo                                             |
//+------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // el miembro de la clase es el objeto del padre
public:
   //--- el constructor por defecto en la lista de inicialización llama al constructor del padre
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
  }

En el ejemplo mencionado, durante la creación del objeto bar se llamará al constructor por defecto CBar() en el que primero se llama al constructor para el padre CFoo, y luego se llama al constructor para el miembro de la clase m_member.

Los destructores son unas funciones especiales llamadas automáticamente a la hora de eliminar un objeto de la clase. El nombre del destructor se escribe como el de la clase con la tilde (~). Las cadenas, arrays dinámicos y los objetos que necesitan deinicialización van a ser deinicializados de cualquier manera independientemente de la presencia del destructor. Disponiendo del destructor, estas acciones van a ser ejecutadas después del arranque del mismo.

Los destructores siempre son virtuales, independientemente de que si están declarados con la palabra clave virtual o no.

Determinación de los métodos de clase

Las funciones-métodos de clase pueden ser determinados tanto dentro de la clase, como fuera de la declaración de la clase. Si el método se determina dentro de la clase, entonces su cuerpo sigue directamente después de la declaración del método.

Ejemplo:

class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  }; 

Las funciones con SetRightBorder(int border) por Draw() se declaran y se determinan directamente dentro de la clase CTetrisShape.

El constructor CTetrisShape() y los métodos CheckDown(int& pad_array[]), CheckLeft(int& side_row[]) y CheckRight(int& side_row[]) se declaran sólo dentro de la clase, pero por ahora no están determinados. Las determinaciones de estas funciones deben seguir más adelante en el código. Para determinar el método fuera de la clase se utiliza la operación del permiso de contexto, como contexto se utiliza el nombre de la clase.

Ejemplo:

//+------------------------------------------------------------------+
//| Constructor de la clase base                                      |
//+------------------------------------------------------------------+
void CTetrisShape::CTetrisShape()
  {
   m_type=0;
   m_ypos=0;
   m_xpos=0;
   m_xsize=SHAPE_SIZE;
   m_ysize=SHAPE_SIZE;
   m_prev_turn=0;
   m_turn=0;
   m_right_border=0;
  }
//+------------------------------------------------------------------+
//| Prueba de posibilidad de moverse abajo (para la vara o el cubo)           |
//+------------------------------------------------------------------+
bool CTetrisShape::CheckDown(int& pad_array[])
  {
   int i,xsize=m_xsize/SHAPE_SIZE;
//---
   for(i=0; i<xsize; i++)
     {
      if(m_ypos+m_ysize>=pad_array[i]) return(false);
     }
//---
   return(true);
  }

 
Modificadores de acceso public, protected y private

A la hora de crear nueva clase se recomienda limitar el acceso a los elementos desde fuera. Para eso se utilizan las palabras claves private o protected. En este caso el acceso a los datos encubiertos puede realizarse sólo desde las funciones-métodos de la misma clase. Si se utiliza la palabra clave protected, entonces el acceso a los datos encubiertos se puede realizar también de los métodos de las clases que son herederos de esta clase. De la misma manera se puede limitar el acceso a las funciones-métodos de clase.

Si se necesita abrir totalmente el acceso a los elementos y/o métodos de clase, entonces se utiliza la palabra clave public.

Ejemplo:

class CTetrisField
  {
private:
   int               m_score;                            // cuenta
   int               m_ypos;                             // posición actual de la pieza
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // matrix del vaso
   int               m_rows[FIELD_HEIGHT];               // numeración de las filas del vaso 
   int               m_last_row;                         // la última fila libre
   CTetrisShape     *m_shape;                            // pieza del tetris
   bool              m_bover;                            // fin del juego
public:
   void              CTetrisField() { m_shape=NULL; m_bover=false; }
   void              Init();
   void              Deinit();
   void              Down();
   void              Left();
   void              Right();
   void              Rotate();
   void              Drop();
private:
   void              NewShape();
   void              CheckAndDeleteRows();
   void              LabelOver();
  }; 

Cualquier elemento y método de la clase que están declarados después del especificador public (y hasta el siguiente especificador del acceso), son accesibles a la hora de cualquier referencia del programa hacia el objeto de esta clase. En este ejemplo son los siguientes elementos: funciones CTetrisField(), Init(),  Deinit(), Down(), Left(), Right(), Rotate() y Drop().

Cualquier elemento de la clase que está declarado después del especificador private (y hasta el siguiente especificador del acceso), es accesible sólo para las funciones-elementos de la misma clase. Los especificadores de acceso a los elementos siempre se terminan con dos puntos (:) y pueden aparecer en la determinación de la clase varias veces.

El acceso a los elementos de la clase base puede volver a determinarse durante la herencia en las clases derivadas.

Modificador final

La presencia del modificador final al declarar una clase, prohíbe la posterior herencia a partir de ella. Si la interfaz de la clase es tal que no haya necesidad de introducir cambios posteriormente, o los cambios no están permitidos por motivos de seguridad, declare la clase con el modificador final. Además, todos los métodos de la clase se también considerarán implícitamente como "final".

class CFoo final
  {
  //--- cuerpo de la clase
  };
 
class CBar : public CFoo
  {
  //--- cuerpo de la clase
  };

Al intentar heredar de una clase con el modificador final, como se muestra en el ejemplo de más arriba, el compilador dará error:

cannot inherit from 'CFoo' as it has been declared as 'final'
see declaration of 'CFoo'

Uniónes (union)

Una unión supone un tipo especial de datos que consta de varias variables que comparten una misma zona de la memoria.  Por consiguiente, la unión proporciona la posibilidad de interpretar una misma secuencia de bits con dos (o más) métodos diferentes. La declaración de una unión es semejante a la declaración de una estructura y comienza con la palabra clave union.

union LongDouble
{
  long   long_value;
  double double_value;
};

Pero, a diferencia de la estructura, los diferentes miembros de una unión se relacionan con una misma zona de la memoria. En este ejemplo se ha declarado la unión LongDouble, en la que el valor del tipo long y el valor del tipo double comparten la misma zona de la memoria. Es importante comprender que  no es posible hacer que la unión guarde al mismo tiempo valores de tipo entero long y reales double (como sucedía en la estructura), puesto que las variables long_value y double_value se solapan (en la memoria) una sobre otra. Sin embargo, un programa MQL5 puede en cualquier momento procesar la información que se contiene en esta unión como un valor entero (long) o como uno real (double).  Por consiguiente, la unión permite obtener dos (o más) variantes de representación de una misma secuencia de datos.

Al declarar una unión, el compilador delimita automáticamente una parte de la memoria que sea suficiente para guardar en una unión las variables del tipo de volumen más grande. Para acceder a un elemento de la unión, se usa la misma sintaxis que para las estructuras: el operador "punto".

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- obtenemos un número no válido -nan(ind) y lo mostramos
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- el mayor número normalizado (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- el menor número normalizado positivo (DBL_MIN)
   lb.long_value=0x0010000000000000;    
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/*  Resultado de la ejecución
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

Puesto que las uniones permiten al programa interpretar los mismos datos en la memoria de forma diferente, con frecuencia se usan en los casos en que se necesita una conversión de tipos.

Las uniones no pueden participar en la herencia, y tampoco pueden tener miembros estáticos por definición. Por lo demás, union se comporta como una estructura en la que todos los miembros tienen un desplazamiento cero. Al mismo tiempo, no pueden ser miembros de una unión los siguientes tipos:

Al igual que las clases, una unión puede tener constructores y destructores, y también los métodos. Por defecto, los miembros de una unión tienen un tipo de acceso public, para crear elementos cerrados, es necesario usar la palabra clave private. Todas estas posibilidades se muestran en este ejemplo, que representa cómo convertir un color con el tipo color en una representación ARGB, como hace la función ColorToARGB().

//+------------------------------------------------------------------+
//| Unión para la conversión de color(BGR) en una representación ARGB      |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- constructores
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- métodos públicos
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- métodos privados
private:
   //+------------------------------------------------------------------+
   //| configuración del color y el valor del canal alfa                          |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- establecemos el color en el miembro clr
      clr=col;
      //--- establecemos el valor del componente Alpha - nivel de opacidad
      argb[3]=alpha;
      //--- cambiamos de lugar los bytes de los componentes R y B (Red y Blue)     
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- el valor 0x55 designa 55/255=21.6 % (0% - totalmente transparente)
   uchar alpha=0x55; 
//--- el tipo "color" tiene una representación 0x00BBGGRR
   color test_color=clrDarkOrange;
//--- aquí aplicaremos los valores de los bytes de la unión ARGB
   uchar argb[]; 
   PrintFormat("0x%.8X - este es el aspecto del tipo "color" para %s, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- el tipo ARGB se representa como 0x00RRGGBB, los componentes RR y BB han sido cambiados de lugar
   ARGB argb_color(test_color);
//--- copiamos la matriz de bytes
   ArrayCopy(argb,argb_color.argb);
//--- veamos qué aspecto tiene en la representación ARGB  
   PrintFormat("0x%.8X - representación ARGB con un canal alfa=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- añadimos el valor de opacidad
   argb_color.Alpha(alpha);
//--- intentamos mostrar ARGB como el tipo "color"
   Print("ARGB como color=(",argb_color.clr,")  canal alfa=",argb_color.Alpha());
//--- copiamos la matriz de bytes
   ArrayCopy(argb,argb_color.argb);
//--- este es el aspecto que tiene en la representación ARGB
   PrintFormat("0x%.8X - representación ARGB con un canal alfa=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- lo cotejamos con lo mostrado por la función ColorToARGB()
   PrintFormat("0x%.8X - resultado ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/* Resultado de la ejecución
   0x00008CFF - este es el aspecto del tipo color para clrDarkOrange, BGR=(255,140,0)
   0x00FF8C00 - representación ARGB con un canal alfa=0x00, ARGB=(0,255,140,0)
   ARGB como color=(0,140,255)  canal alfa=85
   0x55FF8C00 - representación ARGB con un canal alfa=0x55, ARGB=(85,255,140,0)
   0x55FF8C00 - resultado ColorToARGB(clrDarkOrange,0x55)
*/ 

Interfaces

La interfaz ha sido diseñada para definir una cierta funcionalidad, cuya clase en consecuencia puede implementar. En la práctica, se trata de una clase que no puede contener miembros y puede tener un constructor y/o destructor. Todos los métodos declarados en la interfaz son puramente virtuales, incluso sin definición explícita.

Se define la interfaz con la ayuda de la palabra clave interface, como se muestra en el ejemplo:

//--- interfaz básica para describir animales
interface IAnimal
  {
//--- los métodos de la interfaz, por defecto, tienen acceso public
   void Sound();  // sonido que hace el animal
  };
//+------------------------------------------------------------------+
//|  la clase CCat se hereda de la interfaz IAnimal                    |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("Cat was born"); }
                    ~CCat() { Print("Cat is dead");  }
   //--- implementamos el método Sound de la interfaz IAnimal
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  la clase CDog se hereda de la interfaz IAnimal                    |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("Dog was born"); }
                    ~CDog() { Print("Dog is dead");  }
   //--- implementamos el método Sound de la interfaz IAnimal
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- matriz de punteros a los objetos del tipo IAnimal
   IAnimal *animals[2];
//--- generamos los descendientes de IAnimal y guardamos los punteros a estos en una matriz    
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- llamamos el método Sound() de la interfaz básica IAnimal para cada descendiente  
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- eliminamos los objetos
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- resultado de la ejecución
/*
   Cat was born
   Dog was born
   meou
   guaf
   Cat is dead
   Dog is dead
*/
  }

Como sucede con las clases abstractas, no se puede crear un objeto de la interfaz sin herencia. La interfaz puede heredarse solo de otras interfaces y puede actuar como descendiente para la clase. Además, siempre tiene visibilidad pública.

La interfaz no se puede declarar dentro de la declaración de una clase o estructura, pero así y con todo, el puntero a la interfaz se puede guardar en una variable del tipo void *. Hablando en general, en una variable del tipo void * se puede guardar un puntero a un objeto de cualquier clase. Para transformar el puntero void * en el puntero a un objeto de una clase concreta, es necesario usar el operador dynamic_cast.  En el caso de que la transformación no sea posible, el resultado de la operación dynamic_cast será NULL.

Véase también

Programación orientada a objetos