Herencia y disposición de estructuras
Las estructuras pueden tener como campos otras estructuras. Por ejemplo, vamos a definir la estructura Inclosure y a utilizar este tipo para el campo data en la estructura Main (StructsComposition.mq5):
struct Inclosure
|
En la lista de inicialización, el campo data se representa mediante un nivel adicional de llaves con los valores de campo Inclosure. Para acceder a los campos de una estructura de este tipo es necesario utilizar dos operaciones de desreferenciación.
Si la estructura anidada no se utiliza en ningún otro lugar, puede declararse directamente dentro de la estructura externa.
struct Main2
|
Otra forma de disponer estructuras es la herencia. Este mecanismo se utiliza normalmente para construir jerarquías de clases (y se tratará en detalle en la sección correspondiente), pero también está disponible para las estructuras.
A la hora de definir un nuevo tipo de estructura, el programador puede indicar el tipo de la estructura progenitora en su cabecera, después del signo de dos puntos (debe definirse antes en el código fuente). Como resultado, todos los campos de la estructura progenitora se añadirán a la estructura hija (en su inicio), y los campos propios de la nueva estructura se ubicarán en memoria detrás de los campos progenitores.
struct Main3 : Inclosure
|
En este caso, la estructura progenitora no está anidada, sino que forma parte integrante de la estructura hija. Gracias a ello, rellenar campos no requiere llaves adicionales al inicializar, ni una cadena de múltiples operadores de desreferenciación.
Main3 m3 = {0.1, 0.2, -1};
|
Las tres estructuras consideradas, Main, Main2 y Main3, tienen la misma representación en memoria y un tamaño de 20 bytes, pero son tipos diferentes.
Print(sizeof(Main)); // 20
|
Como hemos dicho antes (véase Copiar estructuras), el operador de asignación '=' puede utilizarse para copiar tipos de estructuras relacionadas, más concretamente aquellas que están enlazadas por una cadena de herencia. En otras palabras: una estructura de tipo progenitor puede escribirse en una estructura de tipo hija (en este caso, los campos añadidos en la estructura derivada permanecerán intactos) o viceversa, una estructura de tipo hija puede escribirse en una estructura de tipo progenitor (en este caso, se cortarán los campos «extra»).
Por ejemplo:
Inclosure in = {10, 100};
|
Aquí, la variable m3 tiene un tipo Main3 heredado de Inclosure. Como resultado de la asignación m3 = in, los campos X y Y (la parte común para ambos tipos) se copiarán de la variable in del tipo base en los campos X y Y en la variable m3 del tipo derivado. El campo code de la variable m3 permanecerá inalterado.
No importa si la estructura hijo es descendiente directa o lejana del ancestro, es decir, la cadena de herencia puede ser larga. Esta copia de campos comunes funciona entre «hijos», «nietos» y otras combinaciones de tipos de distintas ramas del «árbol genealógico».
Si la estructura progenitora sólo tiene constructores con parámetros, debe ser invocada desde la lista de inicialización cuando se hereda el constructor de la estructura derivada. Por ejemplo:
struct Base
|
En el constructor Base rellenamos el campo mode. Dado que tiene el modificador const, el constructor es la única manera de establecer un valor para él, y esto debe hacerse en forma de una sintaxis de inicialización especial después de los dos puntos (ya no se puede asignar una constante en el cuerpo del constructor). Tener un constructor explícito hace que el compilador no genere un constructor implícito (sin parámetros). Sin embargo, no tenemos un constructor explícito sin parámetros en la estructura Base, y en su ausencia, cualquier clase derivada no sabe cómo llamar correctamente al constructor Base con un parámetro. Por lo tanto, en la estructura Derived se requiere inicializar explícitamente el constructor base: esto se hace también utilizando la sintaxis de inicialización en la cabecera del constructor, después del signo ':'; en este caso, llamamos a Base(1).
Si eliminamos el constructor Derived obtenemos un error de «número de parámetros no válido» en el constructor base, porque el compilador intenta llamar al constructor de Base por defecto (que debería tener 0 parámetros).
Trataremos la sintaxis y el mecanismo de herencia con más detalle en el Capítulo Clase.