- Fundamentos de la programación orientada a objetos: Abstracción
- Fundamentos de la programación orientada a objetos: Encapsulación
- Fundamentos de la programación orientada a objetos: Herencia
- Fundamentos de la programación orientada a objetos: Polimorfismo
- Fundamentos de la programación orientada a objetos: Composición (diseño)
- Definición de clases
- Derechos de acceso
- Constructores: por defecto, paramétricos y de copia
- Destructores
- Autorreferencia: esto
- Herencia
- Creación dinámica de objetos: nuevo y suprimir
- Punteros
- Métodos virtuales (virtual y override)
- Miembros estáticos
- Tipos anidados, espacios de nombres y operador de contexto '::'
- Dividir definición y declaración de clase
- Clases abstractas e interfaces
- Sobrecarga de operadores
- Conversión de tipos de objeto: dynamic_cast y puntero void *
- Punteros, referencias y const
- Gestión de la herencia: final y delete
Punteros
Como dijimos en la sección Definición de clases, los punteros en MQL5 son ciertos descriptores (números únicos) de objetos, y no direcciones en la memoria, como en C++. Para un objeto automático, obtenemos un puntero anteponiendo un ampersand delante de su nombre (en este contexto, el carácter ampersand es el operador «obtener dirección»). Así, en el siguiente ejemplo, la variable p apunta al objeto automático s.
Shape s; // automatic object
|
En las secciones anteriores aprendimos a obtener un puntero a un objeto como resultado de crearlo dinámicamente con new. En este momento no se necesita un ampersand para obtener un descriptor: el valor del puntero es el descriptor.
La API de MQL5 proporciona la función GetPointer que realiza la misma acción que el operador ampersand '&', es decir, devuelve un puntero a un objeto:
void *GetPointer(Class object); |
Utilizar una u otra opción es cuestión de preferencia.
Los punteros se utilizan a menudo para enlazar objetos. Vamos a ilustrar la idea de crear objetos subordinados que reciben un puntero a this de su creador de objetos (ThisCallback.mq5). Ya mencionamos este truco en la sección sobre la palabra clave this.
Vamos a intentar usarlo para implementar un esquema para notificar al «creador» de vez en cuando el porcentaje de cálculos realizados en el objeto subordinado: hemos hecho su análogo usando el puntero de función. La clase Manager controla los cálculos, y los cálculos en sí (muy probablemente, utilizando fórmulas diferentes) se realizan en clases separadas; en este ejemplo se muestra una de ellas, la clase Element.
class Manager; // preliminary announcement
|
Un objeto subordinado puede utilizar el enlace recibido para notificar al «jefe» el progreso del trabajo. Al llegar al final del cálculo se envía una señal al objeto de control de que es posible borrar el objeto de calculadora, o dejar que trabaje otro. Por supuesto, el array fijo de un elemento de la clase Manager no tiene un aspecto muy impresionante, pero como demostración, sirve para entender el punto. El administrador no sólo gestiona la distribución de tareas informáticas, sino que también proporciona una capa abstracta para notificar al usuario: en lugar de enviar mensajes a un registro, puede escribirlos en un archivo independiente, mostrarlos en pantalla o enviarlos a Internet.
Por cierto: preste atención a la declaración preliminar de la clase Manager antes de la definición de la clase Element. Es necesario describir en la clase Element un puntero a la clase Manager, que se define a continuación en el código. Si se omite la declaración forward, obtendremos el error «'Manager': token inesperado, probablemente falta el tipo...».
La necesidad de una declaración forward surge cuando dos clases se refieren entre sí a través de sus miembros: en este caso, sea cual sea el orden en que dispongamos las clases, es imposible definir completamente ninguna de ellas. Una declaración forward permite reservar un nombre de tipo sin una definición completa.
Una propiedad fundamental de los punteros es que un puntero a una clase base puede utilizarse para apuntar a un objeto de cualquier clase derivada. Esta es una de las manifestaciones de polimorfismo. Este comportamiento es posible porque los objetos derivados contienen «sub-objetos» integrados de clases progenitoras como matrioskas de muñecas anidadas.
En particular, para nuestra tarea con las formas, es fácil describir un array dinámico de punteros Shape y añadirle objetos de distintos tipos a petición del usuario.
El número de clases se ampliará a cinco (Shapes2.mq5). Además de Rectangle y Ellipse, vamos a añadir Triangle y a crear también una clase derivada de Rectangle para un cuadrado (Square), y una clase derivada de Ellipse para un círculo (Circle). Obviamente, un cuadrado es un rectángulo con lados iguales, y un círculo es una elipse de radios grande y pequeño iguales.
Para pasar un nombre de clase de cadena a lo largo de la cadena de herencia, vamos a añadir en las secciones protected de los constructores especiales de las clases Rectangle y Ellipse con un parámetro de cadena adicional t:
class Rectangle : public Shape
|
Entonces, al crear un cuadrado, no sólo establecemos tamaños iguales para los lados, sino que también pasamos typename(this) de la clase Square:
class Square : public Rectangle
|
Además, vamos a mover los constructores de la clase Shape a la sección protected. Esto prohibirá la creación del objeto Shape por sí mismo: sólo puede actuar como base para sus clases descendientes.
Vamos a asignar la función addRandomShape para generar formas, lo que devuelve un puntero a un objeto recién creado. Para fines de demostración, ahora implementará una generación aleatoria de formas: sus tipos, posiciones, tamaños y colores.
Los tipos de forma admitidos se resumen en la enumeración SHAPES: se corresponden con cinco clases implementadas.
La función random devuelve números aleatorios en un rango determinado (utiliza la función integrada rand, que devuelve un entero aleatorio en el rango de 0 a 32767 cada vez que es invocada. Los centros de las formas se generan en el rango de 0 a 500 píxeles, y los tamaños de las formas están en el rango de hasta 200. El color está formado por tres componentes RGB (véase la sección Color), cada uno de los cuales oscila entre 0 y 255.
int random(int range)
|
Generamos 10 formas y las imprimimos en el registro (el resultado puede variar debido a la aleatoriedad de la elección de tipos y propiedades). No olvide borrar los objetos con delete porque fueron creados dinámicamente (aquí esto se hace en el mismo bucle porque las formas no se usan más; en un programa real, lo más probable es que el array de formas se almacene de alguna manera en un archivo para luego cargarla y seguir trabajando con una imagen).
0: Ellipse 241 38
|
Las formas se crean con éxito e informan sobre sus propiedades.
Ya estamos listos para acceder a la API de nuestras clases, es decir, al método draw.