Descargar MetaTrader 5

Usando los punteros de objeto en MQL5

16 diciembre 2013, 11:21
MetaQuotes Software Corp.
0
473

Introducción

En MQL5 puede crear su propia clase para un uso posterior de variables de tipo clase en su código. Como ya vimos en el artículo El orden de la creación y destrucción de un objeto en MQL5, las estructuras y clases pueden crearse de dos formas: de forma automática y de forma dinámica.

Para crear un objeto automáticamente, simplemente declare una variable de tipo clase y el sistema la creará y la inicializará automáticamente. Para crear un objeto dinámicamente es necesario aplicar el operador new  al puntero del objeto explícitamente.

Sin embargo, ¿cuál es la diferencia entre los objetos creados automáticamente y los creados dinámicamente? y ¿cuándo necesitamos usar el puntero de objeto? y ¿cuándo es suficiente crear los objetos automáticamente? Estas cuestiones son tratadas en este artículo. En primer lugar, vamos a ver algunos de los posibles problemas con los que nos podemos encontrar al trabajar con objetos y vamos a ver cómo solucionarlos.

Un error crítico en el acceso a un puntero inválido

Lo primero que debe recordar al usar punteros de objeto es que es obligatorio inicializar el objeto antes de usarlo. Cuando accedemos a un puntero inválido, el funcionamiento de un programa MQL termina con un error crítico, y por tanto el programa es eliminado. Como ejemplo, vamos a considerar un asesor experto simple con la clase CHello declarada en él. El puntero hacia la instancia de la clase es declarado a nivel global.

//+------------------------------------------------------------------+
//|                                             GetCriticalError.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Una clase simple                                                 |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Función de inicialización del Experto                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- llamando a un método para mostrar un estado
   Print(pstatus.GetMessage());
//--- imprimiendo un mensaje si el Expert Advisor se ha inicializado con éxito
   Print(__FUNCTION__," La función OnInit() se ha completado");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Función tick del Experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

La variable de estado es el puntero del objeto, pero hemos "olvidado" intencionadamente crear el propio objeto usando el operador new. Si intenta ejecutar este asesor experto en el gráfico EURUSD, verá el resultado lógico: el asesor experto ha sido inmediatamente descargado en la etapa de ejecución de la función OnInit(). Los mensajes que aparecen en el diario de expertos son:

14:46:17 Expert GetCriticalError (EURUSD, H1) loaded successfully
14:46:18 Initializing of GetCriticalError (EURUSD, H1) failed
14:46:18 Expert GetCriticalError (EURUSD, H1) removed

Este ejemplo es muy simple, la captura del error es fácil. Sin embargo, si su programa MQL5 contiene cientos o incluso miles de líneas de código, capturar estos errores puede llegar a ser complicado. Esto es importante (especialmente en casos en los que se dan condiciones de emergencia en el comportamiento del programa que dependen de factores impredecibles) para la estructura particular del mercado.

Comprobación del puntero antes de usarlo

¿Fue posible evitar la finalización crítica del programa? Sí, ¡por supuesto! Es suficiente insertar la verificación del puntero del objeto antes de usarlo. Vamos a modificar este ejemplo añadiendo la función PrintStatus:

//+------------------------------------------------------------------+
//| Imprime un mensaje usando un método del objeto tipo CHello       |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(pobject.GetMessage());
  }

Ahora, esta función llama al método GetMessage() y el puntero del objeto de tipo CHelo es pasado a la función. Primero verifica el puntero usando la función CheckPointer(). Vamos a añadir un parámetro externo y guardar el código de un asesor experto en el archivo GetCriticalError_OnDemand.mq5.

//+------------------------------------------------------------------+
//|                                    GetCriticalError_OnDemand.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
input bool GetStop=false;// Para obtener un error crítico
//+------------------------------------------------------------------+
//| Una clase simple                                                 |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Imprime un mensaje usando un método del objeto de tipo CHelo     |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," la variable "objetct" ¡no ha sido inicializada!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Función de inicialización del experto                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- llamando a un método para mostrar el estado
   if(GetStop)
      pstatus.GetMessage();
   else
      PrintStatus(pstatus);
