- 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
Definición de clase
La declaración de definición de clase tiene muchos componentes opcionales que afectan a sus características. De forma generalizada, puede representarse del siguiente modo:
class class_name [: modifier_access name_parent_class ...]
|
Para facilitar la presentación, empezaremos por la sintaxis mínima suficiente y la iremos ampliando a medida que avancemos en el material.
Como punto de partida, utilizamos una tarea con un programa de dibujo condicional que admite varios tipos de formas.
Para definir una nueva clase, utilice la palabra clave class seguida del identificador de la clase y un bloque de código entre llaves. Como todas las sentencias, una definición de este tipo debe terminar con un punto y coma.
El bloque de código puede estar vacío. Por ejemplo, una plantilla compilable de la clase Shape para un programa de dibujo tiene este aspecto:
class Shape
|
De los capítulos anteriores del libro, sabemos que las llaves denotan el contexto o ámbito de las variables. Cuando tales bloques aparecen en la definición de una función, definen su contexto local. Además, existe un contexto global en el que se definen las propias funciones, así como las variables globales.
Esta vez, los paréntesis de la definición de clase definen un nuevo tipo de contexto: el contexto de clase. Este es un contenedor tanto para variables como para funciones declaradas dentro de la clase.
La descripción de variables para almacenar propiedades de clase se realiza mediante las sentencias habituales dentro del bloque (Shapes1.mq5).
class Shape
|
Aquí hemos declarado algunos de los campos analizados en las secciones teóricas: las coordenadas del centro de la forma y el color de relleno.
Tras dicha descripción, el tipo definido por el usuario Shape pasa a estar disponible en el programa junto con los tipos integrados. En concreto, podemos crear una variable de este tipo, y ésta contendrá en su interior los campos especificados. Sin embargo, aún no podemos hacer nada con ellos, ni siquiera asegurarnos de que están ahí.
void OnStart()
|
Los miembros de una clase son privados por defecto y, por lo tanto, no se puede acceder a ellos desde otras partes del código externas a la clase. Este es el principio de la encapsulación en acción.
Si intentamos dar salida a una forma en el registro, el resultado nos decepcionará por varias razones.
El enfoque más sencillo provocará el error «los objetos sólo se pasan por referencia» (también lo hemos visto con estructuras):
Print(s); // 's' - objects are passed by reference only |
Los objetos pueden constar de muchos campos y, debido a su gran tamaño, resulta ineficiente pasarlos por valor. Por lo tanto, el compilador requiere que los parámetros de tipo objeto se pasen por referencia, mientras que Print toma valores.
De la sección sobre los parámetros de las funciones (véase la sección Parámetros de valor y parámetros de referencia), sabemos que el símbolo '&' se utiliza para describir referencias. Sería lógico suponer que, para obtener una referencia a una variable (en este caso, un objeto s de tipo Shape), es necesario anteponer el mismo signo a su nombre.
Print(&s); |
Esta sentencia compila y se ejecuta sin problemas, pero no hace exactamente lo que se esperaba.
El programa produce un número entero durante la ejecución, por ejemplo, 1 o 2097152 (lo más probable es que sea diferente). Un signo ampersand delante de un nombre de variable significa obtener un puntero a esta variable, no una referencia (a diferencia de la descripción de un parámetro de función).
Los punteros se tratarán en detalle en otra sección. No obstante, tenga en cuenta que MQL5 no proporciona acceso directo a la memoria, y el puntero a un objeto es un descriptor, o de una manera sencilla, un número de objeto único (es asignado por el propio terminal). Pero incluso si el puntero apuntara a una dirección en memoria (como ocurre en C++), eso no proporcionaría una forma legal de leer el contenido del objeto.
Para enviar el contenido de los objetos Shape al registro o lo que sea, se necesita una función miembro de la clase que podemos llamar toString y debe devolver una cadena con alguna descripción del objeto. Podemos decidir más tarde qué mostrar en ella. Reservemos también el método draw para dibujar la forma. Por ahora, servirá de declaración de la futura interfaz de programación de objetos.
class Shape
|
La definición de las funciones de los métodos se realiza de la forma habitual, con la única diferencia de que se sitúan dentro del bloque de código que forma la clase.
En el futuro aprenderemos a separar la declaración de una función dentro del bloque de clase y su definición fuera del bloque. Este enfoque se utiliza a menudo para poner las declaraciones en un archivo de encabezado y «ocultar» las definiciones en un archivo mq5. Esto hace que el código sea más comprensible (debido a que la interfaz de programación se presenta por separado, de forma compacta, sin implementación). También permite distribuir bibliotecas de software como archivos ex5 si es necesario (sin el código fuente principal pero proporcionando un archivo de encabezado que es suficiente para llamar a los métodos de la interfaz externa).
Dado que el método toString forma parte de la clase, tiene acceso a las variables y puede convertirlas en una cadena. Por ejemplo:
string toString()
|
No obstante, ahora toString y draw son privados, al igual que el resto de los campos. Tenemos que hacer que estén disponibles desde fuera de la clase.