Choisissez votre style : colorisé, impression

Correction 20
Héritage


Les solutions suivantes ne sont évidemment pas uniques. Si vous en trouvez de meilleures (ou si vous trouvez des erreurs), faites-le savoir ! Merci.
Par ailleurs, les solutions proposées correspondent à l'état de vos connaissances au moment où la série est abordée. D'autres solutions pourront être envisagées une fois de nouveaux concepts acquis.
Exercice 1 Exercice 2 Exercice 3 Exercice 4

Exercice 1 : Véhicules (niveau 1)

1.1 La classe Vehicule

Définissez une classe Vehicule qui a pour attributs des informations valables pour tout type de véhicule : sa marque ; sa date d'achat ; son prix d'achat ; et son prix courant.

Solution :

class Vehicule
{
protected:
    string marque;
    unsigned int date_achat;
    double prix_achat;
    double prix_courant;
};
Ces attributs sont «protected» car on ne souhaite pas pouvoir y accéder en dehors de la classe, mais on veut tout de même pouvoir en hériter.

Définissez un constructeur prenant en paramètre la marque, la date d'achat et le prix d'achat .

Une solution possible (une autre dans le fichier complet à la fin) :

class Vehicule
{
public:
    Vehicule(string marque, unsigned int date, double prix)
     : marque(marque), date_achat(date), prix_achat(prix), prix_courant(prix)
    {}

protected:
    string marque;
    unsigned int date_achat;
    double prix_achat;
    double prix_courant;
};
Définissez une méthode publique affiche qui affiche la valeur des attributs.
class Vehicule
{
public:
    Vehicule(string marque, unsigned int date, double prix)
     : marque(marque), date_achat(date), prix_achat(prix), prix_courant(prix)
    {}

    void affiche(ostream& affichage) const;

protected:
    string marque;
    unsigned int date_achat;
    double prix_achat;
    double prix_courant;
};

void Vehicule::affiche(ostream& affichage) const
{
    affichage << "marque : " << marque
              << ", date d'achat : " << date_achat
              << ", prix d'achat : " << prix_achat
              << ", prix actuel : "  << prix_courant
              << endl;
}

Cette méthode est const car elle ne modifie pas l'état de l'objet.

Note : On pourrait aussi ici surcharger l'opérateur externe ostream& operator<<(ostream&, const Vehicule&).

1.2 La classe Voiture et la classe Avion

Définissez deux classes Voiture etAvion, héritant de la classe Vehicule et ayant les attributs supplémentaires suivants : [...]

Commençons par la classe Voiture. Elle doit hériter de la classe Véhicule :

class Voiture : public Vehicule
{
}; 

Nous faisons cet héritage de façon publique (comme dans 90% des cas) car on ne souhaite pas cacher le fait que Voiture est un Vehicule (pensez par exemple à une autre classe Limousine qui hériterait de Voiture, on voudrait que cette classe offre également à ses utilisateurs la possibilités d'utiliser les éléments (attributs/méthodes) publics de Vehicule [éléments qui seront ajoutés dans la suite de l'exercice]).

On ajoute ensuite les champs spécifiques à la classe Voiture :

class Voiture : public Vehicule
{
protected:
    double cylindree;
    unsigned int nb_portes;
    double puissance;
    double kilometrage;
};

Pour la classe Avion, on procède de même :

class Avion : public Vehicule
{
protected:
    Type_Avion moteur;
    unsigned int heures_vol;
};
Définissez un constructeur, ainsi que méthode affichant la valeur des attributs. Ces deux méthodes doivent bien entendu être publiques puisqu'elles sont précisément faite pour être utilisée hors de la classe.
class Voiture : public Vehicule
{
public:
    Voiture(string marque, unsigned int date, double prix,
            double cylindree, unsigned int portes, double cv, double km);
    void affiche(ostream&) const;

protected:
    double cylindree;
    unsigned int nb_portes;
    double puissance;
    double kilometrage;
};

Ces deux méthodes doivent bien entendu être publiques puisqu'elles sont précisément faites pour être utilisées hors de la classe.

