Índex
Miguel A. Almarza
Departament d'Informàtica
IES Mare de Deu de la Merce

       

 
Capítol 5

Herència

  1. Introducció
  2. L'accés als elements de la classe pare.
    1. L'especificador d'accés public
    2. L'especificador d'accés Private
    3. L'especificador d'accés Protected
    4. Resum d'acsés
  3. Constructors i herència.
    1. Ordre d'execucuó dels constructors i dels destructors.
    2. Pas de paràmetres entre constructors.
  4. Herència múltiple.
    1. Classes derivades de classes derivades.
    2. Classe derivada de més d'una classe.
    3. Classes base virtuals.
  5. Punters a classes base i classes derivades.
  6. Funcions virtuals.
  7. Funcions virtuals pures. Classes abstractes.
  8. Exercicis.

5.1. Introducció

La herència és el mecanisme pel que podem derivar classes noves de classes ja existents i d'aquesta manera fer ús del codi ja escrit sense tenir que rescriure'l.

Veiem una classe i una classe derivada:

La classe Punt, que hem utilitzat fins ara, és la classe dels punts de la pantalla en blanc i negre. Ara pensem que tenim una pantalla amb colors i que hem d'afegir nou codi pels punts d'aquesta pantalla. Podrem fer una classe nova, derivada de la classe punt que heretarà les seves dades i les seves funcions membre i a les que afegirem les noves propietats i funcions membre.

La classe PuntColor hereta la classe Punt
La classe Punt
#include <iostream.h>
#include <conio.h>

class Punt
{
    int x;
    int y;
    char Caracter;
public:
    Punt();
    Punt(int n1,int n2);
    Punt(int n1,int n2,char Car);
    void PosaCaracter(char c);
    void PosaCoordenades(int n1, int n2);
    void Dibuixat();
    int TornaX();
    int TornaY();
    char TornaCaracter();
    void ImprimeixDades();
};
Aquesta és la declaració de la classe Punt.

Aquesta declaració de la classe ha de tenir també escrites després totes les funcions. No les incloem aquí ja que estan escrites en capítols anteriors.
La classe PuntColor
class PuntColor : public Punt
{
    int ColorPrimerPla;
    int ColorFons;
public:
    void PosaColors(int C1, int C2);
    void Dibuixat();
    void ImprimeixDadesColor();
};

La declaració de la classe PuntColor és

class PuntColor : public Punt
que indica que hereta la classe Punt. Això fa que els objectes de la nova classe disposin de les dades i funcions membre de la classe Punt.

Veiem que a la classe PuntColor hi ha les noves dades ColorPrimerPla i ColorFons.

També veiem que hi ha les noves funció membre PosaColors() i ImprimeixDadesColor().

Fem notar que també apareix la funció Dibuixat() que ja apareixia a la classe pare Punt. Hem de reescriure-la ja que en aquesta funció hem de incloure el color.

Es a dir que es pot donar dins la classe filla el mateix nom a una funció que sigui més especialitzada que la de la classe pare.

Apareix una altra vegada la idea de polimorfisme amb aquest fet.
void PuntColor::PosaColors(int C1, int C2)
{
    ColorPrimerPla=C1;
    ColorFons =C2;
}

void PuntColor::ImprimeixDadesColor()
{
    cout << "\n";
    ImprimeixDades();
    cout << "Color de primer pla: " << ColorPrimerPla;
    cout << ", Color de fons: " << ColorFons << "\n";
}

void PuntColor::Dibuixat()
{
    gotoxy(TornaX(),TornaY());
    textbackground(ColorFons);
    textcolor(ColorPrimerPla);
    cprintf("%c",TornaCaracter());
}
Implementació de les noves funció membre de la classe PuntColor.

Veiem que la funció ImprimeixDadesColor() té accés a la funció ImprimeixDades() de la classe pare.

Les funcions de les classes filles tenen accés a les funcions públiques de la classe pare.

Fem notar que la funció Dibuixat() de la classe filla és molt diferent de la funció Dibuixat() de la classe pare, però que té el mateix nom.
main ()
{
    clrscr();

    PuntColor A;
    A.PosaCoordenades(3,3);
    A.PosaCaracter('W');
    A.PosaColors(RED,WHITE);
    A.Dibuixat();
    A.ImprimeixDadesColor();
}
A la funció main declarem l'objecte A di tipus PunColor.