//--- imprimiendo un mensaje si el Expert Advisor se ha inicializado con éxito
   Print(__FUNCTION__," La función OnInit() se ha completado");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Función tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Ahora podemos lanzar el asesor experto de dos formas:

  1. Con un error crítico (GetStop = true)
  2. Sin error, pero con el mensaje sobre el puntero inválido (GetStop = false)

Por defecto, la ejecución del asesor experto tiene éxito y aparece el siguiente mensaje en el diario de "expertos":

GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 PrintStatus the variable 'object' isn't initialized!
GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 OnInit function OnInit () is completed

De esta forma, la verificación del puntero antes de su uso permite evitar errores críticos.

Verifique siempre que el puntero es correcto antes de su uso en la función

Pasando el objeto no inicializado por referencia

¿Qué ocurre si pasamos el objeto no inicializado como parámetro de entrada de la función? (el propio objeto por referencia, no el puntero del objeto). Los objetos complejos, como clases y estructuras, son pasados por referencia con un ampersand (&). Vamos a escribir el código del archivo GetCriticalError_OnDemand.mq5. Vamos a renombrar la función PrintStatus() y a reescribir su código de otra forma.

//+------------------------------------------------------------------+
//| Imprime un mensaje usando un método del objeto de tipo CHelo     |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," la variable "objeto" ¡no se ha inicializado!");
   else Print(object.GetMessage());
  }

Ahora, la diferencia es que la propia variable de este tipo es pasada por referencia como parámetro de entrada en lugar del puntero del objeto del tipo CClassHello. Vamos a guardar la nueva versión del asesor experto como GetCriticalError_Unsafe.mq5.

//+------------------------------------------------------------------+
//|                                      GetCriticalError_Unsafe.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
input bool GetStop=false;// Para obtener un error crítico
//+------------------------------------------------------------------+
//| Una clase simple                                                 |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Imprime un mensaje usando un método del objeto tipo CHello       |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," the variable 'object' isn't initialized!");
   else Print(object.GetMessage());
  }
//+------------------------------------------------------------------+
//| Función de inicialización del experto                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- llamando a un método para mostrar el estado
   if(GetStop)
      pstatus.GetMessage();
   else
      UnsafePrintStatus(pstatus);
//--- imprimiendo un mensaje si el Expert Advisor se inicializado con éxito
   Print(__FUNCTION__," La función OnInit() se ha completado");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Función tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Podemos ver la diferencia entre los asesores expertos GetCriticalError_OnDemand.mq5 y GetCriticalError_Unsafe.mq5 por la forma en que los parámetros se pasan a la función. En el primer caso, el puntero del objeto se pasa a la función y, en el segundo, el propio objeto se pasa por referencia. En ambos casos, antes de usar el objeto y su puntero, la función comprueba que este último sea correcto.

¿Significa esto que los asesores expertos funcionarán del mismo modo? No, ¡en absoluto! Vamos a ejecutar el asesor experto con el parámetro GetStop=false, y obtendremos de nuevo un error crítico. El hecho es que si un objeto se pasa por referencia, el error crítico ocurre en la etapa de la llamada de una función ya que el objeto no inicializado se pasa como parámetro. Puede comprobarlo si ejecuta el script en modo depuración directamente desde MetaEditor usando la tecla F5.

Para evitar el uso de puntos de interrupción, vamos a modificar la función añadiendo el punto de interrupción DebugBreak () a la misma.

//+------------------------------------------------------------------+
//| Imprime un mensaje usando un método del objeto de tipo CHelo     |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," la variable "objeto" ¡no se ha inicializado!");
   else Print(object.GetMessage());
  }

En modo depuración, el asesor experto será descargado antes de una llamada de la función DebugBreak(). ¿Cómo escribir código seguro en caso de que un objeto no inicializado se pase por referencia? La respuesta es simple, usando la función de sobrecarga (overloading).

Si un puntero de un objeto no inicializado se pasa por referencia como parámetro de la función, producirá un error crítico y parará el programa mql5.

Usando las funciones de sobrecarga para un código seguro

Habría sido muy inadecuado si el programador que usa la librería externa hubiera forzado la comprobación de los objetos de entrada para evaluar si son correctos. Es mucho mejor realizar todas las comprobaciones necesarias dentro de la librería, algo que puede implementarse usando la sobrecarga de la función.

