Algoritmos y Estructuras de Da tos

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

description

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. - PowerPoint PPT Presentation

Transcript of Algoritmos y Estructuras de Da tos

Page 1: Algoritmos  y Estructuras de Da tos

Algoritmos y Estructuras de Datos

Introducción al C++

Herencia

Page 2: Algoritmos  y Estructuras de Da tos

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.

Page 3: Algoritmos  y Estructuras de Da tos

Herencia

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

Base

Derivada

Page 4: Algoritmos  y Estructuras de Da tos

HerenciaAdemás, cada clase derivada puede usarse como clase base para obtener una nueva clase derivada.

Base

Derivada

Derivada

Page 5: Algoritmos  y Estructuras de Da tos

HerenciaCada 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

Page 6: Algoritmos  y Estructuras de Da tos

HerenciaEsto nos permite crear una jerarquía de clases tan compleja como sea necesario.

Base 1

Derivada

Base 2

Derivada Derivada Derivada

Page 7: Algoritmos  y Estructuras de Da tos

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

Page 8: Algoritmos  y Estructuras de Da tos

Herencia

Vehículos

PasajerosCarga

PublicoParticular

Herencia Simple

Page 9: Algoritmos  y Estructuras de Da tos

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.

Page 10: Algoritmos  y Estructuras de Da tos

HerenciaAve

Caballo

Pegasus

Herencia Múltiple

La herencia múltiple nos permite resolver nuevas situaciones.

Page 11: Algoritmos  y Estructuras de Da tos

Herencia

Que se hereda de la clase base:

Clase BaseDatos miembro

Funciones miembro

Constructores

Destructores

(=) Sobrecargado

Otros operadores sobrecargados

Clase DerivadaDatos miembro

Funciones miembro

Constructores

Destructores

(=) Sobrecargado

Otros operadores sobrecargados

Datos miembro propios

Funciones miembro propios

Constructores propios

Destructores propios

Page 12: Algoritmos  y Estructuras de Da tos

HerenciaDerivació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

Page 13: Algoritmos  y Estructuras de Da tos

HerenciaEjemplo: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

Page 14: Algoritmos  y Estructuras de Da tos

HerenciamyBox occupies 24 bytesmyCandyBox occupies 26 bytesmyMintBox occupies 26 bytesmyBox length is 4

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

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

Page 15: Algoritmos  y Estructuras de Da tos

HerenciaEjemplo:// 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 accessibleParecerí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)

Page 16: Algoritmos  y Estructuras de Da tos

HerenciaEjemplo: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; };};

Page 17: Algoritmos  y Estructuras de Da tos

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 bytesmyCandyBox occupies 26 bytesmyMintBox occupies 26 bytesmyMintBox 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.

Page 18: Algoritmos  y Estructuras de Da tos

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

Page 19: Algoritmos  y Estructuras de Da tos

HerenciaResumen: 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

Page 20: Algoritmos  y Estructuras de Da tos

HerenciaOperació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

Page 21: Algoritmos  y Estructuras de Da tos

HerenciaOperació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

Page 22: Algoritmos  y Estructuras de Da tos

HerenciaOperació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.

Page 23: Algoritmos  y Estructuras de Da tos

HerenciaEjemplo: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

Page 24: Algoritmos  y Estructuras de Da tos

HerenciaEjemplo: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

Page 25: Algoritmos  y Estructuras de Da tos

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 calledCBox constructor calledCCandyBox constructor1 calledCBox constructor calledCCandyBox constructor2 calledmyBox occupies 24 bytesmyCandyBox occupies 28 bytesmyMintBox occupies 28 bytesmyMintBox 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 ).

Page 26: Algoritmos  y Estructuras de Da tos

HerenciaOperació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.

Page 27: Algoritmos  y Estructuras de Da tos

HerenciaEl 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; }

Page 28: Algoritmos  y Estructuras de Da tos

HerenciaEl 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); }

Page 29: Algoritmos  y Estructuras de Da tos

HerenciaEl 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 calledCCandyBox constructor2 calledCBox copy constructor calledCCandyBox copy constructor calledVolume of ChocBox is 24Volume of ChocolateBox is 1

Como puede observarse el volumen de la caja de chocolate es 1!!! y no 24 como esperábamos.

Page 30: Algoritmos  y Estructuras de Da tos

HerenciaEl 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

Page 31: Algoritmos  y Estructuras de Da tos

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

Page 32: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 }

}

Page 33: Algoritmos  y Estructuras de Da tos

HerenciaClases 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 }

}

Page 34: Algoritmos  y Estructuras de Da tos

HerenciaLimitaciones 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;}

Page 35: Algoritmos  y Estructuras de Da tos

HerenciaLimitaciones 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

Page 36: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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;};

Page 37: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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

Page 38: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 24CBox 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.

Page 39: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 24CBox usable volume is 24direct Acces to myBox volume(): 24direct Access to myGlassBox volume(): 20.4Access 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()

Page 40: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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

Page 41: Algoritmos  y Estructuras de Da tos

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

Page 42: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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;};

Page 43: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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){}};

Page 44: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 24CBox usable volume is 20.4

Page 45: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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

Page 46: Algoritmos  y Estructuras de Da tos

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

Page 47: Algoritmos  y Estructuras de Da tos

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

Page 48: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 24CBox 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.

Page 49: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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

Page 50: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 functionint 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 referencevoid Output(CBox& aBox){ aBox.ShowVolume();}

CBox usable volume is 24CBox 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

Page 51: Algoritmos  y Estructuras de Da tos

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

Page 52: Algoritmos  y Estructuras de Da tos

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

Page 53: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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;};

Page 54: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 24Volume 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.

Page 55: Algoritmos  y Estructuras de Da tos

HerenciaFunciones Virtuales Puras

Podemos probar ahora crear otro nivel de derivación para ver que ocurre con las funciones heredadas:

Class Ccontainer

Class CBoxClass 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

Page 56: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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(); }};

Page 57: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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;};

Page 58: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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;};

Page 59: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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){}};

Page 60: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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 24Volume is 45.9458Volume 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.

Page 61: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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; }

};

Page 62: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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:

Page 63: Algoritmos  y Estructuras de Da tos

HerenciaFunciones Virtuales Puras: Destructores virtuales

Volume is 24Delete CBoxCContainer destructor called

Volume is 102Delete CGlassBoxCContainer destructor called

Volume is 45.9458Volume is 20.4CGlassBox destructor calledCBox destructor calledCContainer destructor calledCCan destructor calledCContainer 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

Page 64: Algoritmos  y Estructuras de Da tos

HerenciaFunciones 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; } ……………………….. ………………………..};

Page 65: Algoritmos  y Estructuras de Da tos

HerenciaFunciones Virtuales Puras: Destructores virtuales

Volume is 24Delete CBoxCBox destructor calledCContainer destructor called

Volume is 102Delete CGlassBoxCGlassBox destructor calledCBox destructor calledCContainer destructor called

Volume is 45.9458Volume is 20.4CGlassBox destructor calledCBox destructor calledCContainer destructor calledCCan destructor calledCContainer 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 CBoxClass CCan

Class CGlassBox