Veiem que aquest objecte té accés a tots els elements (dades i funcions membre) de la classe pare Punt.

5.2 L'accés als elements de la classe pare.

La forma general de la declaració d'una classes derivada és

class <Nom_Classe_Derivada> : <especificador d'accés <Nom_Classe_Pare>

on especificador d'accés és la forma d'accés de la classe deriva i pot ser un dels tres tipus public, private o protected.

5.2.1 L'especificador d'accés public

Accés de les classes derivades: public
#include <iostream.h>
#include <conio.h>

class Rectangle
{
    int x1,y1;
    int x2,y2;
public:
    int Tipus;
    void PosaA(int x, int y){x1=x;y1=y;}
    void PosaB(int x, int y){x2=x;y2=y;}
    int TornaX1(){return x1;}
    int TornaY1(){return y1;}
    int TornaX2(){return x2;}
    int TornaY2(){return y2;}
    void Dibuixat();
};

class RectangleInteligent : public Rectangle
{
public:
    float Superficie();
};
Escrivim la classe Rectangle que te com a dades privades x1,y1 i x2,y2. Te una sola dada pública que és el tipus i totes les funcions membre són públiques.

Fem que la classe RectangleInteligent sigui filla de la classe rectangle amb accés public de la classe filla respecte a la classe pare.

Feta d'aquesta manera l'herència tenim que:

  • x1,y1,x2,y2 són dades privades de la classe RectangleInteligent. Les funcions membre de RectangleInteligent no tenen accés a elles.
  • Tipus i totes les funcions membre de Rectangle són elements públics de la classe RectangleInteligent.
float RectangleInteligent::Superficie()
{
    int Base, Altura;
    Base = TornaX2()-TornaX1()+1;
    //Base = x2-x1+1;   Errada
    Altura = TornaY2()-TornaY1() +1;
    return (float) Base * Altura;
}

La funció membre RectangleInteligent té accés a les funcions publiques de la classe pare com TornaX1() o TornaY1() però no té accés a les dades privades de la classe pare com x1 o y1.
void Rectangle::Dibuixat()
{
    int i;
    gotoxy(x1,y1); cout << "┌";
    for(i=x1+1;i<x2;i++) cout << "─";
    gotoxy(x2,y1); cout << "┐";
    for(i=y1+1;i<y2;i++)
    {
        gotoxy(x1,i); cout << "│";
        gotoxy(x2,i); cout << "│";
    }
    gotoxy(x1,y2); cout << "└";
    for(i=x1+1;i<x2;i++) cout << "─";
    gotoxy(x2,y2); cout << "┘";
}
 
main()
{
    clrscr();

    RectangleInteligent C1;

    C1.PosaA(2,2);
    C1.PosaB(20,20);
    C1.Dibuixat();
    cout << "\n" << C1.Superficie() << "\n";
    C1.Tipus = 23;
    cout << C1.Tipus;

    //C1.x1=3;        Errada
}
Els objectes de la classe RectangleInteligent tenen accés a tots els elements públics de la classe Pare com la dada Tipus o la funció membre PosaA.

Els objectes de la classe RectangleInteligent no tenen accés a cap element privat de la classe Pare Rectangle.


5.2.2 L'especificador d'accés Private

Accés de les classes derivades: private
#include <iostream.h>

class Pare
{
private:
    int a;
public:
    int b;
    void PosaA(int n){a=n;}
};

class Fill : private Pare
{
    int c;
public:
    void PosaDades(int n1,int n2, int n3);
};
Declarem la classe Pare amb tres elements, la dada privada a, la dada pública b i la funció pública PosaA.

Declarem la classe Fill amb herència de tipus private respecte a la classe Pare.

Així tenim que tots els elements de la classe Pare són elements privats de la classe filla. és a dir que a, b i PosaA són elements privats de la classe Fill.
void Fill::PosaDades(int n1,int n2, int n3)
{
    PosaA(n1);
    b=n2;
    c=n3;
    //a=n1; Errada
}
Les funcions de la classe filla tenen accés als elements públics de la classe Pare quan l'accés de la classe filla és private.

