La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

Algoritmos y Estructuras de Datos Introducción al C++ Herencia.

Presentaciones similares


Presentación del tema: "Algoritmos y Estructuras de Datos Introducción al C++ Herencia."— Transcripción de la presentación:

1 Algoritmos y Estructuras de Datos Introducción al C++ Herencia

2 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.

3 Herencia La nueva clase obtenida se conoce como clase derivada, y las clases a partir de las cuales se deriva, clases base. Base Derivada

4 Herencia Además, cada clase derivada puede usarse como clase base para obtener una nueva clase derivada. Base Derivada

5 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 Derivada Base 2

6 Herencia Esto nos permite crear una jerarquía de clases tan compleja como sea necesario. Base 1 Derivada Base 2 Derivada

7 Herencia 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.

8 Herencia Vehículos Pasajeros Carga Publico Particular Herencia Simple

9 Herencia 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.

10 Herencia Ave Caballo Pegasus Herencia Múltiple La herencia múltiple nos permite resolver nuevas situaciones.

11 Herencia Que se hereda de la clase base: Clase Base Datos miembro Funciones miembro Constructores Destructores (=) Sobrecargado Otros operadores sobrecargados Clase Derivada Datos miembro Funciones miembro Constructores Destructores (=) Sobrecargado Otros operadores sobrecargados Datos miembro propios Funciones miembro propios Constructores propios Destructores propios

12 Herencia Derivación Sintaxis: class : [public|private|protected] [,[public|private|protected] ] {}; 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

13 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

14 Herencia myBox occupies 24 bytes myCandyBox occupies 26 bytes myMintBox occupies 26 bytes myBox length is 4 Ejemplo (cont): // EX10_01.CPP // Using a derived class #include // For stream I/O #include // 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; } 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

15 Herencia Ejemplo: // EX10_01.CPP // Using a derived class #include // For stream I/O #include // 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 miembro m_Length cambio 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)

16 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. 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; }; };

17 Herencia int main(void) { CBox myBox(4.0,3.0,2.0); // Create CBox object CCandyBox myCandyBox; CCandyBox myMintBox("Wafer Thin Mints"); // Create CCandyBox object 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; } myBox occupies 24 bytes myCandyBox occupies 26 bytes myMintBox occupies 26 bytes myMintBox volume is 1 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.

18 Herencia 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 !!! 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.

19 Herencia 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!!!!! class Cbox { public: ………… protected: ………… private: ………… } class CBBox : protected CBox { protected: ………… protected: ………… } class Cbox : private CBox { private: ………… private: ………… } class CAbox : public CBox { public: ………… protected: ………… } No access

20 Herencia 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 la clase base debe ser llamado primero. Entonces el programa debe llamar al constructor de la clase base antes de empezar a ejecutar el 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

21 Herencia 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. 2-Llamada al constructor por defecto de la clase base 1-Invocación al constructor de la clase derivada (no se ejecuta ) 3-Ejecución del código del constructor de la clase derivada

22 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.

23 Herencia 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 <<"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); } ~CCandyBox() // Destructor { delete[] m_Contents; } }; 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

24 Herencia 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 <<"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); } 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. Operación de los Constructores en las clases derivadas

25 Herencia // 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); } 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"); 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 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 ).

26 Herencia Operación de los Destructores en las clases derivadas 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.

27 Herencia El copiador constructor en la clase derivada 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; }

28 Herencia 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: // 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); }

29 Herencia El copiador constructor en la clase derivada Los resultados después de agregar ambos constructores copiadores y probarlos con el siguiente main son: 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.

30 Herencia 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) // 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

31 Herencia El copiador constructor en la clase derivada 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.

32 Herencia Funciones miembro Amigas Una función amiga puede ser a su vez miembro de otra clase Ej: 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 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 }

33 Herencia 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 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 }

34 Herencia 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. 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; }

35 Herencia 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 Amiga de CBottle CBottle Nueva CCarton

36 Herencia 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(). 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; };

37 Herencia 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. 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

38 Herencia 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. 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 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.

