Choisissez votre style : colorisé, impression

Série 20 (niveau 0)  :
Héritage

Exercice 0 : exemple simple (héritage, niveau 0)

Introduction

Le but de cet exercice est de reprendre l'exemple du cours illustrant la notion d'héritage en utilisant une hiérarchie de figures géométriques.

Dans le fichier figures.cc, commencez par recopier les définitions des classe Rectangle et Cercle ci-dessous :

#include <cmath>
using namespace std;

// ----------------------------------------------------------------------
class Rectangle
{
private:
    double largeur;
    double longueur;
public:
    double surface () const { return largeur * longueur; }
    double getLongueur() const { return longueur; }
    double getLargeur()  const { return largeur;  }
    void setLargeur(double l)  { largeur  = l; }
    void setLongueur(double l) { longueur = l; }
};


// ----------------------------------------------------------------------
class Cercle
{
public:
    double surface() const { return M_PI * rayon * rayon; }
    bool estInterieur(const double x, const double y) const
    {
        return (((x-this->x) * (x-this->x) +
                (y-this->y) * (y-this->y))
                <= rayon * rayon);
    }
    void getCentre(double &x, double &y) const
    {
        x = this->x;
        y = this->y;
    }
    void setCentre(const double x, const double y)
    {
        this->x = x;
        this->y = y;
    }
    double getRayon() const { return rayon; }
    void setRayon(double r)
    {
        if (r < 0.0)
            r = 0.0;
        rayon = r;
    }

private:
    double rayon;
    double x;  // abscisse du centre
    double y;  // ordonnée du centre
};

Notez tout de même que normalement en programmation, on évite de faire cela (copier-coller) et on préfèrerait utiliser la compilation séparée (faire plusieurs fichiers) !
Mais bon, ce n'est pas le sujet du jour...

Commençons par ajouter la classe « rectangle coloré » qui hérite de rectangle.
Cette classe doit simplement avoir un attribut de plus, couleur, disons de type unsigned int.

[Essayez de le faire par vous même avant de regarder la solution qui suit]

Ceci se fait très simplement :

  1. la classe RectangleColore hérite de Rectangle :
    class RectangleColore : Rectangle
    {
    };
    

    On préfèrera l'héritage « public », beaucoup plus "naturel" (et justifié ici) que l'héritage « private » par défaut :

    class RectangleColore : public Rectangle
    {
    };
    
  2. et a un attribut de plus :
    class RectangleColore : public Rectangle
    {
    protected:
        unsigned int couleur; 
    };
    
    
  3. mais il ne faut pas oublier de récupérer les attributs et méthodes que l'on souhaite hériter de Rectangle. Il faut pour cela changer leur statut de private à protected :
    class Rectangle
    {
    protected:
        double largeur;
        double longueur;
    public:
        // ... comme avant
    };
    

Pour rendre ces classes un peu plus réalistes (et utilisables) ajoutons leur à chacune un constructeur:

class Rectangle
{
protected:
    double largeur;
    double longueur;
public:
    Rectangle(double larg, double L)
     : largeur(larg), longueur(L) {}
    // ... comme avant
    ...

class RectangleColore : public Rectangle
{
protected:
    unsigned int couleur;
public:
    RectangleColore(double larg, double L, unsigned int c)
     : Rectangle(larg, L), couleur(c) {}
};

Testez avec ce main simpliste :

int main()
{
    RectangleColore r(4.3, 12.5, 4);
    cout << r.getLargeur() << endl;
    return 0;
}

Continuez en définissant les classes suivantes :

[Essayez de le faire par vous même avant de regarder la solution qui suit]

Pour la classe Figure, rien de particulier, et il suffit de déplacer les définitions correspondantes de la classe Cercle à la classe Figure (donc les supprimer de Cercle) :

class Figure
{
protected:
    double x;  // abscisse du centre
    double y;  // ordonnée du centre

public:
    void getCentre(double &x, double &y) const
    {
        x = this->x;
        y = this->y;
    }
    void setCentre(const double x, const double y)
    {
        this->x = x;
        this->y = y;
    }
};

puis on ajoute la méthode affiche et le constructeur (rien de spécial ici) :

class Figure
{
protected:
    double x;  // abscisse du centre
    double y;  // ordonnée du centre

public:
    Figure(double x =0.0, double y =0.0) { this->x = x; this->y=y; }
    void affiche(ostream& sortie)
    {
        sortie << "centre = (" << x << ", " << y << ")";
    }
    //... suite comme avant
};

Venons en à l'héritage proprement dit. Il suffit simplement d'ajouter la « marque » de l'héritage aux deux classes concernées.

class Rectangle : public Figure
{
    ...
};
class Cercle : public Figure
{
    ....
};

On peut maintenant tester que l'on hérite bien des propriétés de la classe Figure :

int main()
{
    RectangleColore r(4.3, 12.5, 4);
    cout << r.getLargeur() << endl;
    r.affiche(cout);
    cout << endl;

    Cercle c;
    c.setCentre(2.3, 4.5);
    c.setRayon(12.2);
    c.affiche(cout);
    cout << endl;
    return 0;
}

Si vous êtes perdus, voici ici le code complet à ce stade.

[Testez ce programme : compilez le, exécutez le. Essayez ensuite d'autres utilisations pour bien comprendre. ]

Évidemment pour être plus intéressantes, il faudrait répercuter les possibilités de la classe Figure au niveau des constructeurs des classes Rectangle et Cercle, et éventuellement enrichir la méthode affiche.

Pour les constructeurs c'est assez facile :

class Rectangle : public Figure
{
    ...
    Rectangle(double larg, double L, double x, double u) 
      : Figure(x,y), largeur(larg), longueur(L) {};
    ...
};

class Cercle : public Figure
{
public:
    Cercle(double rayon, double x = 0.0, double y = 0.0)
      : Figure(x,y), rayon(rayon) {}
    ...
};

Pour la méthode affiche c'est un peu plus subtile. Supposons que l'on souhaite que pour la classe Cercle la méthode affiche également le rayon (mais continue d'afficher le centre).

On veut donc faire quelque chose comme (syntaxe fantaisiste) :
« cercle.affiche() = figure.affiche() + { cout << rayon } »

Cela se fait exactement de cette façon mais en utilisant la syntaxe du C++ et plus exactement l'opérateur de résolution de portée :

void Cercle::affiche(ostream& sortie)
{
    Figure::affiche(sortie);
    sortie << ", r=" << rayon;
}

et ne pas oublier de définir la méthode dans la classe Cercle :
class Cercle : public Figure
{
public:
    Cercle(double rayon, double x = 0.0, double y = 0.0)
     : Figure(x,y), rayon(rayon) {}
    void affiche(ostream&);
    ...
};

On peut bien sûr faire de même avec la classe Rectangle :

void Rectangle::affiche(ostream& sortie)
{
    Figure::affiche(sortie);
    sortie << ", largeur=" << largeur 
           << ", longueur=" << longueur;
}
Testez le programme avec le main suivant :

int main()
{
    RectangleColore r(4.3, 12.5, 4);
    cout << r.getLargeur() << endl;
    r.affiche(cout);
    cout << endl;

    Cercle c(12.2, 2.3, 4.5);
    c.affiche(cout);
    cout << endl;

    Rectangle r2(1.2, 3.4, 12.3, 43.2);
    r2.affiche(cout);
    cout << endl;

    return 0;
}

[ici le code à ce stade]

Vous pouvez terminer l'exercice en ajoutant les classes Rectangle3D et Cylindre et les différentes versions de la méthode surface.

Cela se fait exactement comme la méthode affiche ci-dessus.


Dernière mise à jour : $Date: 2022/03/22 20:56:00 $ ($Revision: 1.7 $)