Però no tenen accés als elements privats de la classe Pare.

Així la funció PosaDades de la classe Fill té accés a la funció PosaA i a la dada a de la classe Pare.

La funció PosaDades no té accés a la dada b de la classe Pare.
main()
{
    Fill Obj;

    // Obj.PosaA(23);    Errada
    // Obj.b=23;         Errada

    PosaDades(1,2,3);
}
Ara declarem l'objecte obj de la classe Fill que té accés privat a la classe Pare.

Aquest objecte no té accés a cap dada de la classe Pare.

5.2.3 L'especificador d'accés Protected

En una classe podem declarar una secció de dades i funcions membre de tipus protected. En aquest cas els elements declarats com a protecteds són privats per a la la classe però seran visibles per a les funcions membre de les classes filles que estiguin declarades d'accés public a la classe pare.

Els objectes de les classes filles no tenen accés als membres protected de la classe pare.

L'especificador d'accés Protected
#include <iostream.h>

class Pare
{
private:
    int a;
protected:
    int b;
public:
    int c;
    void PosaDades(int n1,int n2,int n3)
    {
        a=n1;b=n2;c=n3;
    }
    int TornaA(){return a;}
    int TornaB(){return b;}
    int TornaC(){return c;}
};

main()
{
    Pare Objecte1;
    Objecte1.PosaDades(34,23,12);
    Objecte1.a = 888; //No vàlid ja que a ès private
    Objecte1.b = 888; //No vàlid ja que b és protected
    Objecte1.c = 888; //Vàlid ja que c ès públic
}
Els objectes d'una classe no tenen accés als elements declarats com a protected.

Així l'objecte Objecte1 no té accés a la dada b.
class Fill : public Pare
{
    int d;
public:
    void PosaD(int n){d=n;}
    int TornaD(){return d;}
};

main()
{
    Fill Objecte2;
    
    //Valid: public + public = public
    Objecte2.PosaDades(34,23,12); 
    
    //No vàlid: private + public = private
    Objecte2.a = 888; 
    
    //No vàlid: protected + public  = protected
    Objecte2.b = 888; 
    
    //Vàlid: public + public     = public
    Objecte2.c = 888; 
}
Els objectes de la classe heretera tipus public tenen accés als elements protected de la classe pare.

Així l'objecte Objecte2 té accés a la dada b.
class Fill : public Pare
{
    int d;
public:
    void PosaD(int n){d=n;}
    int TornaD(){return d;}
    void CanviaDadesPare()
    {
        a=10; //No vàlid. private de la classe Pare
        b=10; //Vàlid. protected de la classe Pare
        c=10; //Vàlid. public de la classe Pare
    }
};
Les funcions membre de la classe filla tenen accés als elements protected de la classe pare

Així la funció membre CanviaDadesPare de la classe Fill té accés a la dada b de la classe Pare.


5.2.4 Resum d'acsés

  
Element de la classe pare
   Public          Private         Protected      
Accés de la classe
filla a la classe pare.
Public Public Private Protected
Private Private Private Private
Protected Protected Private Protected
  
Elements de la classe filla

5.3. Constructors i herència.

Les classes tenen funcions constructores i funcions destructores. Es plantegen doncs una sèrie de problemes quan parlem de classes que s'hereten unes a altres com quins constructors s'executen abans, els de la classe Pare o el de la classe Fill, es poden passar o no paràmetres d'uns constructors a altres etc.

5.3.1. Ordre d'execució dels constructors i dels destructors.

Una classe Pare té un constructor i una classe Fill té un altre constructor. Quan declarem un element de la classe Fill primer s'executa el constructor de la classe Pare i després el de la classe Fill.

Aquest que ordre és l'ordre lògic ja que les funcions de la classe Fill han de tenir accés a les dades de la classe Pare, en particular un constructor de la classe Fill pot necessitar les dades de la classe Pare per construir-se ell mateix. també hem de pensar que la classe Pare no necessita per a res la classe Fill ja que és totalment independent d'aquesta classe.

L'ordre en que s'executen els destructors és el contrari. Primer s'executa el destructor de la classe Fill i després el destructor de la classe Pare.

També aquest és l'ordre lògic per a l'execució dels destructors ja que si executem el destructor de la classe Pare abans hem d'haver destruït les dades subordinades de la classe Fill.