Vamos a implementar el método UnsafePrintStatus() con la sobrecarga de la función y escribir dos versiones de esta. La primera, para usar el puntero de objeto pasado en lugar del objeto mismo y, la segunda, para pasar el objeto por referencia. Ambas funciones tienen el mismo nombre "PrintStatus" pero esta implementación ya no será potencialmente peligrosa.

//+------------------------------------------------------------------+
//| La impresión segura de un mensaje usando                         |
//| el puntero de objeto CHello                                      |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," la variable 'objecto' ¡no se ha inicializado!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Imprimiendo un mensaje usando el objeto CHello,                  |
//| pasado por referencia                                            |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }

Ahora, cuando la función es llamada pasando el puntero de objeto, se realiza la comprobación de si es correcto y el error crítico no ocurre. Si llamamos a la función sobrecargada pasando un objeto por referencia, primero obtendremos el puntero de objeto usando la función GetPointer y luego llamaremos al código seguro que pasa el objeto a través del puntero.

Podemos reescribir una versión segura de la función aun más corta, en lugar de

void SafePrintStatus (CHello & pobject)
  (
   DebugBreak ();
   CHello * p = GetPointer (pobject);
   SafePrintStatus (p);
  )

vamos a escribir la versión compacta:

void SafePrintStatus (CHello & object)
  (
   DebugBreak ();
   SafePrintStatus (GetPointer (object));
  )

Ambas versiones son iguales. Vamos a salvar la segunda versión con el nombre GetCriticalError_Safe.mq5.

//+------------------------------------------------------------------+
//|                                      GetCriticalError_Unsafe.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
input bool GetStop=false;// Para obtener un error crítico
//+------------------------------------------------------------------+
//| Una clase simple                                                 |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Starting...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| La impresión segura de un mensaje usando                         |
//| puntero de objeto de Hello                                       |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," la variable 'object' ¡no se ha inicializado!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Imprimiendo un mensaje usando                                    |
//| el objeto CHello pasado por referencia                           |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }
//+------------------------------------------------------------------+
//| Función de inicialización del experto                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- llamando a un método para mostrar el estado
   if(GetStop)
      pstatus.GetMessage();
   else
      SafePrintStatus(pstatus);
//--- imprimiendo un mensaje si el Expert Advisor se ha inicializado con éxito
   Print(__FUNCTION__,"La función OnInit() se ha completado");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Función tick del Experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Si usamos dos funciones con diferente implementación (pasando la referencia y pasando el puntero del objeto) podremos garantizar un funcionamiento seguro de la función sobrecargada.

Finalmente, hemos aprendido cómo usar los objetos pasados a las funciones como parámetros. Es el momento de aprender:

¿Cuándo necesitamos punteros?

Los punteros de objeto permiten realizar una gestión flexible del proceso de creación y destrucción de objetos y nos permite crear objetos más complejos y abstractos. Hace que el programa sea más flexible.

Lista vinculada

Pero, en algunos casos, se requiere una forma diferente de organización de los datos, y la lista vinculada es uno de ellos. La clase de una lista vinculada CList se encuentra disponible en la librería estándar. Aquí, vamos a presentar nuestros propios ejemplos. Una lista vinculada significa que cada elemento de la lista está conectado con el siguiente y con el anterior, si existen. Para organizar estos vínculos es aconsejable usar los punteros de objeto de los elementos de la lista (LisItem).

Vamos a crear una clase que representará un elemento de una lista, como la siguiente:

class CListItem
  {
private:
   int               m_ID;
   CListItem        *m_next;
   CListItem        *m_prev;
public:
                    ~CListItem();
   void              setID(int id){m_ID=id;}
   int               getID(){return(m_ID);}
   void              next(CListItem *item){m_next=item;}
   void              prev(CListItem *item){m_prev=item;}
   CListItem*        next(){return(m_next);}
   CListItem*        prev(){return(m_prev);}
  };

La lista misma se organizará en clases separadas:

//+------------------------------------------------------------------+
//| Lista vinculada                                                  |
//+------------------------------------------------------------------+
class CList
  {
private:
   int               m_counter;
   CListItem        *m_first;
public:
                     CList(){m_counter=0;}
                    ~CList();
   void              addItem(CListItem *item);
   int               size(){return(m_counter);
  };

La clase Clist contiene un puntero m_first del primer elemento de la lista. Siempre hay disponible un acceso a los demás elementos de la lista a través de las funciones next () y prev () de la clase CListItem (). La clase CList tiene dos interesantes funciones. La primera es la función para añadir un nuevo elemento a la lista.

//+------------------------------------------------------------------+
//| Añadir un elemento a la lista                                    |
//+------------------------------------------------------------------+
CList::addItem(CListItem *item)
  {
//--- comprobando un puntero, debe ser correcto
   if(CheckPointer(item)==POINTER_INVALID) return;
//--- aumentando el número de los elementos de la lista
   m_counter++;
//--- si no hay ningún elemento en la lista
   if(CheckPointer(m_first)!=POINTER_DYNAMIC)
     {
      m_first=item;
     }
   else
     {
      //--- estableciendo para el primero un puntero hacia el elemento previo
      m_first.prev(item);
      //--- guardando un puntero del primer elemento actual
      CListItem *p=m_first;
      //--- ubicando el elemento de entrada en el lugar del primer elemento
      m_first=item;
      //--- para el primer elemento de la lista, estableciendo un puntero hacia el elemento siguiente 
      m_first.next(p);
     }
  }

Cada elemento añadido a la lista se convierte en el primer elemento y el puntero del primer elemento previo se guarda en el campo m_next. De esta forma, un elemento añadido a la lista el primero, estará al final de la lista. El último elemento añadido será el primero y su puntero se guarda en la variable m_first. La segunda función interesante es el destructor ~CList(). Realiza la llamada cuando el objeto es destruido y debe asegurar la correcta destrucción de los objetos de la lista. Puede hacerse de forma muy simple:

//+------------------------------------------------------------------+
//| Destructor de la lista                                           |
//+------------------------------------------------------------------+
CList::~CList(void)
  {
   int ID=m_first.getID();
   if(CheckPointer(m_first)==POINTER_DYNAMIC) delete(m_first);
   Print(__FUNCTION__," EL primer elemento con ID =",ID," es destruido");
  }

Podemos ver que si m_first contiene el puntero correcto del elemento de la lista, solo se elimina el primer elemento de la lista. Todos los demás elementos son eliminados ya que el destructor de la clase CListItem(), a su vez, produce la deinicialización correcta del objeto.

//+------------------------------------------------------------------+
//| Destructor del elemento                                          |
//+------------------------------------------------------------------+
CListItem::~CListItem(void)
  {
   if(CheckPointer(m_next)==POINTER_DYNAMIC)
     {
      delete(m_next);
      Print(__FUNCTION__," Eliminando un elemento con ID =",m_ID);
     }
   else
      Print(__FUNCTION__," El siguiente elemento no está definido para el elemento con ID=",m_ID);
  }

La corrección del puntero se comprueba dentro del destructor, si se especifica, y el objeto con el puntero m_next se destruye usando el operador delete (). Antes de la destrucción, el primer elemento de la lista llama al destructor, borrará el segundo elemento y luego provocará el borrado del tercer elemento y así sucesivamente hasta el final de la cadena.

El funcionamiento de la lista se encuentra más abajo en el script SampleList.mq5. La declaración de una lista (una variable de CListType) OnStart () está en la función. La lista se creará e inicializará automáticamente. El llenado de la lista se realiza dentro de una lista y, primero, cada elemento de la lista se crea dinámicamente usando el operador nuevo y luego se añade a la lista. 

void OnStart()
  {
//---
   CList list;
   for(int i=0;i<7;i++)
     {
      CListItem *item=new CListItem;
      item.setID(i);
      list.addItem(item);
     }
     Print("There are ",list.size()," items in the list");
  }

Ejecute el script y verá los siguientes mensajes en el diario de los expertos.

2010.03.18 11:22:05 SampleList (EURUSD, H1) CList:: ~ CList The first item with ID=6 is destroyed
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removing an item with ID = 6
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removing an item with ID = 5
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removing an item with ID = 4
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removing an item with ID = 3
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removing an item with ID = 2
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removing an item with ID = 1
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem El siguiente elemento no está definido para el elemento con ID=0
2010.03.18 11:22:05 SampleList (EURUSD, H1) Hay 7 elementos en la lista

Si ha estudiado el código detenidamente, no será una sorpresa comprobar que el elemento con ID=0 no tiene al siguiente elemento. Sin embargo, hemos considerado solo una razón por la que el uso de punteros hace que la escritura de programas sea cómoda y de fácil lectura. Hay una segunda razón y se llama.

Polimorfismo

A menudo es necesario implementar la misma funcionalidad para distintos objetos del mismo tipo. Por ejemplo, hay objetos simples como Línea, Triángulo, Rectángulo y Círculo. A pesar del hecho de que parecen distintos, tienen una característica común: pueden ser dibujados. Vamos a crear una clase básica CShape y usarla para crear sus descendientes para cada tipo de forma geométrica.


Por razones educativas la clase básica y sus descendientes tienen una funcionalidad mínima.

//+------------------------------------------------------------------+
//| La clase básica para un objeto Shape                             |
//+------------------------------------------------------------------+
class CShape
  {
private:
   int               m_type;
public:
                     CShape(){m_type=0;}
   void              Draw();
   string            getTypeName(){return("Shape");}
  };
//+------------------------------------------------------------------+
//| La clase para un objeto Line                                     |
//+------------------------------------------------------------------+
class CLine:public CShape
  {
private:
   int               m_type;
public:
                     CLine(){m_type=1;}
   void              Draw();
   string            getTypeName(){return("Line");}
  };
//+------------------------------------------------------------------+
//| La clase para un objeto triángulo                                |
//+------------------------------------------------------------------+
class CTriangle:public CShape
  {
private:
   int               m_type;
public:
                     CTriangle(){m_type=2;}
   void              Draw();
   string            getTypeName(){return("Triangle");}
  };
//+------------------------------------------------------------------+
//| La clase para un objeto rectángulo                               |
//+------------------------------------------------------------------+
class CRectangle:public CShape
  {
private:
   int               m_type;
public:
                     CRectangle(){m_type=3;}
   void              Draw();
   string            getTypeName(){return("Rectangle");}
  };
//+------------------------------------------------------------------+
//| La clase para un objeto círculo                                  |
//+------------------------------------------------------------------+
class CCircle:public CShape
  {
private:
   int               m_type;
public:
                     CCircle(){m_type=4;}
   void              Draw();
   string            getTypeName(){return("Circle");}
  };

La clase matriz CShape contiene dos funciones, que son pasadas por alto en sus descendientes - Draw() y getTypeName(). La función Draw() dibuja una forma y la función getTypeName() devuelve la descripción de un script de la forma.

Vamos a crear una matriz de shapes[] que contiene punteros del tipo base CShape y especifica los valores de un puntero para distintas clases.

//+------------------------------------------------------------------+
//| Función de inicio del programa script                            |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- una matriz de punteros de objetos del tipo CShape 
   CShape *shapes[];
//--- redimensionado de una matriz 
   ArrayResize(shapes,5);
//--- rellenado de una matriz de punteros
   shapes[0]=new CShape;
   shapes[1]=new CLine;
   shapes[2]=new CTriangle;
   shapes[3]=new CRectangle;
   shapes[4]=new CCircle;
//--- imprimiendo el tipo de cada elemento de la matriz
   for(int i=0;i<5;i++)
     {
      Print(i,shapes[i].getTypeName());
     }
//--- borrando todos los objetos de la matriz
   for(int i=0;i<5;i++) delete(shapes[i]);
  }

