- 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, referencias y const
Una vez descubiertos los tipos integrados y de objeto, así como los conceptos de referencia y puntero, probablemente tenga sentido hacer una comparación de todas las modificaciones de tipo disponibles.
Las referencias en MQL5 sólo se utilizan cuando se describen los parámetros de funciones y métodos. Además, los parámetros de tipo objeto deben pasarse por referencia.
void function(ClassOrStruct &object) { } // OK
|
Aquí ClassOrStruct es el nombre de la clase o estructura.
Está permitido pasar sólo variables (LValue) como argumento de un parámetro de tipo referencia, pero no constantes o valores temporales obtenidos como resultado de la evaluación de una expresión.
No se puede crear una variable de tipo referencia ni devolver una referencia desde una función.
ClassOrStruct &function(void) { return Class(); } // wrong
|
Los punteros en MQL5 sólo están disponibles para objetos de clase. No se admiten punteros a variables de estructuras o tipos integrados.
Puede declarar una variable o parámetro de función de tipo puntero a un objeto, y también devolver un puntero a un objeto desde la función.
ClassOrStruct *pointer; // OK
|
Sin embargo, no se puede devolver un puntero a un objeto local automático, ya que éste se liberará al salir la función y el puntero dejará de ser válido.
Si la función devuelve un puntero a un objeto asignado dinámicamente dentro de la función con new, entonces el código de llamada debe «recordar» liberar el puntero con delete.
Un puntero, a diferencia de una referencia, puede ser NULL. Los parámetros de puntero pueden tener un valor por defecto, pero las referencias no (error «la referencia no se puede inicializar»).
void function(ClassOrStruct *object = NULL) { } // OK
|
Los enlaces y los punteros pueden combinarse en la descripción de un parámetro. Así, una función puede tomar una referencia a un puntero, y luego los cambios en el puntero en la función estará disponible en el código de llamada. En concreto, puede implementarse de este modo la función de fábrica, que es responsable de la creación de objetos.
void createObject(ClassName *&ref)
|
Es cierto que, para devolver un único puntero desde una función, suele ser habitual utilizar la sentencia return, por lo que este ejemplo es un tanto artificial. Sin embargo, en aquellos casos en los que es necesario pasar un array de punteros al exterior, una referencia al mismo en el parámetro se convierte en la opción preferida. Por ejemplo, en algunas clases de la biblioteca estándar para trabajar con clases contenedoras del tipo map con pares [clave, valor] (MQL5/Include/Generic/SortedMap.mqh, MQL5/Include/Generic/HashMap.mqh) existen métodos CopyTo para obtener arrays con elementos CKeyValuePair.
int CopyTo(CKeyValuePair<TKey,TValue> *&dst_array[], const int dst_start = 0); |
El tipo de parámetro dst_array puede parecer poco familiar: se trata de una plantilla de clase. Hablaremos de las plantillas en el capítulo siguiente. Aquí, por ahora, lo único que nos importa es que se trata de una referencia a un array de punteros.
El modificador const impone un comportamiento especial para todos los tipos. En relación con los tipos integrados, esto se abordó en la sección sobre Variables constantes. Los tipos de objetos tienen sus propias características.
Si un parámetro de función o variable se declara como puntero o referencia a un objeto (sólo es una referencia en el caso de un parámetro), la presencia del modificador const sobre ellos limita el conjunto de métodos y propiedades a los que se puede acceder a sólo aquellos que también tengan el modificador const. En otras palabras: sólo las propiedades constantes son accesibles a través de punteros y referencias constantes.
Cuando intente llamar a un método no constante o cambiar un campo no constante, el compilador generará un error: «llamar a método no constante para objeto constante» o «no se puede modificar la constante».
Un parámetro de puntero no constante puede tomar cualquier argumento (constante o no constante).
Hay que tener presente que en la descripción del puntero se pueden establecer dos modificadores const, uno referido al objeto y el segundo, al puntero:
- Class *pointer es un puntero a un objeto; el objeto y el puntero funcionan sin limitaciones;
- const Class *pointer es un puntero a un objeto constante; para el objeto, sólo están disponibles los métodos constantes y las propiedades de lectura, pero el puntero se puede cambiar (asignándole la dirección de otro objeto);
- const Class * const pointer es un puntero constante a un objeto constante; para el objeto, sólo están disponibles los métodos constantes y las propiedades de lectura; el puntero no se puede modificar;
- Class * const pointer es un puntero constante a un objeto; el puntero no puede cambiarse, pero las propiedades del objeto sí.
Considere la siguiente clase Counter (CounterConstPtr.mq5) como ejemplo.
class Counter
|
Ha hecho artificialmente la variable pública counter. La clase también tiene dos métodos, uno de los cuales es constante (clone) y el segundo, no (increment). Recordemos que un método constante no tiene derecho a modificar los campos de un objeto.
La siguiente función con el parámetro de tipo Counter *ptr puede llamar a todos los métodos de la clase y cambiar sus campos.
void functionVolatile(Counter *ptr)
|
La siguiente función con el parámetro const Counter *ptr arrojará un par de errores.
void functionConst(const Counter *ptr)
|
Por último, la siguiente función con el parámetro const Counter * const ptr hace aún menos.
void functionConstConst(const Counter * const ptr)
|
En la función OnStart, donde hemos declarado dos objetos Counter (uno constante y el otro no), puede llamar a estas funciones con algunas excepciones:
void OnStart()
|
En primer lugar, tenga en cuenta que las variables también generan un error cuando se intenta llamar a un método constante increment en un objeto no constante.
En segundo lugar, constCounter no puede pasarse a la función functionVolatile: obtenemos el error «no se puede convertir de puntero constante a puntero no constante».
Sin embargo, ambos errores pueden eludirse mediante la asignación explícita de tipos sin el modificador const, aunque esto no es recomendable.