Ordre d'execució dels constructors i destructors
#include <iostream.h>

class Pare
{
    int a;
public:
    Pare(int a=0)
    {
        this->a = a;
        cout << "Executat el constructor de la classe Pare\n";
    }
    ~Pare()
    {
        cout << "Executat el destructor de la classe Pare\n";
    }

};

class Fill : public Pare
{
    int b;
public:
    Fill(int b=0)
    {
        this->b = b;
        cout << "Executat el constructor de la classe Fill\n";
    }
    ~Fill()
    {
        cout << "Executat el destructor de la classe Fill\n";
    }

};

main()
{
    Fill Objecte1;
}
Sortida per pantalla

5.3.2. Pas de paràmetres entre constructors.

Tenim una classe Pare amb un constructor que ha de rebre paràmetres i una classe Fill que també ha de rebre paràmetres.

Així si la classe Pare té un constructor com el següent

Pare(int x, int y)

la classe Fill ha de tenir el constructor

Fill(int x, int y, int z) : Pare(x, y)

de manera que és el constructor de la classe Fill el que s'encarrega de passar les dades al constructor de la classe Pare.

Hem de pensar que quan es declara un element de la classe Fill primer es crida al seu constructor, però que abans d'executar-se es crida el constructor de la classe Pare i s'executa aquest en primer lloc amb les dades que rep del constructor de la classe Fill.

Pas de paràmetres entre constructors.
#include <iostream.h>

class Pare
{
    int x,y;
public:
    Pare(int x, int y)
    {
        this->x=x;
        this->y=y;
    }
    void PImprimeix()
    {
        cout << "x=" << x << ", y=" << y << "\n";
    }
};

class Fill : public Pare
{
    int z;
public:
    Fill(int x, int y, int z) : Pare(x, y)
    {
        this->z = z;
    }
    void  FImprimeix()
    {
        PImprimeix();
        cout << "z= " << z << "\n";
    }
};

main()
{
    Fill Objecte1(2,5,9);
    Objecte1.FImprimeix();
}

5.4 Herència múltiple.

L'herència entre classes no es limita a una classe que hereta una altra. Una classe pot heretar i ser heretada per diverses classes.

De fet quan es parla d'una llibreria de classes normalment està composada per un o més arbres de classes. Cadascun dels arbres consta de classes hereteres unes d'altres.

En C++ existeixen dues maneres de concebre aquest tipus d'herència:

5.4.1 Classes derivades de classes derivades.

Com veiem a l'exemple següent tenim una classe que anomenem PuntRecta que té una dada x. D'aquesta classe derivem la classe PuntPla que té altra dada anomenada y.

Després tenim que de la classe PuntPla es deriva la classe PuntEspai que afegeix una nova dada que és la dada z.

Si ho penses bé tenim tres objectes de tipus Punt, un sobre la recta A(x), un altre sobre el pla A(x, y) i un altre sobre l'espai A(x, y, z) i el que fem és fer que del PuntRecta es deriva el PuntPla i d'aquest el PuntEspai.

Es a dir que el PuntEspai hereta les dades i funcions membre de les dues classes PuntRecta i PuntPla.

Respecte als constructors fem notar que l'ordre en que es criden és PuntEspai, PuntPla, PuntRecta encara que l'execució d'aquests constructors és el contrari, PuntRecta PuntPla, PuntEspai.

També veiem a l'exemple, que si fem constructors amb paràmetres aquests paràmetres es passen d'un constructor a altre fins que arriben al seu constructor.

Els destructors s'executen en el sentit contrari que els constructors. Si haguéssim escrit destructors en el nostre exemple l'ordre d'execució hauria estat PuntEspai, PuntPla, PuntRecta.

Classe derivada de classe derivada.
#include <iostream.h>

class PuntRecta
{
    double x;
public:
    PuntRecta(double x=0) {this->x=x;}
    void AssignaPuntRecta(double x) {this->x=x;}
    double TornaX() {return x;}
};

class PuntPla : public PuntRecta
{
    double y;
public:
    PuntPla(double x=0, double y=0) : PuntRecta(x)
    {
        this->y=y;
    }
    void AssignaPla(double x, double y)
    {
        AssignaPuntRecta(x);
        this->y=y;
    }
    double TornaY() {return y;}
};

