Practica6

= = =**Practica 6: Herencia (II)**=

**1. Sobrecarga (re-definición) de funciones**
En esta práctica vamos a profundizar en la herencia. Para ello, ampliaremos el ejercicio de la práctica anterior (Shape, Circle y Square). Lo primero que vamos a hacer es sobrecargar o redefinir las funciones draw en las clases Circle y Square. Agregue el siguiente método publico al Circle code format="cpp" /** void draw {   int x,y; getPosition(x,y); cout<<"x,y="<<x<<" "<<y<<" color="<<getColor<<" radius="<<_radius<<endl; }

code Observe que dado que las variables_x,_y y _color son privadas, no se pueden usar en la clase Square. Por tanto, tendremos que usar sus funciones miembro para conocer esos valores en Circle.

Ahora, agregue en Square

code format="cpp" /** void draw { int x,y; getPosition(x,y); cout<<"x,y="<<x<<" "<<y<<" color="<<getColor<<" SquareSize="<<_squareSize<<endl; } code Con esto hemos redefinido la función draw en las clases base. Esto es importante porque nos permite definir una función adaptada a la clase. No es la mismo dibujar un circulo que un cuadrado. Las rutinas que usaría una hipotética aplicación serian diferentes.

Si ahora volemos a ejecutar el main obtendremos que el compilador está llamando a la función draw de la clase hija y no de la clase padre. Esto es lo que queríamos.

Ahora, vamos a complicar un poco las cosas. Vamos a tratar con punteros. Escribid el siguiente main:

code format="cpp" using namespace shapes;
 * 1) include 
 * 2) include 

int main(int argc,char **argv) { Circle *C=new Circle; Shape *S=dynamic_cast(C); S->setPosition(10,10); S->setColor(10); S->draw; // S->setRadius(1); this is not possible C->setColor(10); C->setRadius(1); //this is correct C->draw; delete S;

cout<<"--"<(Sq); S->setPosition(20,20); S->setColor(20); S->draw;

Sq->setSquareSize(10); Sq->draw; delete S; } code Lo que hemos hecho aquí es crear una función que fija la posición y color de un objeto y después lo dibuja. Cuando ejecutamos el código obtenemos: code x,y=10 10 color=10 x,y=10 10 color=10 radius=1 -- x,y=20 20 color=20 x,y=20 20 color=20 SquareSize=10

code Fijaros que interesante. Estamos usando un puntero S de tipo Shape* para que apunte a un objeto Circle. Con S podemos acceder a la parte de C que es de la clase Shape. Por ello, podemos llamar a setPosition y setColor. Sin embargo, no es posible llamar a setRadius porque pertenecen a la clase hija. Igual ocurre para la clase Square.

Esto es un mecanismo muy potente ya que nos permite crear funciones que operen de forma “independiente” de la clase siempre y cuando hereden de una conocida. Por ejemplo:

code format="cpp" void modify(int x,int y,int color, Shape *S) {       S->setPosition(x,y); S->setColor(color); }

code la función modifica puede ser usada siempre para modificar el valor de un objeto que herede de Shape.

**2. Funciones virtuales**
Sin embargo aun tenemos un problema. No es posible crear una función de pintado genérica. Es decir, si queremos crear la función:

code format="cpp" void modifyAndPrint(int x,int y,int color, Shape *S) {       S->setPosition(x,y); S->setColor(color); S->draw; }

code Lo que obtenemos es que S->draw está llamando a la función draw de la clase Shape y no a la de las clases hijas. Por tanto, no obtenemos un comportamiento del todo adecuado. ¿ Es posible cambiar este comportamiento? Si, usando funciones virtuales.

Las funciones virtuales son el mecanismo para que desde la clase padre se pueda acceder a las funciones de la clase hija. Para ello, se debe indicar en la clase padre que una función es virtual. Vamos a hacerlo, en la clase Shape. Modificad la función draw para que su declaración sea como sigue: code format="cpp" /** virtual void draw{ cout<<"x,y="<<_x<<" "<<_y<<" color="<<_color<<endl; } code Lo único que hemos hecho es añadir la palabra clave virtual al principio de la declaración. Con esto, le estamos diciendo al compilador: ”si en una clase hija se redefine esta función, llámala a ella y no a mi”. Por tanto es algo de lo que se encarga el compilador. Tras esto, recompilar el main y ejecutar. Vereis que ahora el resultado es code x,y=10 10 color=10 radius=0 x,y=10 10 color=10 radius=1 -- x,y=20 20 color=20 SquareSize=0 x,y=20 20 color=20 SquareSize=10

code Como se observa, ahora se llama a la función de la clase hija y no a la del padre.

Este mecanismo podemos decir que es “inteligente”. En caso de redefinir la función se llama a la del hijo. Si no se redefine, se llama a la del padre.

En caso de herencia de múltiples niveles (una clase que hereda de una clase que hereda de una clase), se llamará a la última en la jerarquía. Pero para ello, en cada uno de los niveles habrá que indicar que la función es virtual. .

2.1 Uso de Destructores cuando se usan funciones virtuales
Una cosa importante a tener en cuenta cuando se usan fuciones virtuales, es que los destructores de la clases padre tambien han de definirse como virtuales. Veamos un ejemplo:

code format="cpp" /**Clase padre class MyC { public: MyC{_myVar=0;} virtual ~MyC{} virtual void do{_myVar=10;}

private: int _myVar; }

/**Clase hija class MyCC:public MyC { public: MyCC{ } ~MyCC{} void do{_myVar=100;} };

int main { MyC *ptr= new MyCC; ptr->do; delete ptr; }

code

El motivo es el que se observa en la función main. En el ejemplo anterior tenemos un puntero del tipo de la clase padre que apunta a la clase hija (ptr). Cuando hacemos delete de ptr, el compilador deberia poder llamar tanto al destructor de la clase padre como al destructor de la clase hija. Para ello, el destructor de la clase padre ha de ser declarado como virtual. De todas formas, el compilador os avisara de ello si no lo haceis.

**3. Funciones virtuales puras y clases abstractas**
Hay un tipo especial de funciones virtuales llamadas puras. Las funciones virtuales puras son funciones que no están definidas en la clase padre ya que en ellas no tiene sentido. Una función virtual pura se define de la siguiente manera (veámoslo con el ejemplo de Shape)

code format="cpp" virtual void draw=0; code

Como se observa, se pone al final de la declaración =0. Ahora bien, al hacer esto, tenemos que quitar el código de la función de la clase Shape. La función virtual pura no tiene código en la clase padre.

Al hacer esto, lo que se hace es obligar a los hijos a redefinirla. Es decir, la función no está definida en el padre, se define en los hijos. Y además, es obligatorio que los hijos la definan. Con esto, la clase padre se dice que es una clase abstracta. Las clases abstractas tienen la particularidad de que no es posible instanciar objetos de ellas. Esto es porque no tienen todas sus funciones definidas. Solo es posible instanciar punteros a estas clases para usar la virtualidad con sus hijas.

Vamos a ver el ejemplo. Tras hacer draw virtual pura en Shape, haced que el main sea el siguiente: code main { Shape S; } code

y compilad. El compilador os dirá que no podéis definir S porque tiene funciones virtuales puras.

Igualmente, probar a comentar la función draw de Circle y compila el siguiente código:

code format="cpp" main { Circle C; } code

Os dirá que no es posible porque hay funciones sin definir.

**4. Parámetros por defecto**
Los parámetros por defecto son un mecanismo de C++ para evitar la llamada a función con parámetros que casi siempre serán los mismos. Veamos un ejemplo. (myclass.h) code format="cpp" class MyClass {   public: MyClass{} MyClass(const MyClass &M){} int doSomething(int a,int b=10) int doMore(int a=1,float b=10,string s=""){ cout<<s<<endl; return a*b;} private: };

myclass.cpp

int MyClass::doSomething(int a,int b) { return a*b; } int MyClass::doMore(int a,float b,string s) { cout<<s<<endl; return a*b; } code

Y el main.cpp code format="cpp" int main {   MyClass C;

cout<< C.doSomething(10)<<endl;

cout<< C.doSomething(10,2)<<endl;

cout<< C.doSomething(10,10)<<endl;

cout<<C.doMore<<endl; cout<<C.doMore(20)<<endl; cout<<C.doMore(20,22)<<endl; cout<<C.doMore(20,22,"JOOOOOOOOOR")<<endl;

}

code Como podéis ver, en la declaración de la función, es posible definir cual es el valor que tienen los parámetros. Pero daros cuenta, que esto no se repite en la implementación de la función en el .cpp. Al definir un valor por defecto para el parámetro, es posible llamar a la función sin necesidad de especificar el valor para ese parámetro como se puede ver en el main.cpp. Si no se da valor, el compilador inicializa el parámetro con el valor por defecto. Sin embargo, si se desea, se puede especificar el valor deseado a la hora de llamar a la función.

¿Cuales son las reglas para definir una función con parámetros por defecto? Las siguientes a) Cuando declaramos una parámetro por defecto, el resto de parámetros de la función que queden a la derecha también deberán estar especificados por defecto.

Ejemplo:

code format="cpp" int foo(int a,int b=10, int c=20); //Esto es correcto int foo(int a,int b=10,int c); //Esto es incorrecto. El parámetro c debe estar indicado por defecto. code Eso se debe a que de no hacerse así, se crearía una indeterminación en la llamada a la función que el compilador no podría resolver. Piense en el ejemplo

code format="cpp" int foo(int a,int b=10,int c);

code Si ahora hago la llamada foo(10,20); ¿a qué parametro corresponde 20? ¿a b o a c?. No es posible saberlo. Por tanto, la declaración correcta es int foo(int a,int b=10, int c=20); Lo que permite hacer las llamadas foo(10); foo(10,23); y foo(10,23,11);

b) No debe haber conflictos con otras funciónes. Ejemplo: Si en una clase definimos code format="cpp"   int foo    int foo(int a=10);

code El compilador no sabría a qué función llamar al hacer foo;

**5. Variables estáticas**
Hasta ahora, cuando hemos definido clases, hemos declarado variables de la clase. Hemos dicho, que al declarar un objeto de la clase, cada objeto tiene una copia de la variable para sí mismo.

En ocasiones es necesario que todos objetos de una clase compartan un variable que sea la misma para todos los objetos. Para ello, se definen las funciones estáticas. Veamos un ejemplo (myc.h):

code format="cpp" namespace test {   class MyC {       public: MyC; MyC(const MyC &M); void modify(int v); void printVar; private: static int _globalClassVariable; }; }
 * 1) ifndef _MyC__H_
 * 2) define _MyC__H_

code y en myc.cpp
 * 1) endif

code format="cpp" using namespace std; namespace test { int MyC::_globalClassVariable = -1;
 * 1) include 
 * 2) include

MyC::MyC { }

MyC::MyC(const MyC &M) {

}

void MyC::modify(int v) { _globalClassVariable=v; }

void MyC::printVar {   cout<<_globalClassVariable<
 * 2) include

int main {   MyC M1; M1.modify(11); M1.printVar; MyC M2; M2.modify(9); M2.printVar; M1.printVar; //imprime 9 }

code Como se observa, para declarar una variable de tipo estática hay que hacer dos cosas. La primera, declararla como tal en el .h (usando la palabra clave static). La segunda, definirla en el .cpp. Daros cuenta que ambos pasos son necesarios. Al definirla en el .cpp, estamos reservando la memoria para esa variable y estamos asignándole un valor inicial.

**6. Ejercicios**
Como ejercicio, vamos a seguir trabajando con la clases de animales. a) Debeís crear la clase Dog, que será similar a Cat, pero que en lugar de miauu tendrá una función llamada void guau. b) Depues, debereís crear una función virtual pura en Animal que se llame void eat; que representa que el animal está comiendo. c) En Cat, la función eat deberá imprimir por pantalla, “Cats eat birds”. d) En Dog, la función deberá imprimir “Dogs eat meat”. Compilad con el siguiente código: code format="cpp" using namespace std; using namespace animals; void doEat(Animal *A) {   A->eat; }
 * 1) include
 * 2) include 
 * 3) include 
 * 4) include

int main(int argc,char **argv) {

time_t timenow; time(&timenow);

Cat cat("hugo",timenow,"largo"); cat.miauu;

Dog dog("snoopy",timenow,"corto"); dog.guau;

doEat(&cat); doEat(&dog);

}

code