39 Herencia Funciones Virtuales Podríamos verificar si la función volumen de la clase derivada funciona bien para ello podemos construir el siguiente main: 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 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()

40 Herencia 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

41 Herencia Funciones Virtuales 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.

42 Herencia Funciones Virtuales Modifiquemos entonces el programa de manera de hacer virtual a la función volume() en la clase base : 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; };

43 Herencia 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 class CGlassBox: public CBox // Derived class { public: // Function to calculate volume of a CGlassBox allowing 15% for packing virtual double Volume(void) { return 0.85*m_Length*m_Breadth*m_Height; } // Constructor CGlassBox(double lv, double bv, double hv): CBox(lv,bv,hv){} };

44 Herencia Funciones Virtuales Si ahora repetimos la prueba obtenemos el siguiente resultado: 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

45 Herencia Funciones Virtuales void ShowVolume(void) // Function to show the volume of an object { cout << endl << "CBox usable volume is " << Volume() ; } virtual double Volume(void) // CBox { return m_Length*m_Breadth*m_Height; } Object.ShowVolume(void) // Call virtual double Volume(void) // CGlassBox { return 0.85*m_Length*m_Breadth*m_Height; } 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) Tipo de objeto Selector según el Tipo de objeto

46 Herencia Funciones Virtuales 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 como polimorfismo.

47 Herencia Funciones Virtuales 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 como polimorfismo.

48 Herencia 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: 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.

49 Herencia Funciones Virtuales Punteros a objetos de una clase 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

50 Herencia 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 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

51 Herencia Funciones Virtuales Puras C++ tiene una variante de las funciones virtuales llamadas funciones virtuales puras. Una función virtual pura es una función virtual que tiene un prototipo pero carece de definición. La sintaxis para definir estas funciones consiste en poner “ =0 ” antes del ;. Cuando una clase tiene una función virtual pura no se pueden crear objetos de dicha clase La idea es que las clases con funciones virtuales puras sirvan solo como clases base a partir de las cuales se pueden derivar otras. Por esta razón a las clases que tienen una o mas funciones virtuales puras se las denomina clases abstractas Cada clase derivada de la clase abstracta deberá tener definiciones para cada función virtual pura de la clase base de lo contrario la clase derivada también seria una clase abstracta Una clase abstracta puede tener funciones y datos miembros. Lo que hace abstracta a una clase es la existencia de por lo menos de una función virtual pura.

52 Herencia Funciones Virtuales Puras Supongamos que definimos una clase CContainer como clase base abstracta para definir objetos como Cbox o CBottle. Esta clase no debería tener miembros pero tendría una función virtual Volume() que serviría para cualquier clase derivada. class CContainer // Generic base class for specific containers { public: // Function for calculating a volume - no content // This is defined as a 'pure' virtual function, signified by '=0' virtual double Volume(void) = 0; // Function to display a volume virtual void Show Volume() { cout << endl << "Volume is " << Volume(); } }; La función ShowVolume() es declarada acá como virtual y sirve para mostrar el volumen de objetos de clases derivadas. Dado que es virtual puede ser redefinida en las clases derivadas. Si no se redefine se llamara a la versión de la clase base.

53 Herencia Funciones Virtuales Puras Ejemplo: Definimos dos clases derivadas de CContainer: CBox y CCan. class CBox : public CContainer // Derived class { public: // Function to show the volume of an object virtual void ShowVolume(void) { cout << endl << "CBox usable volume is " << Volume(); } // Function to calculate the volume of a CBox object virtual double Volume(void) { 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; }; class CCan : public CContainer // Derived class { public: // Function to calculate the volume of a can virtual double Volume() { return.25*PI*m_Diameter*m_Diameter*m_Height; } // Constructor CCan(double hv=4.0, double dv=2.0) : m_Height(hv), m_Diameter(dv){} protected: double m_Height; double m_Diameter; };