class PuntEspai : public PuntPla
{
    double z;
public:
    PuntEspai(double x=0, double y=0, double z =0) : PuntPla(x, y)
    {
        this->z=z;
    }
    void AssignaEspai(double x, double y, double z)
    {
        AssignaPla(x, y);
        this->z=z;
    }
    double TornaZ() {return z;}
};

main()
{
    PuntEspai A(4,6,8);
    cout << "(" << A.TornaX() << "," << A.TornaY() << "," << A.TornaZ() << ")";
}

Possiblement hagis pensat que el gràfic de la derivació de classes està mal fet, però no és així. Sorprèn una mica el sentit de les fletxes, però aquest gràfic ha de llegir-se segons la frase següent:

La classe PuntEspai es deriva de PuntPla que es deriva de PuntRecta.

D'aquesta manera si que té sentit la direcció de les fletxes. Tots els gràfics de derivació de classes que porten fletxes es fan d'aquesta manera.

5.4.2 Classe derivada de més d'una classe.

Una classe pot derivar directament de més d'una classe segons veiem a l'exemple següent.

Tenim dues classes Xoco i Llet que tenen com a dada el pes de xocolata i el volum de llet respectivament.

La classe XocoLlet es deriva de les dues anteriors i hereta les dues dades (Pes de xocolata i volum de llet) i les seves funcions membre.

Classe derivada de dues classes.
#include <iostream.h>

class Xoco
{
    float Pes;
public:
    Xoco(float Pes=0){this->Pes = Pes;}
    float TornaPes(){return Pes;}
};

class Llet
{
    float Volum;
public:
    Llet(float Volum=0){this->Volum = Volum;}
    float TornaVolum(){return Volum;}
};

class XocoLlet : public Xoco, public Llet
{
public:
    XocoLlet(float Pes=0, float Volum=0) : Xoco(Pes), Llet(Volum){};
};

main()
{
    XocoLlet Got1(200,250);
    cout << "Got1. Xoco pes: "<<  Got1.TornaPes() << ", Llet volum; " << Got1.TornaVolum();
}

5.4.3 Classes base virtuals.

Quan declarem un objecte que es deriva de més d'una classe es reserva memòria per a les dades del la classe i les de totes les classes antecessores.

Si ens fixem en la figura veiem que la classe Derivada3 hereta dues vegades a la classe Base, una pel camí de la classe Derivada1 i altra pel camí de la classe Derivada2.

Així quan declarem un objecte de la classe Derivada3 tindrem espai reservat per a les dades de la classe Base dos cops en el nostre objecte de la classe Derivada3.

De fet el llenguatge C++ fa aquesta duplicació i quan intentem accedir a aquestes dades dona error d'ambigüitat ja que les té duplicades i no sap quines ha d'utilitzar.

Per evitar això hem de fer que l'herència de les classes Derivada1 i Derivada2 sigui virtual respecte a la classe Base segons la notació

class Derivada1 : virtual public Base
class Derivada2 : virtual public Base

Així evitarem que es dupliquin les dades dins la classe Derivada3.

A l'exemple següent fem la classe Producte que té les classes derivades Xoco i Llet declarades com virtuals. D'aquestes dues classes derivem la classe XocoLlet que per tant també es deriva de la classe Producte.

Cal indicar que ara en la crida al constructor de la classe XocoLlet incloem la crida als constructors de les tres classes de la que es derivada aquesta de XocoLlet incloent el de la classe Producte encara que XocoLlet no es deriva directament de Producte.

Classes base virtuals.
#include <iostream.h>
#include <string.h>

class Producte
{
    char Tipus[45];
public:
    Producte(char * T){strcpy(Tipus,T);}
    char * TornaTipus(){return Tipus;}
};

class Xoco : virtual public Producte
{
    float Pes;
public:
    Xoco(float Pes=0) : Producte("Comestible")
    {this->Pes = Pes;}
    float TornaPes(){return Pes;}
};

class Llet : virtual public Producte
{
    float Volum;
public:
    Llet(float Volum=0) : Producte("Comestible")
    {this->Volum = Volum;}
    float TornaVolum(){return Volum;}
};