Dentro del ciclo for() llamamos al método getTypeName () para cada elemento de la matriz *shapes[] . La primera vez puede sorprenderle el hecho de que la función getTypeName() de una clase básica llame a cada elemento de la lista, a pesar del hecho de que cada clase derivada tiene su propia implementación de la función getTypeName().

2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 4 Shape
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 3 Shape
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 2 Shape
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 1 Shape
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 0 Shape

La explicación de este hecho es la siguiente: la matriz *shapes []  es declarada como una matiz de punteros del tipo CShape y por tanto cada objeto de la matriz llama al método getTypeName() de una clase base, incluso si una descendiente tiene una implementación distinta. Para llamar a la función getTypeName() correspondiente al tipo de objeto actual (descendiente) en el momento de la ejecución del programa, es necesario declarar esta función en una clase base como función virtual.

Vamos a añadir la palabra clave virtual a la función getTypeName() en la declaración de una clase CShape matriz.

class CShape
  (
private:
   int m_type;
public:
                     CShape () (m_type = 0;)
   void Draw ();
   virtual string getTypeName () (return ("Shape");)
  )

y ejecutar de nuevo el script. Ahora los resultados son tal y como se esperaban:

2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 4 Circle
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 3 Rectangle
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 2 Triangle
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 1 Line
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 0 Shape