On pourrait tout aussi bien déclarer affiche sans lui passer de paramètre :
void affiche() const;
et la faire directement opérer sur cout.

La définition de ces deux méthodes ne pose aucune difficulté. Voici un exemple possible :

Voiture::Voiture(string marque, unsigned int date, double prix,
                 double cylindree, unsigned int portes, double cv,
                 double km) 
 : Vehicule(marque, date, prix)
{
    this->cylindree = cylindree;
    nb_portes = portes;
    puissance = cv;
    kilometrage = km;
}

void Voiture::affiche(ostream& affichage) const
{
    affichage << " ---- Voiture ----" << endl;
    Vehicule::affiche(affichage);
    affichage << cylindree << " litres, "
              << nb_portes << " portes, "
              << puissance << " CV, "
              << kilometrage << " km." << endl;
}
Notez que pour le constructeur de Voiture, on fait appel au constructeur de Vehicule :
Voiture::Voiture(string marque, unsigned int date, double prix,
                 double cylindree, unsigned int portes, double cv,
                 double km) 
: Vehicule(marque, date, prix)
{
    ...
Les méthodes de la classe Avion s'implémentent de même :
class Avion : public Vehicule
{
public:
    Avion(string marque, unsigned int date, double prix,
          Type_Avion moteur, unsigned int heures);
    void affiche(ostream&) const;

protected:
    Type_Avion moteur;
    unsigned int heures_vol;
};

Avion::Avion(string marque, unsigned int date, double prix,
             Type_Avion moteur, unsigned int heures)
 : Vehicule(marque, date, prix)
{
    this->moteur = moteur;
     heures_vol = heures;
}

void Avion::affiche(ostream& affichage) const
{
    affichage << " ---- Avion à ";
    if (moteur == HELICES)
        affichage << "hélices";
    else
        affichage << "réaction";
    affichage << " ----" << endl;
    Vehicule::affiche(affichage);
    affichage << heures_vol << " heures de vol." << endl;
}

Encore des méthodes

Ajoutez une méthode void calculePrix() dans la classe Vehicule [...]
Le prix doit rester positif (i.e., s'il est négatif, on le met à 0).
class Vehicule
{
public:
    Vehicule(string marque, unsigned int date, double prix);
    void affiche(ostream&) const;
    void calculePrix();
    ...
};
...
void Vehicule::calculePrix()
{
    double decote((2019 - date_achat) * .01);
    prix_courant = max(0.0, (1.0 - decote) * prix_achat); 
}
Redéfinissez cette méthode dans les deux sous-classesVoiture et Avion. [...]

Le prototype est le même pour Voiture et Avion que pour Vehicule, par contre les définitions diffèrent.

void Voiture::calculePrix()
{
    double decote((2019 - date_achat) * .02); 
    decote += 0.05 * kilometrage / 10000.0; 
    if (marque == "Fiat" || marque == "Renault") 
        decote += 0.1; 
    else if (marque == "Ferrari" || marque == "Porsche") 
        decote -= 0.2; 

    prix_courant = max(0.0, (1.0 - decote) * prix_achat); 
}

...

void Avion::calculePrix()
{
    double decote;
    if (moteur == HELICES)
         decote = 0.1 * heures_vol / 100.0;
    else
        decote = 0.1 * heures_vol / 1000.0;

    prix_courant = max(0.0, (1.0 - decote) * prix_achat); 
}
Pour finir, ceux qui le souhaitent peuvent trouver ici le code complet.

Exercice 2 : Vaccins (niveau 1)

Voici un codage possible de cet exercice  :

#include <string>
#include <vector>
#include<iostream>

using namespace std;

// prix du conditionnement d'une unité
const double COND_UNITE(0.5);

// prix de base de fabrication d'une unité
const double PRIX_BASE(1.5);

// majoration du prix de fabrication pour vaccin "high tech"
const double MAJORATION_HIGHTECH(0.5);

// reduction du cout du à la delocalisation
const double REDUCTION_DELOC(0.2);

enum Fabrication {Standard, HighTech};

/**************************************
 * Un classe pour représenter un vaccin
 *************************************/

class Vaccin{
public:
	
  Vaccin(string _nom, double _volume_dose, unsigned int _nb_doses,
		 Fabrication _fabrication = Standard)
    :nom(_nom), volume_dose(_volume_dose), nb_doses(_nb_doses), 
     mode_fabrication(_fabrication)
    {}

  ~Vaccin()
  {}
	// surcharge de l'opérateur << pour afficher les
	// données relatives à un vaccin
	friend ostream& operator<< (ostream& out, const Vaccin& v)
		{
			out << v.nom << endl;
			out << "volume/dose : " << v.volume_dose << endl;
			out << "nombre de doses: " << v.nb_doses << endl;
			out << "mode de fabrication ";
			if (v.mode_fabrication == HighTech)
				out << "haute technologie" << endl;
			else out << "standard" << endl;
			return out;
		}

	// méthode du calcul du cout de conditionnement
	double conditionnement() const{
	  return (volume_dose * nb_doses) * COND_UNITE;
  }
  
	// méthode du calcul du cout de fabrication

	double fabrication() const
		{
			double prix(volume_dose * nb_doses * PRIX_BASE);
			if (mode_fabrication == HighTech)
			{
				prix += prix * MAJORATION_HIGHTECH;

			}
			return prix;
		}
	
	// méthode du calcul du cout de production

	double production() const
		{
			
			return fabrication()+conditionnement();
		}

protected:
	// nom du vaccin
	string nom;
	// volume par dose de vaccin
	double volume_dose;
	// nombre de doses
	unsigned int nb_doses;
	// mode de fabrication du vaccin
	Fabrication mode_fabrication;
	
	
};

/*******************************************
 * Une classe pour représenter un vaccin
 * pouvant etre produit de façon délocalisée
 *******************************************/
class Delocalise: public Vaccin{
public:
  Delocalise(string _nom, double _volume_dose, 
		  unsigned int _nb_doses, Fabrication _fabrication,
		  bool  _frontalier)
    :Vaccin(_nom,_volume_dose , _nb_doses, _fabrication), 
     frontalier(_frontalier)  {}
	
  ~Delocalise()
  {}

	// masquage de la méthode héritée de Vaccin
	double production() const
		{
			double prix = Vaccin::production();
			if (frontalier)
			{
				prix -= prix * REDUCTION_DELOC;
			}
			else
			{
				prix /= 2;
			}
			return prix;
		}
	
private:
	// indique si la production est délocalisée
	// dans un pays frontalier ou non
	bool frontalier;
};

// ======================================================================
// un petit main pour tester tout ca
int main() {

	Vaccin v1("Zamiflu", 0.55, 200000, HighTech);
	Vaccin v2("Triphas", 0.20 , 10000);
	// affichage des vaccins à compléter ici
	cout << v1 << endl;
	cout << v2 << endl;

	cout << "le  cout deproduction de  v1 et v2 est : ";
	cout << v1.production() + v2.production() << endl;
	
     cout << "test des parties suivantes ..." << endl;

	Delocalise v3("Zamiflu", 0.55, 15000, HighTech, false);
	Delocalise v4("Triphas", 0.20, 15000, Standard, true);
	cout << "le  cout de production de  v3 et v4 est : ";
	cout << v3.production() + v4.production() << endl;
	return 0;
}

Exercice 3 : Héritage d'un vector (niveau 2)

Ici l'héritage privé impose quelques contraintes sur le codage de la surcharge d'opérateur. Voici une solution possible :

#include <iostream>
#include <vector>

using namespace std;


// un Porte monnaie est implémenté comme un vecteur d'entiers.
class PorteMonnaie : private vector <int> 
{
public:

	void gagner(int sous)
		{
			// un porte monnaie hérite de vecteur
			// il possède la méthode push_back par héritage
			// (idem pour toutes les autres méthodes héritées)
			push_back(sous);
		}

	void vider()
		{
			clear();
			
		}

	// cette méthode (ou un équivalent) doit etre définie pour permettre
	// à l'opérateur d'affichage d'avoir accès à la taille du vecteur
	// Utiliser directement le size hérité de vector n'est pas possible
	// à l'extérieur de la classe PorteMonnaie car l'héritage est privé.
        //
	// Le const doit etre mis. Autrement, l'opérateur d'affichage ne
	// pourrait pas avoir un object constant comme second argument.
 	int size() const
 		{
			// cette méthode réutilise la méthode masquée héritée de
			// vector
 			return vector<int>::size();
 		}
	
	// meme commentaire que pour la méthode size.
	// notez la syntaxe utilisée pour l'indexation
	int getI(int i) const
		{
			return (*this)[i];
		}
};

//OPERATEUR D'AFFICHAGE
ostream& operator<<(ostream& out, const PorteMonnaie& p)
{
	int total(0); 
 	for (int i(0); i < p.size(); ++i){
		total += p.getI(i);
		//total += p[i]; // pas possible car l'héritage est privé.
 	}
	out << "Mon porte monnaie contient : " <<  total << " francs"  << endl;
	return out;
}

//PROGRAMME PRINCIPAL
int main()
{
	PorteMonnaie p;
	p.gagner(2);
	p.gagner(3);
	p.gagner(12);

	cout << p << endl;

	p.vider();

	cout << p << endl;
	

	return 0;
}

Exercice 4 : attribut statique (niveau 1)

Voici un codage possible de cet exercice :

#include <string>
#include <iostream>
using namespace std;


class Ami {
public:
  //arghh.. il y a un attribut statique:
  // cet attribut est commun à toutes les 
  // instances de la classe et peut être 
  // utilisé indépendamment de la création de
  // tout objet de la classe.
  int static nombre_ami_Lausanne;

  Ami(string nom, string ville);
  Ami(const Ami& ami);
  ~Ami();
  void setVille(string ville);
  void afficher() const;

private:
  string nom;
  string ville;
};


// l' initialisation d'un attribut statique doit toujours de faire 
// à l'exterieur de la classe en utilisant l'opérateur ::
int Ami::nombre_ami_Lausanne = 0;

int main(){
  Ami a1("Toto", "Lausanne");
  a1.afficher();
  Ami a2(a1);
  a2.setVille("Brazzaville");
  a2.afficher();
  cout << "J'ai " << Ami::nombre_ami_Lausanne << " ami(s) vivant à Lausanne" << endl;
  return 0;
}

/* Définition exteriorisée des méthodes de la classe Ami */

// le constructeur:
// si l'ami "construit" habite  à Lausanne
// il faut incrémenter le compteur d'amis.
Ami::Ami(string nom, string ville)
  :nom(nom), ville(ville)
{cout<< "Un nouvel ami" <<  endl;
 if (ville == "Lausanne") ++nombre_ami_Lausanne;}

// constructeur de copie
// là encore il faut augmenter le nombre
// d'amis vivant à Lausanne (si l'ami "copié"
// vit à Lausanne bien sûr)
Ami::Ami(const Ami& ami)
  :nom(ami.nom), ville(ami.ville)
{cout << "Clone d'un ami" << endl;
 if (ville == "Lausanne") ++nombre_ami_Lausanne;}


// destructeur "standard"
// avec le comptage approprié du nombre d'ami.
// prenez la bonne habitude de toujours déclarer
// vos destructeurs comme virtuels (voir cours)

Ami::~Ami(){
  cout << "Sniff, fin d'une amitie" << endl;
  if (ville == "Lausanne") --nombre_ami_Lausanne;
}

// Ici il fallait penser à mettre à jour le 
// compteur d'ami dans deux situations possibles:
// l'ami (this en fait) s'établit à Lausanne
// après avoir été dans une autre ville ou  alors
// il s'établit dans une autre ville après avoir
// été à Lausanne.
void Ami::setVille(string ville){
  if ((this->ville == "Lausanne") && (ville != "Lausanne")) --nombre_ami_Lausanne;
 if ((this->ville != "Lausanne") && (ville == "Lausanne")) ++nombre_ami_Lausanne;
  this->ville = ville;


}

// on ne peut plus classique
void Ami::afficher() const{
  cout << "Mon ami s'appelle " << nom << endl;
  cout << "Il habite " << ville <<endl;
}

Dernière mise à jour : 2024/03/21 12:13