class XocoLlet : public Xoco, public Llet
{
public:
    XocoLlet(float Pes=0, float Volum=0) : Xoco(Pes),
                                           Llet(Volum),
                                           Producte("Comestible")     
                                           {};
};

main()
{
    XocoLlet Got1(200,250);
    cout << Got1.TornaTipus();
}


5.5 Punters a classes base i classes derivades.

Els punters a classes que tenen classes derivades són especials i diferents a tot la resta de punters.

Podem fer que un punter de una classe base tingui l'adreça d'un objecte d'una classe derivada seva.

La classe derivada hereta unes dades i funcions membre de la classe base. El punter que hem parlat només té accés a aquest elements, els heretats de la classe base. No té accés als elements propis de la classe derivada.

Això té un sentit i és que el punter de la classe base no té coneixement de l'existència de la classe derivada.

En sentit invers, no es pot assignar a un punter de la classe derivada l'adreça de memòria d'un objecte de la classe base.

També hem de tenir cura amb l'aritmètica de punters, ja que, un increment d'un punter de la classe base i un de la classe derivada fan un salt d'un número diferent de bytes, menys el cas que la classe derivada no afegeixi dades a les de la classe pare.

Si fem un increment d'un punter a classe base no apuntarà al següent objecte de la classe derivada sinó al que hauria de ser següent objecte de la classe base.

Punters a classes base i classes derivades.
#include <iostream.h>

class Base
{
    int a;
public:
    void PosaA(int n) {a=n;}
    int TornaA(){return a;}
};

class Derivada : public Base
{
    int b;
public:
    void PosaB(int n) {b=n;}
    int TornaB(){return b;}
};

main()
{
    Derivada ObjecteDerivada;
    ObjecteDerivada.PosaA(10);
    ObjecteDerivada.PosaB(34);

    //Assignació de ll'adreça d'un objecte
    //derivat a un punter a la classe base
    Base * pObjecteBase;
    pObjecteBase = &ObjecteDerivada;
    pObjecteBase->PosaA(75);
    cout << "a=" << pObjecteBase->TornaA() << "\n";

    pObjecteBase->PosaB(95); //Errada
}

5.6 Funcions virtuals.

Una funció membre d'una classe base pot definir-se també en la classe derivada.

Així quan cridem aquesta funció amb un objecte de la classe base s'executa la funció de la classe base i quan la cridem amb un objecte de la classe derivada s'executa la de la classe derivada.

Funcions definides en la classe base i en la derivada.
#include <iostream.h>

class Base
{
    double x;
public:

    Base(double x) {this->x=x;}
    void Imprimeix()
    {
        cout << "x= " << x << endl;
    }
    double TornaX(){return x;}
};

class Derivada : public Base
{
    double y;
public:
    Derivada(double x, double y) : Base(x)
    {this->y=y;};
    void Imprimeix()
    {
        cout << "x=" << TornaX() << ", y=" << y << endl;
    }
};

main()
{
    Base P(6);
    Derivada Q(2,6);

    P.Imprimeix();
    Q.Imprimeix();
}
La funció void Imprimeix() està definida en la classe Base i en la classe Derivada.

Així tenim que amb les intruccions

P.Imprimeix();
Q.Imprimeix();


la funció que s'executa depen del tipus d'objecte que la crida, es a dir depen del tipus de P i del tipus de Q.

Hem de fer notar que el compilador sap, en el moment de compilar quina funció Imprimeix() ha d'utilitzar ja que és l'objecte P o l'Objecte Q els que estan escrits a aquestes instruccions.


Les funcions virtuals constitueixen el mecanisme pel qual es poden fer tries de funcions membre en el moment d'execució del programa i no en el moment de compilació.

Podem fer esment a l'exemple anterior, que es diu "lligadura primerenca". El compilador sap quin objecte executarà una funció i pot triar la funció en el moment de la compilació.

Al contrari, "lligadura dinàmica" significa que fins que no s'executi el programa no se sap quin objecte és el que executarà una certa funció, i és per tant, en el moment d'execució quan es tria la funció a executar.

Mecanisme virtual:

Les funcions virtuals són el mecanisme que permet en C++ fer la lligadura dinàmica.
Funcions virtuals
#include <iostream.h>

