650 likes | 825 Views
Algoritmos y Estructuras de Da tos. Introducción al C++ Herencia. Herencia. Herencia es el mecanismo por el cual se pueden crear clases nuevas a partir de clases existentes, conservando las propiedades de la original y añadiendo otras nuevas. Base. Derivada. Herencia.
E N D
Algoritmos y Estructuras de Datos Introducción al C++ Herencia
Herencia Herencia es el mecanismo por el cual se pueden crear clases nuevas a partir de clases existentes, conservando las propiedades de la original y añadiendo otras nuevas.
Base Derivada Herencia La nueva clase obtenida se conoce como clase derivada, y las clases a partir de las cuales se deriva, clases base.
Base Derivada Derivada Herencia Además, cada clase derivada puede usarse como clase base para obtener una nueva clase derivada.
Base 1 Base 2 Derivada Herencia Cada clase derivada puede serlo de una o más clases base. En éste último caso hablaremos de derivación múltiple.
Base 1 Base 2 Derivada Derivada Derivada Derivada Herencia Esto nos permite crear una jerarquía de clases tan compleja como sea necesario.
La herencia permite ir de lo general a lo particular estableciendo una jerarquía. Por ejemplo podemos clasificar un vehículo en función de sus propiedades. Independientemente del uso que se le de, todos los vehículos tienen ciertas propiedades comunes: peso , autonomía ,velocidad máxima , antigüedad, patente, impuesto a la patente. El paso siguiente seria hacer una clasificación menos general. Por ejemplo podemos establecer dos grandes grupos: Vehículos de transporte de carga y de transporte de pasajeros. A su vez los vehículos de transporte de pasajeros podrían volver a clasificarse en dos grupos: Públicos y Particulares. Herencia
Vehículos Herencia Simple Herencia Pasajeros Carga Publico Particular
Cada vez que estemos creando un objeto de cualquier tipo derivado por ejemplo un objeto de tipo particular estaremos creando en un solo objeto un objeto del tipo particular , del tipo pasajeros y tipo vehículo. Nuestro programa podría tratar a ese objeto como si fuese cualquiera de ellos. Podemos aplicar procedimientos genéricos a una clase dada por ejemplo si aumentamos el impuesto a la patente esto afectara a todos los vehículos independiente del tipo. Herencia
Ave Herencia Múltiple Caballo Herencia Pegasus La herencia múltiple nos permite resolver nuevas situaciones.
Clase Base Clase Derivada Datos miembro Funciones miembro Constructores Destructores (=) Sobrecargado Otros operadores sobrecargados Datos miembro Funciones miembro Constructores Destructores (=) Sobrecargado Otros operadores sobrecargados Datos miembro propios Funciones miembro propios Constructores propios Destructores propios Que se hereda de la clase base: Herencia
Derivación Sintaxis: class <clase_derivada> : [public|private|protected] <clase_base1> • [,[public|private|protected] <clase_base2>] {}; Como podemos ver existen tres tipos de atributos de acceso a la clase base desde la derivada, si no se especifica ninguno la especificación de acceso por defecto es privada . La tipo de acceso a la clase base desde la derivada tiene los siguientes efectos, si es: • public: los miembros heredados de la clase base conservan el tipo de acceso con que fueron declarados en ella. • prívate: todos los miembros heredados de la clase base pasan a ser miembros privados en la clase derivada. • protected: todos los miembros heredados de la clase base pasan a ser miembros protegidos en la clase derivada. Ejemplos: class carga : vehículo {…..}; // El especificador de acceso de la clase carga es private (defecto) class carga : prívate vehículo {…..}; // El especificador de acceso de la clase carga es private (explicito) class carga : public vehículo {…..}; // El especificador de acceso de la clase carga es public Herencia
Ejemplo: class CBox { public: double m_Length; double m_Breadth; double m_Height; CBox(double lv=1.0, double bv=1.0, double hv=1.0) { m_Length = lv; m_Breadth = bv; m_Height = hv; } }; class CCandyBox : CBox { public: char* m_Contents;//Nuevo miembro CCandyBox(char* str= "Candy")// Constructor { m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } ~CCandyBox()// Destructor { delete[] m_Contents; }; }; En la definición de la clase CCandyBox se observa la sintaxis mencionada para la derivación esto es, el agregado del operador : para indicar que CCandyBox deriva de la clase CBox. Excepto por la sintaxis de derivación el resto de la clase no presenta novedades. A la clase se le ha agregado un nuevo miembro m_contents que es usado para indicar el contenido de la caja. Para inicializar a este nuevo miembro se define un nuevo constructor y un destructor para liberar la memoria usada para guardar el mensaje. Todos los objetos de la clase CcandyBox tendrán todos los miembros de la clase base (CBox) mas el miembro adicional m_contents Herencia
Ejemplo (cont): // EX10_01.CPP // Using a derived class #include <iostream.h> // For stream I/O #include <string.h> // For strlen() and strcpy() int main(void) { CBox myBox(4.0,3.0,2.0); // Create CBox object // Create CCandyBox object CCandyBox myCandyBox; CCandyBox myMintBox("Wafer Thin Mints"); // Show how much memory the objects require cout << endl << "myBox occupies " << sizeof myBox << " bytes" << endl << "myCandyBox occupies " << sizeof myCandyBox << " bytes" << endl << "myMintBox occupies " << sizeof myMintBox << " bytes"; cout << endl << "myBox length is " << myBox.m_Length; cout << endl; return 0; } myBox occupies 24 bytes myCandyBox occupies 26 bytes myMintBox occupies 26 bytes myBox length is 4 Herencia Esto se compilo con un compilador de 16 bits (Borland C++ 3.1) Cada double tiene 8 bytes como tenemos 3 miembros double en la clase base entonces myBox ocupa 24 bytes. Como CCandyBox tiene un miembro adicional m_contents (que es un puntero) hay que agregar 2 bytes mas lo que da un total de 26 bytes para este objeto. Es decir que el objeto myCandyBox y myMintBox efectivamente heredaron los 3 miembros de CBox Nota: La cantidad de bytes también puede depender de la alineación que use el compilador
Ejemplo: // EX10_01.CPP // Using a derived class #include <iostream.h> // For stream I/O #include <string.h> // For strlen() and strcpy() int main(void) { // Create CBox object CBox myBox(4.0,3.0,2.0); // Create CCandyBox object CCandyBox myCandyBox; CCandyBox myMintBox("Wafer Thin Mints"); // Show how much memory the objects require cout << endl << "myBox occupies " << sizeof myBox << "bytes" << endl << "myCandyBox occupies " << sizeof myCandyBox << " bytes" << endl << "myMintBox occupies " << sizeof myMintBox << " bytes"; cout << endl << "myBox length is " << myBox.m_Length; myBox.m_Length = 10.0; myCandyBox.m_Length = 10.0; cout << endl; return 0; } Si en nuestro ejemplo anterior le agregamos la siguiente línea: myCandyBox.m_Length = 10.0; Nos encontraremos con el siguiente mensaje de error: 'CBox::m_Length' is not accessible Parecería ser que el miembrom_Lengthcambio su atributo original (public) a private. En efecto cuando especificamos el control de acceso de la clase base no pusimos nada y por lo tanto el control de acceso a la clase base desde la derivada quedo con la especificación de privada en consecuencia todos los miembros se heredaron como privados. Es importante enfatizar que son privados para la clase derivada y por lo tanto NO pueden ser accedidos desde la misma !!!!. Si el acceso a la clase base hubiese sido public el acceso al miembro m_length hubiese sido posible (pues ya es publico en la clase base) Herencia
Ejemplo: class CBox { public: //Function to calculate the volume of a CBox object double Volume(void) { return m_Length*m_Breadth*m_Height; } CBox(double lv=1.0, double bv=1.0, double hv=1.0) { m_Length = lv; m_Breadth = bv; m_Height = hv; } private: double m_Length; double m_Breadth; double m_Height; }; Si en nuestra clase ponemos a los miembros como privados, NO es posible tener acceso desde la clase derivada independientemente del tipo de acceso que definamos en la derivación. Si algo es privado en la clase base lo será para todo el mundo incluso para la clase derivada. La única forma de acceso seria mediante una función publica (en este caso volumen() ) que oficiaría de interfase. Herencia cclass CCandyBox : public CBox { public: char* m_Contents; CCandyBox(char* str= "Candy") // Constructor { m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } ~CCandyBox() // Destructor { delete[] m_Contents; }; };
Con este cambio entonces acceder a los miembros privados de la clase base desde una clase derivada. Es decir mediante una función publica (en este caso volumen() ) que oficiaría de interfase. int main(void) { CBox myBox(4.0,3.0,2.0); // Create CBox object CCandyBox myCandyBox; CCandyBox myMintBox("Wafer Thin Mints"); // Create CCandyBoxobject cout << endl << "myBox occupies " << sizeof myBox // Show how much memory << " bytes" << endl // the objects require << "myCandyBox occupies " << sizeof myCandyBox << " bytes" << endl << "myMintBox occupies " << sizeof myMintBox << " bytes"; cout << endl // Get volume of a CCandyBox object << "myMintBox volume is " << myMintBox.Volume(); cout << endl; return 0; } Herencia myBox occupies 24 bytes myCandyBox occupies 26 bytes myMintBox occupies 26 bytes myMintBox volume is 1
Ejemplo: class CBox { public: double Volume(void) { return m_Length*m_Breadth*m_Height; } CBox(double lv=1.0, double bv=1.0, double hv=1.0) { m_Length = lv; m_Breadth = bv; m_Height = hv; } private: protected: double m_Length; double m_Breadth; double m_Height; }; cclass CCandyBox : public CBox { public: char* m_Contents; double Volume(void) { return m_Length*m_Breadth*m_Height; } CCandyBox(char* str= "Candy") // Constructor { m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } ~CCandyBox() // Destructor { delete[] m_Contents; }; }; Si modificamos el ejemplo anterior como se indica en la figura, la clase derivada ahora tendrá acceso a los miembros de la clase base. Los miembros protegidos de la clase base continúan siendo privados para dicha clase. Miembros protegidos de la clase base Los miembros protegidos de la clase base continúan siendo privados para dicha clase es decir que no pueden ser accedidos por funciones ordinarias fuera de la clase. En cambio los miembros protegidos de la clase base pueden ser vistos por: Funciones miembro de la clase Funciones amigas de la clase Funciones miembros de una clase amiga Funciones miembro de la clase derivada NEW !!! Herencia
class CAbox : public CBox { public: ………… protected: ………… } Resumen: Es importante tener en cuenta que mediante los atributos de acceso solo podemos hacer que el control de acceso a la clase base mas exigente pero Nunca menos!!!!! Herencia class Cbox { public: ………… protected: ………… private: ………… } class CBBox : protected CBox { protected: ………… protected: ………… } class Cbox : private CBox { private: ………… private: ………… } No access
Operación de los Constructores en las clases derivadas Cuando se crea un objeto de una clase derivada ,el programa llama primero al constructor de la clase base y después al constructor de la clase derivada. Esto es lógico pues el constructor de la clase derivada puede necesitar de los datos miembro de la clase base para completar su tarea. Esto es lo mismo que cuando se construye un edificio primero se debe empezar por la base del mismo y luego el resto. Veamos esto con mas detalle: El programa NO puede construir un objeto CCandyBox si no construyo primero un objeto CBox es decir que el constructor de laclase base debe ser llamado primero. Entonces el programa debe llamar al constructor de la clase base antesde empezar a ejecutarel código del constructor de la clase derivada. Por otro lado el constructor de la clase base no puede ser llamado hasta después de ser invocado el constructor de la clase derivada, esto es así porque es el constructor de la clase derivada es quien es invocado inicialmente y debe ser el quien invoque al constructor de la clase base Herencia
2-Llamada al constructor por defecto de la clase base 3-Ejecución del código del constructor de la clase derivada 1-Invocación al constructor de la clase derivada (no se ejecuta ) Operación de los Constructores en las clases derivadas Supongamos que el único constructor de la clase CCandyBox es el constructor por defecto es decir : CCandyBox ( ) { } Nota: La forma de escribir el constructor en una sola línea es perfectamente valido tanto en C como C++ se ha hecho así para poder explicar en detalle lo que ocurre. En algún lugar después de la llamada a la función pero antes del cuerpo de la función es donde se debe hacer la llamada al constructor de la clase base y es exactamente eso lo que ocurre. CCandyBox ( ) { } Este mecanismo ocurre por defecto lo que garantiza la creación del objeto de la clase base antes de ejecutarse el código del constructor de la clase derivada. Herencia
Operación de los Constructores en las clases derivadas Dado que cuando se quiere crear un objeto de la clase derivada se invoca al constructor de la misma todos los parámetros necesarios para la construcción total del objeto deben pasarse a través de los argumentos de este ultimo. Alguno de estos parámetros deberán ser usados para invocar a algún constructor de la clase base. La forma de hacer llegar estos parámetros al constructor de la clase base es usando un mecanismo que se había visto anteriormente: las listas de inicialización CCandyBox (double lv, double bv, double hv, char* str= "Candy") : CBox(lv,bv,hv) { …. } Es interesante notar que la sintaxis usada para invocar al constructor de la clase base es el mismo que se uso para inicializar las variables (después de todo al invocar a CBox estamos creando una variable e inicializándola). Los constructores de la clase derivada no deben duplicar el trabajo que hacen los de la clase base sino completar los detalles adicionales que necesita la clase derivada para completar el objeto (Por ejemplo inicializar los nuevos datos miembros de la clase derivada ). No es necesario agregar destructores en la clase derivada a menos que esta ultima deba realizar tareas que no serán hechas por la clase base. Cuando un objeto deja de existir primero se llama a los destructores de la clase derivada (si los hay) y luego al destructor de la clase base. Herencia
Operación de los Constructores en las clases derivadas Ejemplo: class CBox { public: //Function to calculate the volume of a CBox object double Volume(void) { return m_Length*m_Breadth*m_Height; } // Base class constructor CBox(double lv=1.0, double bv=1.0, double hv=1.0) { cout << endl << "CBox constructor called"; m_Length = lv; m_Breadth = bv; m_Height = hv; } private: double m_Length; double m_Breadth; double m_Height; }; cclass CCandyBox : public CBox { public: char* m_Contents; // Constructor to set dimensions and contents // with explicit call of CBox constructor CCandyBox(double lv, double bv, double hv, char* str= "Candy") :CBox(lv,bv,hv) { cout << endl <<"CCandyBoxconstructor2 called"; m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } // Constructor to set contents // calls default CBox constructor automatically CCandyBox(char* str= "Candy") { cout << endl << "CCandyBoxconstructor1 called"; m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } ~CCandyBox() // Destructor { delete[] m_Contents; } }; Herencia Observar los constructores de la clase derivada el constructor 2 hace una llamada explicita al constructor de la clase base mientras que el constructor 1 lo hace en forma automática.
Operación de los Constructores en las clases derivadas Ejemplo: int main(void) { CBox myBox(4.0,3.0,2.0); CCandyBox myCandyBox; CCandyBox myMintBox(1.0,2.0,3.0,"Wafer Thin Mints"); cout << endl << "myBox occupies " << sizeof myBox // Show how much memory << " bytes" << endl // the objects require << "myCandyBox occupies " << sizeof myCandyBox << " bytes" << endl << "myMintBox occupies " << sizeof myMintBox << " bytes"; cout << endl << "myMintBox volume is " // Get volume of a << myMintBox.Volume(); // CCandyBox object cout << endl; return 0; } // Constructor to set dimensions and contents // with explicit call of CBox constructor CCandyBox(double lv, double bv, double hv, char* str= "Candy") :CBox(lv,bv,hv) { cout << endl <<"CCandyBoxconstructor2 called"; m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } // Constructor to set contents // calls default CBox constructor automatically CCandyBox(char* str= "Candy") { cout << endl << "CCandyBoxconstructor1 called"; m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } Herencia Observar los constructores de la clase derivada: el constructor 2 hace una llamada explicita al constructor de la clase base mientras que el constructor 1 lo hace en forma automática o implícita.
Ejemplo: int main(void) { CBox myBox(4.0,3.0,2.0); CCandyBox myCandyBox; CCandyBox myMintBox(1.0,2.0,3.0,"Wafer Thin Mints"); CBox constructor called CBox constructor called CCandyBox constructor1 called CBox constructor called CCandyBox constructor2 called myBox occupies 24 bytes myCandyBox occupies 28 bytes myMintBox occupies 28 bytes myMintBox volume is 6 Operación de los Constructores en las clases derivadas // Constructor to set dimensions and contents // with explicit call of CBox constructor CCandyBox(double lv, double bv, double hv, char* str= "Candy") :CBox(lv,bv,hv) { cout << endl <<"CCandyBox constructor2 called"; m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } // Constructor to set contents // calls default CBox constructor automatically CCandyBox(char* str= "Candy") { cout << endl << "CCandyBox constructor1 called"; m_Contents = new char[ strlen(str) + 1 ]; strcpy(m_Contents, str); } Herencia Los constructores de la clase derivada no deben duplicar el trabajo que hacen los de la clase base sino completar los detalles adicionales que necesita la clase derivada para completar el objeto (Por ejemplo inicializar los nuevos datos miembros de la clase derivada ).
Operación de los Destructores en las clases derivadas Herencia No es necesario agregar destructores en la clase derivada a menos que esta ultima deba realizar tareas que no serán hechas por la clase base. Cuando un objeto deja de existir primero se llama a los destructores de la clase derivada (si los hay) y luego al destructor de la clase base. Es decir en el orden inverso en el que fueron llamados los constructores.
El copiador constructor en la clase derivada Herencia Recordemos que el constructor copiador es invocado en forma automática cuando deseamos construir un objeto nuevo a partir de uno existente de la misma clase. CBox mybox(2.0, 3.0, 4.0); // Declare and initialize CBox CopyBox(mybox); // Use copy constructor Podemos agregar un Constructor copiador a la clase base Cbox y usar esta ultima para definir un objeto de la clase derivada CCandyBox. // Copy constructor CBox(const CBox& initB) { cout << endl << "CBox copy constructor called"; m_Length = initB.m_Length; m_Breadth = initB.m_Breadth; m_Height = initB.m_Height; }
El copiador constructor en la clase derivada Si la clase derivada CCandyBox es la misma que usamos antes, el constructor copiador de la misma será el que suministra el compilador por defecto. Este ultimo copia el objeto inicializador miembro por miembro a los miembros correspondientes del nuevo objeto. Pero dado que la clase derivada tiene un miembro creado en forma dinámica (m_Contents) el copiador por defecto no sirve y debemos suministrar nuestro propio constructor: Herencia // Derived class copy constructor CCandyBox(const CCandyBox& initCB) { cout << endl << "CCandyBox copy constructor called"; // Get new memory m_Contents = new char[ strlen(initCB.m_Contents) + 1 ]; // Copy string strcpy(m_Contents, initCB.m_Contents); }
El copiador constructor en la clase derivada Los resultados después de agregar ambos constructores copiadores y probarlos con el siguiente main son: Herencia int main(void) { CCandyBox ChocBox(2.0, 3.0, 4.0, "Chockies"); // Declare and initialize CCandyBox ChocolateBox(ChocBox); // Use copy constructor cout << endl << "Volume of ChocBox is " << ChocBox.Volume() << endl << "Volume of ChocolateBox is " << ChocolateBox.Volume() << endl; return 0; } CBox constructor called CCandyBox constructor2 called CBox copy constructor called CCandyBox copy constructor called Volume of ChocBox is 24 Volume of ChocolateBox is 1 Como puede observarse el volumen de la caja de chocolate es 1!!! y no 24 como esperábamos.
El copiador constructor en la clase derivada Evidentemente se llamo al constructor por defecto de la clase base y no el CC de la misma. Lo que debemos hacer es llamar explícitamente al constructor de la clase base (como cuando invocamos al CC desde un main) Herencia // Derived class copy constructor CCandyBox(const CCandyBox& initCB):CBox(initCB) { cout << endl << "CCandyBox copy constructor called"; // Get new memory m_Contents = new char[ strlen(initCB.m_Contents) + 1 ]; // Copy string strcpy(m_Contents, initCB.m_Contents); } Ahora todo funciona correctamente dado que llamamos al CC de la clase base con el objeto initCB. Solo la parte correspondiente a la clase base de initCB va a ser modificada . Ahora el objeto de la clase derivada tendrá el mismo volumen que el objeto que la inicializa
El copiador constructor en la clase derivada Herencia Todo constructor que se escribe para la clase derivada debe ser responsable de la inicialización de todos los miembros de la clase derivada así como sus miembros heredados.
Funciones miembro Amigas Una función amiga puede ser a su vez miembro de otra clase Ej: Herencia Class CCarton { private: double m_Length; //Carton lenght double m_Breadth; //Carton Breadth double m_Height; //Carton height public: Ccarton(CBottle aBottle) { m_Length = aBottle.m_height; //Bottle height m_Breadth= 4.0 * abottle.m_diameter; // Four rows of .. m_Height= 3.0* aBottle.m_diameter; // …three bottles } } Class CBottle { private: double m_height; //Bottle height double m_diameter //Bottle Diameter public: CBottle (double height , double diameter) { m_height = height; m_diameter=diameter; } friend Ccarton::Ccarton(CBottle aBottle) } Los miembros de CBotlle son privados de manera que el constructor de CCarton no los puede acceder. Para acceder a los miembros necesitamos declarar al constructor Ccarton como amigo de la clase CBottle. (declaración en rojo) Observar el uso del operador resolución de entorno para referirse a la clase Ccarton
Clases Amigas Podemos hacer que las funciones miembro de una clase tengan acceso total a los miembros dato de la otra. Esto se puede hacer si la declaramos como clase amiga en la otra. Ej:. Podríamos definir la clase CCarton como amiga de Cbottle si la de claramos como amiga en la definición de Cbottle es decir: friend CCarton; Todas las funciones miembro de CCarton ahora tendrán acceso libre a todos los miembros dato de CBottle Herencia Class CCarton { private: double m_Length; //Carton lenght double m_Breadth; //Carton Breadth double m_Height; //Carton height public: CCarton(CBottle aBottle) { m_Length = aBottle.m_height; //Bottle height m_Breadth= 4.0 * abottle.m_diameter; // Four rows of .. m_Height= 3.0* aBottle.m_diameter; // …three bottles } } Class CBottle { private: double m_height; //Bottle height double m_diameter //Bottle Diameter public: CBottle (double height , double diameter) { m_height = height; m_diameter=diameter; } friend CCarton; }
Limitaciones de las clases amigas La relacion de “amistad” entre las clases no es reciproca es decir que si CCarton es amiga de Cbottle no significa que la situacion inversa sea cierta. La unica manera de lograr esto es declarar a ambas clases como una amiga de la otra. Herencia Class CBottle { private: double m_height; //Bottle height double m_diameter //Bottle Diameter public: CBottle (double height , double diameter) { m_height = height; m_diameter=diameter; } friend CCarton; } Class CCarton { private: double m_Length; //Carton lenght double m_Breadth; //Carton Breadth double m_Height; //Carton height public: CCarton(CBottle aBottle) { m_Length = aBottle.m_height; //Bottle height m_Breadth= 4.0 * abottle.m_diameter; // Four rows of .. m_Height= 3.0* aBottle.m_diameter; // …three bottles } friend CBottle; }
CBottle CCarton Nueva Limitaciones de las clases amigas Las funciones amigas no se heredan. Si definimos una clase que tenga como base a Cbottle las funciones miembro de CCarton NO tendrán acceso a los datos miembros de esta clase ni siquiera a los heredados de CBottle Herencia Amiga de CBottle
Funciones Virtuales Vamos a estudiar en mas profundidad el comportamiento de las funciones miembro heredadas y su relación con las funciones miembro de la clase derivada. Para esto vamos a agregar a la clase CBox una función que muestre el volumen llamada: ShowVolume(). Herencia class CBox // Base class { public: void ShowVolume(void)// Function to show the volume of an object { cout << endl << "CBox usable volume is " << Volume(); } double Volume(void)// Function to calculate the volume of a CBox object { return m_Length*m_Breadth*m_Height; } // Constructor CBox(double lv=1.0, double bv=1.0, double hv=1.0) :m_Length(lv), m_Breadth(bv), m_Height(hv) {} protected: double m_Length; double m_Breadth; double m_Height; };
Funciones Virtuales Vamos ahora a derivar una caja cuyo contenido sea un material mas frágil: cristal y a la que llamaremos CGlassBox. Dado que hay que proteger el contenido de dicha caja debemos dejar espacio para poner material protector (un 15%) por lo tanto el volumen efectivo será menor que el de una caja común. Queda claro entonces que el calculo del volumen de la caja será diferente. Herencia class CGlassBox: public CBox // Derived class { public: // Function to calculate volume of a CGlassBox allowing 15% for packing double Volume(void) { return 0.85*m_Length*m_Breadth*m_Height; } // Constructor CGlassBox(double lv, double bv, double hv): CBox(lv,bv,hv){} }; Ahora cada clase tiene su propia versión de Volume() (esto se conoce como redefinición de método o función miembro) y por lo tanto seria de esperar que cuando queramos mostrar el volumen de un objeto de la clase derivada mediante la función ShowVolume() se llame a la versión Volume() de la clase derivada (CGlasBox). Nota: Cuando se redefine un método de una clase base se debe preservar el nombre de la función su firma (numero y tipo de los parámetros) y el tipo retornado
Funciones Virtuales Podemos ahora pasar a escribir un main que pruebe las clases. Para esto vamos a crear dos cajas una de la clase base y otra de la clase derivada de iguales dimensiones y verificar que los volúmenes correctos de cada caja sean mostrados. Herencia int main(void) { CBox myBox(2.0, 3.0, 4.0); // Declare a base box CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box - same size myBox.ShowVolume(); // Display volume of base box myGlassBox.ShowVolume();// Display volume of derived box cout << endl; return 0; } Algo anduvo mal!! La segunda llamada para un objeto de la clase derivada CGlassBox no hizo uso de la función Volume() de la clase derivada sino la de la clase base. CBox usable volume is 24 CBox usable volume is 24
Funciones Virtuales Podríamos verificar si la función volumen de la clase derivada funciona bien para ello podemos construir el siguiente main: Herencia int main(void) { CBox myBox(2.0, 3.0, 4.0); // Declare a base box CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box - same size myBox.ShowVolume(); // Display volume of base box myGlassBox.ShowVolume(); // Display volume of derived box cout << endl; cout <<"direct Access to myBox volume(): " << myBox.Volume(); cout << endl; cout << "direct Access to myGlassBox volume(): " << myGlassBox.Volume(); cout << endl; cout << "Access to myBox volume() from derived class: " << myGlassBox.CBox::Volume(); return 0; } CBox usable volume is 24 CBox usable volume is 24 direct Acces to myBox volume(): 24 direct Access to myGlassBox volume(): 20.4 Access to myBox volume() from derived class: 24 Como vemos Volume() funciona perfectamente en la clase derivada y además oculta a la función homónima de la clase base lo cual es lo que en cierta forma lo que queríamos. Pero esto ultimo no ocurre si lo hacemos mediante ShowVolume()
Funciones Virtuales El motivo por el cual ocurrió esto es porque la llamada a la función Volume() desde la función ShowVolume() fue fijada en tiempo de complicación por el compilador para la versión de Volume() definida en la clase base. Esto se conoce bajo los siguientes nombres : Static resolución , enlace estático o early binding. Lo que en realidad queremos es que la decisión de a cual del as funciones Volume() se debe llamar se debería resolver en tiempo de ejecución y no en tiempo de compilación basado en el objeto con que se invoca a ShowVolume() lo que se conoce como enlace dinámico o late binding. C++ provee una solucion a este problema y es lo que se conoce como función virtual Herencia
Funciones Virtuales Herencia Una función virtual es una función perteneciente a la clase base que se declara con la palabra reservada virtual. Una función que se declara como virtual en la clase base y que a su vez tiene otra versión de la misma en la clase derivada le indica al compilador que no se desea enlace estático para esta función. Lo que se desea es un enlace dinámico es decir que la selección de la función a invocar deberá hacerse en tiempo de ejecución en base al tipo de objeto que hace la llamada.
Funciones Virtuales Modifiquemos entonces el programa de manera de hacer virtual a la función volume() en la clase base : Herencia class CBox // Base class { public: void ShowVolume(void) // Function to show the volume of an object { cout << endl << "CBox usable volume is " << Volume(); } virtual double Volume(void) // Function to calculate the volume of a CBox object { return m_Length*m_Breadth*m_Height; } // Constructor CBox(double lv=1.0, double bv=1.0, double hv=1.0) :m_Length(lv), m_Breadth(bv), m_Height(hv) {} protected: double m_Length; double m_Breadth; double m_Height; };
Funciones Virtuales En la clase derivada hacemos lo mismo no porque sea necesario sino porque permite a toda persona que lee la definición de la clase saber que la función es virtual y por lo tanto va a ser seleccionada en forma dinámica Herencia class CGlassBox: public CBox // Derived class { public: // Function to calculate volume of a CGlassBox allowing 15% for packing virtualdouble Volume(void) { return 0.85*m_Length*m_Breadth*m_Height; } // Constructor CGlassBox(double lv, double bv, double hv): CBox(lv,bv,hv){} };
Funciones Virtuales Si ahora repetimos la prueba obtenemos el siguiente resultado: Herencia int main(void) { CBox myBox(2.0, 3.0, 4.0); // Declare a base box CGlassBox myGlassBox(2.0, 3.0, 4.0); // Declare derived box - same size myBox.ShowVolume(); // Display volume of base box myGlassBox.ShowVolume();// Display volume of derived box cout << endl; return 0; } CBox usable volume is 24 CBox usable volume is 20.4
Funciones Virtuales Herencia Object.ShowVolume(void)// Call Tipo de objeto void ShowVolume(void) // Function to show the volume of an object { cout << endl << "CBox usable volume is " << Volume() ; } La decisión de a que función se llama se hace en tiempo de ejecución basado en el tipo de objeto que hace la llamada (Late binding) Selector según el Tipo de objeto virtual double Volume(void) // CGlassBox { return 0.85*m_Length*m_Breadth*m_Height; } virtual double Volume(void) // CBox { return m_Length*m_Breadth*m_Height; }
Funciones Virtuales Herencia Importante: Para que una función se comporte como virtual debe tener en todas las clases derivadas el misma lista de parámetros y tipo retornado que la de la clase base de lo contrario el mecanismo de las funciones virtuales no funciona y el enlace es estático El mecanismo de las funciones virtuales es muy poderoso y conocido comopolimorfismo.
Funciones Virtuales Herencia Importante: Para que una función se comporte como virtual debe tener en todas las clases derivadas el misma lista de parámetros y tipo retornado que la de la clase base de lo contrario el mecanismo de las funciones virtuales no funciona y el enlace es estático El mecanismo de las funciones virtuales es muy poderoso y conocido comopolimorfismo.
Funciones Virtuales Punteros a objetos de una clase A un puntero se le puede asignar la dirección de un objeto tanto de la clase base como de sus derivadas. Como consecuencia de esto y del mecanismo de las funciones virtuales si tenemos un puntero a la clase base entonces tendremos diferentes comportamientos según el tipo de objeto apuntado por dicho puntero. Veamos un ejemplo: Herencia int main(void) { CBox myBox(2.0, 3.0, 4.0); // Declare a base box CGlassBox myGlassBox(2.0,3.0,4.0); // Declare derived box of same size CBox* pBox = 0; // Declare a pointer to base class objects pBox = &myBox; // Set pointer to address of base object pBox->ShowVolume(); // Display volume of base box pBox = &myGlassBox; // Set pointer to derived class object pBox->ShowVolume(); // Display volume of derived box cout << endl; return 0; } CBox usable volume is 24 CBox usable volume is 20.4 Como vemos el resultado es exactamente el mismo que en el ejemplo anterior solo que en este ultimo caso la llamada fue explicita.
Funciones Virtuales Punteros a objetos de una clase Herencia El ejemplo con punteros es particularmente interesante pues aun cuando no sabemos a que tipo de objeto esta apuntando el puntero a la clase base el mecanismo de funciones virtuales asegura que la función correcta sea llamada. Esto puede suceder por ejemplo cuando pasamos un puntero a la clase base como argumento de una función. La función no sabe a que tipo de objeto apunta el puntero pero el mecanismo de funciones virtuales garantiza que la función invocada sea la correcta
Funciones Virtuales Uso de Referencias con funciones virtuales Si se define una función que recibe como parámetro una referencia a la clase base entonces podemos pasar como argumento un objeto de la clase derivada. Como consecuencia cuando llamemos a esta función la función virtual apropiada será seleccionada automáticamente veamos un ejemplo Herencia class CBox; // Incomplete class definition required for prototype void Output(CBox& aBox);// Prototype of function int main(void) { CBox myBox(2.0, 3.0, 4.0); // Declare a base box CGlassBox myGlassBox(2.0,3.0,4.0); // Declare derived box of same size Output(myBox); // Output volume of base class object Output(myGlassBox); // Output volume of derived class object cout << endl; return 0; } // Function to output a volume via a virtual function call using a reference void Output(CBox& aBox) { aBox.ShowVolume(); } CBox usable volume is 24 CBox usable volume is 20.4 La salida del programa es exactamente la misma que antes. A que versión de Volume() se invoca dependerá del tipo de objeto que inicializa la referencia