Por tanto, la declaración de una función virtual en una clase ha permitido llamar a la misma función de una descendiente durante la ejecución del programa. Ahora podemos implementar la función Draw () con todas sus opciones para cada una de las clases derivadas.

Puede comprobarse un ejemplo de su funcionamiento en el script adjunto DrawManyObjects.mq5, que muestra formas aleatorias en el gráfico.

Conclusión

Es momento de hacer un resumen. En MQL5, la creación y destrucción de objetos se realiza automáticamente, por lo que debe usar los punteros solo si son realmente necesarios y si comprende cómo funcionan.

Sin embargo, si no puede hacer esto sin usar los punteros, asegúrese de comprobar que el puntero es correcto antes de usarlo a través de CheckPointer(). Se ha añadido especialmente para estos casos.

Una última cosa: en MQL5 los punteros no son realmente punteros de memoria, tal y como se usan en C++, por lo que no debe pasarlos a DLL como parámetros de entrada.

Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/36

Introducción a MQL5: Cómo escribir un Expert Advisor y un Indicador Personalizado Introducción a MQL5: Cómo escribir un Expert Advisor y un Indicador Personalizado

MetaQuotes Programming Language 5 (MQL5), incluido en el terminal del cliente de MetaTrader 5, tiene muchas nuevas posibilidades y un mayor rendimiento, en comparación con MQL4. Este artículo le ayudará a familiarizarse con este nuevo lenguaje de programación. En este artículo se encuentran los sencillos ejemplos de cómo escribir un Expert Advisor y un Indicador Personalizado. También tendremos en cuenta algunos detalles del lenguaje MQL5, que son necesarios para entender estos ejemplos.

MQL5 para Principiantes: Guía para el Uso de Indicadores Técnicos en Asesores Expertos MQL5 para Principiantes: Guía para el Uso de Indicadores Técnicos en Asesores Expertos

Para obtener valores en un indicador incorporado o personalizado en un Asesor Experto, en primer lugar, su identificador se debe crear usando la función correspondiente. Los ejemplos de este artículo muestran cómo usar un indicador técnico mientras crea sus propios programas. El artículo describe indicadores creados con el lenguaje MQL5. Está pensado para aquellos que no tienen mucha experiencia en el desarrollo de estrategias de trading, y ofrece formas sencillas y claras de trabajar con indicadores usando la biblioteca de funciones facilitada.

Indicadores personalizados para principiantes en MQL5 Indicadores personalizados para principiantes en MQL5

Cualquier materia parece complicada y difícil de aprender para un principiante. Materias que ahora nos parecen muy simples y claras. Pero no olvidemos que todos tenemos que aprender desde cero, incluso nuestro propio idioma. Lo mismo ocurre con el lenguaje de programación MQL5 que ofrece grandes posibilidades para desarrollar nuestras propias estrategias de trading. Podemos empezar a aprenderlo comenzando con nociones más básicas y los ejemplos más sencillos. En este artículo vamos a considerar la interacción de un indicador técnico con el terminal de cliente con un ejemplo de indicador personalizado SMA.

Procesando los eventos de transacciones en el Expert Advisor por medio de la función OnTrade() Procesando los eventos de transacciones en el Expert Advisor por medio de la función OnTrade()

MQL5 introdujo infinidad de soluciones innovadoras, incluyendo el trabajo con distintos tipos de eventos (eventos de reloj, eventos sobre transacciones, eventos personalizados, etc.). La capacidad para gestionar eventos permite crear un tipo completamente nuevo de programas para el trading automático o semi-automático. En este artículo vamos a ver los eventos de transacciones y a escribir código para la función OnTrade(), encargada de procesar el evento Trade.