class Original
{
public:
    double x;
    Original(double x) {this->x=x;}
    virtual void Mostra()
    {
        cout << "Fent ús de Mostra() de la classe original ";
        cout << x << endl;
    }
};

class Derivada1 : public Original
{
public:
    Derivada1(double x) : Original(x){};
    void Mostra()
    {
        cout << "Fent ús de Mostra() de la classe Drivada1 ";
        cout << 2*x << endl;
    }
};

class Derivada2 : public Original
{
public:
    Derivada2(double x) : Original(x){};
    void Mostra()
    {
        cout << "Fent ús de Mostra() de la classe Drivada2 ";
        cout << x*x << endl;
    }
};

main()
{
    int Opcio;
    Original *pOb;

    Original Ob(10);
    Derivada1 Ob1(10);
    Derivada2 Ob2(10);

    cout << "Tria (1 Original//2 Derivada1//3 Derivada2): ";
    cin >> Opcio;

    if (Opcio==1) pOb = &Ob;
    if (Opcio==2) pOb = &Ob1;
    if (Opcio==3) pOb = &Ob2;

    pOb->Mostra();
}
En la classe original origina veiem que la funció Mostra es declara com a virtual i que també l'hem definida.

En les dues classes derivades també està declarada i definida.

El prototip de les tres funcions és el mateix.

En la funció main veiem que declarem tres objectes de cada classe i u punter a objecte de la classe base Original.

Després de construir els tres objectes demanem a l'usuari quin vol mostrar i és en aquest moment quan assignem a al punter pOb un dels objectes.

Així que la instrucció pOb->Mostra() executa una funció segons el que triï l'usuari. (Moment d'execució, lligadura dinàmica)

Això és polimorfisme en temps d'execució.

5.7 Funcions virtuals pures. Classes abstractes.

Una funció virtual pura és una funció que es declara en una classe base i no es defineix en aquesta classe base.

Les funcions virtuals pures es declaren en la firma següent:

virtual Tipus Funció(Paràmetres) = 0;

Allò que li confereix el caràcter de virtual pura és el fer la funció igual a zero. Li diu al compilador que aquesta funció no es defineix en la classe base.

Les funcions virtuals pures es defineixen obligatòriament en les classes derivades. Si no es defineix en les classes derivades dona un error de compilació.

Una classe que té una funció virtual pura és una classe abstracta.

Ja que una classe abstracta té al menys una funció sense definir aquestes classes són classes incompletes. Per tant no es poden declarar objectes d'aquest tipus. Si que es poden declarar punters ja que ha de permitir-nos fer ús del polimorfisme en temps d'execució, que com ja sabem s'aconsegueix a través de punters a la classe base.

Funcions virtuals pures. Classes abstractes.
#include <iostream.h>

class BaseAltura
{
    double Base;
    double Altura;
public:
    void PosaDimensions(double Base, double Altura)
    {
        this->Base=Base;
        this->Altura=Altura;
    }

    void DonamDimensions(double &b, double &h)
    {
        b=Base;
        h=Altura;
    }

    virtual double TornaArea() = 0;
};

class Rectangle : public BaseAltura
{
public:
    double TornaArea()
    {
        double Base, Altura;
        DonamDimensions(Base,Altura);
        return Base*Altura;
    }
};

class Triangle : public BaseAltura
{
public:
    double TornaArea()
    {
        double Base, Altura;
        DonamDimensions(Base,Altura);
        return Base*Altura/2;
    }
};


main()
{
    BaseAltura *pBA;
	
    Rectangle R;
    R.PosaDimensions(4,5);
    Triangle T;
    T.PosaDimensions(10,10);

    pBA = &R;
    cout << "El rectangle té àrea " << pBA->TornaArea() << endl;
    pBA = &T;
    cout << "El triangle té àrea " << pBA->TornaArea() << endl;
}
La classe BaseAltura és una classe que serveix de base per a dues classes Triangle i Rectangle.

Aquesta classe BaseAltura té una funció virtual pura, la funció:

virtual double TornaArea() = 0;

que ha de definir-se en les classes derivades.

Aixó fa que la classe BaseAltura sigui una classe abstracta que serveix en aquest cas per proveir a les classes derivades de les dades Base i Altura i els mètodes d'assignació i extracció de dades.