54 Herencia Funciones Virtuales Puras Podemos probar las clases definidas en el ejemplo anterior con el siguiente main. int main(void) { // Pointer to abstract base class // initialized with address of CBox object CContainer* pC1 = new CBox(2.0,3.0,4.0); // Pointer to abstract base class // initialized with address of CCan object CContainer* pC2 = new CCan(6.5, 3.0); pC1->ShowVolume(); // Output the volumes of the two pC2->ShowVolume(); // objects pointed to cout << endl; delete pC1; // Now clean up the free store delete pC2; //.... return 0; } CBox usable volume is 24 Volume is 45.9458 Nótese que en la primer llamada se llamo a la función ShowVolume () redefinida en CBox mientras en el segundo caso se llamo a la versión se dicha función en la clase base.

55 Herencia Funciones Virtuales Puras Podemos probar ahora crear otro nivel de derivación para ver que ocurre con las funciones heredadas: Class Ccontainer Class CBox Class CCan Class CGlassBox Direct Base of Ccan Direct Base of CBox Indirect Base of CGlassBox Direct Base of CGlassBox MAIN Observar que la única función virtual redefinida es Volume(). La función ShowVolume() no esta redefinida en las clases derivadas

56 Herencia Funciones Virtuales Puras class CContainer // Generic base class for specific containers { public: // Function for calculating a volume - no content // This is defined as a 'pure' virtual function, signified by '=0' virtual double Volume(void) = 0; // Function to display a volume virtual void ShowVolume() { cout << endl << "Volume is " << Volume(); } };

57 Herencia Funciones Virtuales Puras class CBox : public CContainer // Derived class { public: // Function to calculate the volume of a CBox object virtual double Volume(void) { 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; };

58 Herencia Funciones Virtuales Puras class CCan : public CContainer { public: // Function to calculate the volume of a can virtual double Volume() { return 0.25*PI*m_Diameter*m_Diameter*m_Height; } // Constructor CCan(double hv=4.0, double dv=2.0) : m_Height(hv), m_Diameter(dv){} protected: double m_Height; double m_Diameter; };

59 Herencia Funciones Virtuales Puras class CGlassBox: public CBox // Derived class { public: // Function to calculate volume of a CGlassBox // allowing 15% for packing virtual double Volume(void) { return 0.85*m_Length*m_Breadth*m_Height; } // Constructor CGlassBox(double lv, double bv, double hv): CBox(lv,bv,hv){} };

60 Herencia Funciones Virtuales Puras int main(void) { // Pointer to abstract base class initialized with CBox object address CContainer* pC1 = new CBox(2.0,3.0,4.0); CCan myCan(6.5, 3.0); // Define CCan object CGlassBox myGlassBox(2.0, 3.0, 4.0); // Define CGlassBox object pC1->ShowVolume(); // Output the volume of CBox delete pC1; // Now clean up the free store // initialized with address of CCan object pC1 = &myCan; // Put myCan address in pointer pC1->ShowVolume(); // Output the volume of CCan pC1 = &myGlassBox; // Put myGlassBox address in pointer pC1->ShowVolume(); // Output the volume of CGlassBox cout << endl; return 0; } Volume is 24 Volume is 45.9458 Volume is 20.4 La función ShowVolume() efectivamente se propago a todas las clases derivadas Mientras que la version apropiada de Volumen fue llamada en cada caso.

61 Herencia Funciones Virtuales Puras: Destructores virtuales class CContainer { public: // Destructor ~CContainer() { cout << "CContainer destructor called" << endl; } ……………………….. }; Uno de los problemas que existen cuando se manejan objetos de la clases derivadas mediante punteros es que no siempre se llama al destructor apropiado de la clase. Podemos ver esto agregando a cada clase derivada un destructor con un mensaje que nos indique cual fue invocado. class CCan : public CContainer { public: // Destructor ~CCan() { cout << "CCan destructor called" << endl; } ………………………………….. }; class CBox : public CContainer { public: // Destructor ~CBox() { cout << "CBox destructor called" << endl; } ………………………………….. }; class CGlassBox: public CBox { public: // Destructor ~CGlassBox() { cout << "CGlassBox destructor called" << endl; } };

62 Herencia Funciones Virtuales Puras: Destructores virtuales int main(void) { // Pointer to abstract base class initialized with CBox object address CContainer* pC1 = new CBox(2.0,3.0,4.0); CCan myCan(6.5, 3.0); // Define CCan object CGlassBox myGlassBox(2.0, 3.0, 4.0); // Define CGlassBox object pC1->ShowVolume(); // Output the volume of CBox cout << endl << "Delete CBox" << endl; delete pC1; // Now clean up the free store pC1 = new CGlassBox(4.0, 5.0, 6.0); // Create CGlassBox dynamically pC1->ShowVolume(); //...output its volume... cout << endl << "Delete CGlassBox" << endl; delete pC1; //...and delete it pC1 = &myCan; // Get myCan address in pointer pC1->ShowVolume(); // Output the volume of CCan pC1 = &myGlassBox; // Get myGlassBox address in pointer pC1->ShowVolume(); // Output the volume of CGlassBox cout << endl; return 0; } Podemos probar las clases con el siguiente main:

63 Herencia Funciones Virtuales Puras: Destructores virtuales Volume is 24 Delete CBox CContainer destructor called Volume is 102 Delete CGlassBox CContainer destructor called Volume is 45.9458 Volume is 20.4 CGlassBox destructor called CBox destructor called CContainer destructor called CCan destructor called CContainer destructor called La salida del programa es la siguiente int main(void) { // Pointer to abstract base class initialized with CBox object address CContainer* pC1 = new CBox(2.0,3.0,4.0); CCan myCan(6.5, 3.0); // Define CCan object CGlassBox myGlassBox(2.0, 3.0, 4.0); // Define CGlassBox object pC1->ShowVolume(); // Output the volume of CBox cout << endl << "Delete CBox" << endl; delete pC1; // Now clean up the free store pC1 = new CGlassBox(4.0, 5.0, 6.0); // Create CGlassBox dynamically pC1->ShowVolume(); //...output its volume... cout << endl << "Delete CGlassBox" << endl; delete pC1; //...and delete it pC1 = &myCan; // Get myCan address in pointer pC1->ShowVolume(); // Output the volume of CCan pC1 = &myGlassBox; // Get myGlassBox address in pointer pC1->ShowVolume(); // Output the volume of CGlassBox cout << endl; return 0; } CGlass Out of scope CCan Out of scope

64 Herencia Funciones Virtuales Puras: Destructores virtuales De la salida del programa se puede ver que en el caso de los objetos creados en forma dinámica no se llamo a los destructores apropiados de la clase derivada solamente al destructor de la clase base. En el caso de los objetos automáticos no existe dicho problema dado que el compilador sabe que tipo de objetos son en tiempo de compilación y por lo tanto sabe a que destructores invocar. En el caso de los objetos creados en forma dinámica (new) en el momento de llamar al destructor la única información que tiene el compilador que el puntero usado en el delete es un puntero a la clase base y por lo tanto solo establece un enlace estático (early binding) con el destructor de la clase base. La solución a este problema es definir al destructor de la clase base como virtual de la misma manera que se hizo con las demás funciones virtuales. No es necesario hacer esto con los destructores de la clase derivada pero mejora la documentación hacerlo. class CContainer { public: // Destructor virtual ~CContainer() { cout << "CContainer destructor called" << endl; } ……………………….. };

65 Herencia Funciones Virtuales Puras: Destructores virtuales Volume is 24 Delete CBox CBox destructor called CContainer destructor called Volume is 102 Delete CGlassBox CGlassBox destructor called CBox destructor called CContainer destructor called Volume is 45.9458 Volume is 20.4 CGlassBox destructor called CBox destructor called CContainer destructor called CCan destructor called CContainer destructor called Con esta simple modificación la salida es: Ahora los destructores para los objetos creados en forma dinámica son invocados en el mismo orden que los destructores para los objetos automáticos. Class Ccontainer Class CBox Class CCan Class CGlassBox


Descargar ppt "Algoritmos y Estructuras de Datos Introducción al C++ Herencia."

Presentaciones similares


Anuncios Google