Més important és la declaració de la funció TornaArea() com a virtual que proveeix a les classes derivades d'un prototipat obligatori així com la obligació de definir-la en les classes derivades.

5.8 Exercicis

  1. Tenim un fitxer punt.h amb la classe següent:

    Classe Punt
    #include <iostream.h>
    
    class Punt
    {
        double  x,y;
      public:
        Punt(double n1=0.0, double n2=0.0)
        {
            x = n1;
            y = n2;
        }
        void Imprimeix()
        {
            cout << "(" << x << "," << y << ")";
        }
        double TornaX() {return x;}
        double TornaY() {return y;}
    };
    

    1. Escriu una classe derivada de Punt que es digui PuntDerivada que tingui una única funció que ens torni la distància del punt a l'origen de coordenades. (Arrel quadrada de x*x + y*y).
    2. Repeteix l'exercici però declarant les dades x i y com a dades protegides de la classe Punt i no com a dades privades.
    3. Crea un constructor per a la classe PuntDerivada.


  2. Escriu una classe derivada de la classe Puntb anterior que es digui PuntColor amb les següents condicions:
    1. Té una nova dada que és un enter per posar el color.
    2. Té un constructor que passa les dades fins a la classe Pare.
    3. Té la funció ImprimeixDades que posa les tres dades a la pantalla.
    4. Fes un objecte d'aquest tipus dins la funció main i fes que utilitzi totes les funcions que pugui de les tres classes a les que té accés.


  3. Una classe Vehicle té les dades i funcions membre següents:

    class vehicle
    class Vehicle
    {
        char Tipus[50];
        int nRodes;
    public:
        Posa(char * T=NULL, int nRodes=0);
        TornaTipus(char *T);
        int TornaRodes();
    }
    


    1. Fes una classe derivada publica que es digui VehicleTerrestre que tingui la dada char TipusT[45] i que tingui els mètodes per posar i obtenir dades.
    2. Crea un objecte dins la funció main i fes que tingui dades i que ens apareguin per pantalla.
    3. Modifica l'accés de la classe a private i descriu que és passa amb el teu programa.
    4. Torna a deixar l'accés de la classe com a públic.
    5. Ara canvia de secció les dades de la classe vehicle i posa-les a la secció pública. Que modificacions pots fer en la classe derivada?
  4. Afegeix a la classe Vehicle un constructor amb pas de paràmetres i valors d'aquests paràmetres per defecte.

    Fes el mateix amb la classe VehicleTerrestre. Ten cura de passar les dades a la classe Pare.
  5. Crea altra classe derivada que es digui VehicleMaritim. Ha de tenir com a dada el TipusM i l'eslora.

    També ha de tenir un constructor amb pas de paràmetres i les funcions adients per posar i obtenir dades. Ten cura de que el constructor passi les dades adients al constructor de la classe base.
  6. Classes base virtuals:

    1. Ara heu de fer la classe VehicleAmfibi que ha de heretar de les dues classes anteriors.
    2. Quina modificació has de fer a les classes base per a que no es donin errades d'ambigüitat?
    3. Fes el constructor de la classe VehicleAmfibi adient per tal d'inicialitzar totes les dades de les classes antecessores.
  7. Funcions virtuals:

    1. Fes una funció virtual anomenada ImprimeixDades en la classe Vehicle i fes la redefinició d'aquesta funció en totes les classes derivades.
    2. Comprova amb un exemple que aquestes funcions estan ben definides amb quatre objectes, un de cada classe.
    3. Fes un punter al l'objecte de la classe arrel (Vehicle) i fes que l'usuari ens digui quin dels quatre objectes vol imprimir, i, amb una solo instrucció d'impressió de dades, el programa en les dona per pantalla.
    4. En quin apartat has fet ús en algun moment del polimorfisme en temps d'execució? En el a) o en el b).
  8. Funcions virtuals pures:

    1. Explica que és una funció virtual pura i una classe abstracta.
    2. Executa l'exercici de l'apartat 5.7.
    3. Comprova que passa cas que no defineixes alguna funció virtual dins una classe derivada.
    4. Que passarà si no defineixes la funció virtual pura dins una classe derivada d'una classe derivada?.