corriges-oop-w05
corriges-oop-w05
corriges-oop-w05
POO C++
Corrigés semaine 5
Les corrigés proposés correspondent à l'ordre des apprentissages : chaque corrigé correspond à la solution à laquelle vous
pourriez aboutir au moyen des connaissances acquises jusqu'à la semaine correspondante.
Cet exercice correspond à l'exercice n°60 (pages 150 et 335) de l'ouvrage C++
par la pratique (3e édition, PPUR).
1.1 Formes :
Définissez une classe Forme en la dotant d'une méthode qui affiche [...]
#include <iostream>
using namespace std;
class Forme {
public:
void description() const {
cout << "Ceci est une forme." << endl;
}
};
Ajoutez au programme une classe Cercle héritant de la classe Forme, et possédant une méthode void
description() qui affiche [...]
Le polymorphisme n'opère pas ici car aucune des deux conditions nécessaires n'est remplie : la méthode n'est pas virtuelle
et on ne passe par pas par des références ni pointeurs.
Avec cette fonction, nous apportons une solution au second aspect puisqu'en effet nous passons l'argument par référence.
Le résultat n'est cependant toujours pas satisfaisant (c'est toujours la méthode Forme::description() qui est
appelée) car le premier problème subsiste : la méthode n'est pas virtuelle.
Modifiez le programme (ajoutez 1 seul mot) pour que le résultat soit plus conforme à ce que l'on pourrait attendre.
Il suffit donc d'ajouter virtual devant le prototype de la méthode description de la classe Forme.
#include <iostream>
using namespace std;
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme." << endl;
}
};
int main()
{
Cercle c;
affichageDesc(c);
return 0;
}
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
virtual double aire() const = 0;
};
Ce qui en fait une méthode virtuelle pure c'est le =0 derrière qui indique que pour cette classe cette méthode ne sera pas
implémentée (i.e. pas de définition, c.-à-d. pas de corps).
Écrivez une classe Triangle et modifiez la classe Cercle existante héritant toutes deux de la classe Forme, et
implémentant les méthodes aire() et description(). [...]
void affichageDesc(Forme& f) {
f.description();
cout << " son aire est " << f.aire() << endl;
}
et le programme complet :
#include <iostream>
#include <cmath> // pour M_PI
using namespace std;
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
virtual double aire() const = 0;
};
void affichageDesc(Forme& f) {
f.description();
cout << " son aire est " << f.aire() << endl;
}
int main()
{
Cercle c(5);
Triangle t(10, 2);
affichageDesc(t);
affichageDesc(c);
return 0;
}
Cet exercice correspond à l'exercice n°60 (pages 149 et 334) de l'ouvrage C++
par la pratique (3e édition, PPUR).
#include <iostream>
using namespace std;
class Figure {
public:
virtual void affiche () const = 0;
virtual Figure* copie() const = 0;
};
Une classe nommée Dessin, qui modélise une collection de figures. [...]
class Dessin {
public:
void ajouteFigure();
private:
vector<Figure*> contenu;
};
Comme conseillé en cours, plutôt que d'hériter de la classe vector, on préfère encapsuler la collection dans la classe.
Pour les pointeurs, nous avons ici choisi des pointeurs «à la C». En C++11, on pourrait plutôt opter pour des
unique_prt. Une solution dans ce sens est proposée à la fin.
[...] définissez les attributs requis pour modéliser les objets correspondant
private:
double rayon;
};
//----------------------------------------------------------------------
class Carre : public Figure {
public:
Carre(double x = 0.0) : cote(x) {
cout << "Construction d'un carré de coté " << cote << endl;
}
private:
double cote;
};
//----------------------------------------------------------------------
class Triangle : public Figure {
public:
Triangle(double b = 0.0, double h = 0.0) : base(b), hauteur(h) {
cout << "Construction d'un triangle " << base << "x" << hauteur << endl;
}
private:
double base; double hauteur;
};
Cette partie, quoique finalement simple, est peut être d'un abord plus difficile.
Ne vous laissez pas démonter par l'apparente difficulté conceptuelle et, une fois de plus, décomposez le problème :
Que veut-on ?
de nous-même.
On a donc : Cercle(*this)
Que la copie soit effectivement placée en mémoire et on veut en retourner l'adresse (allocation dynamique).
Finalement, définissez la méthode virtuelle affiche, affichant le type de l'instance et la valeur de ses attributs.
Trivial :
~Dessin() {
cout << "Le dessins s'efface..." << endl;
for (auto & fig : contenu) delete fig;
}
public:
~Dessin() {
cout << "Le dessins s'efface..." << endl;
for (unsigned int i(0); i < contenu.size(); ++i) delete contenu[i];
}
void ajouteFigure(const Figure& fig) {
contenu.push_back(fig.copie());
}
void affiche() const {
cout << "Je contiens :" << endl;
for (unsigned int i(0); i < contenu.size(); ++i) {
contenu[i]->affiche();
}
}
private:
vector<Figure*> contenu;
};
Votre programme devrait indiquer que le destructeur du dessin est invoqué... mais pas les destructeurs des figures
stockées dans le dessin. Pourquoi ?
Car le destructeur n'est pas virtuel. Le compilateur vous a d'ailleurs avertit par les warnings.
#include <iostream>
#include <vector>
using namespace std;
//----------------------------------------------------------------------
class Figure {
public:
virtual void affiche() const = 0;
virtual Figure* copie() const = 0;
virtual ~Figure() { cout << "Une figure de moins." << endl; }
};
//----------------------------------------------------------------------
class Cercle : public Figure {
public:
Cercle(double x = 0.0) : rayon(x) {
cout << "Construction d'un cercle de rayon " << rayon << endl;
}
private:
double rayon;
};
//----------------------------------------------------------------------
class Carre : public Figure {
public:
Carre(double a = 0.0) : cote(a) {
cout << "Construction d'un carré de coté " << cote << endl;
}
private:
double cote;
};
//----------------------------------------------------------------------
class Triangle : public Figure {
public:
Triangle(double b = 0.0, double h = 0.0) : base(b), hauteur(h) {
cout << "Construction d'un triangle " << base << "x" << hauteur << endl;
}
private:
double base; double hauteur;
};
//----------------------------------------------------------------------
class Dessin {
public:
~Dessin() {
cout << "Le dessins s'efface..." << endl;
for (unsigned int i(0); i < contenu.size(); ++i) delete contenu[i];
}
void ajouteFigure(const Figure& fig) {
contenu.push_back(fig.copie());
}
void affiche() const {
cout << "Je contiens :" << endl;
for (unsigned int i(0); i < contenu.size(); ++i) {
contenu[i]->affiche();
}
}
private:
vector<Figure*> contenu;
};
int main()
{
Dessin dessin;
dessin.ajouteFigure(Triangle(3,4));
dessin.ajouteFigure(Carre(2));
dessin.ajouteFigure(Triangle(6,1));
dessin.ajouteFigure(Cercle(12));
unCercleDePlus(dessin); // impossible
[...] le système doit interrompre prématurément votre programme, en vous adressant un message hargneux [...]
Quel est, à votre avis, le motif pour un tel comportement ?
Ceci est le cas classique de la libération incongrue de mémoire par une copie passagère de l'objet. Voir le cours pour plus
de détails, mais en deux mot, tmp crée une copie de img qui est détruite à de la fin de la fonction et libère la mémoire
pointée, laquelle est encore utilisée par l'objet passé en argument comme img.
La prochaine tentative d'accès à cette mémoire via cet objet (du main()) provoque une erreur comme indiquée.
Pour y palier, il faudrait définir le constructeur de copie de Dessin afin de faire une copie profonde.
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Figure {
public:
virtual void affiche() const = 0;
virtual unique_ptr<Figure> copie() const = 0;
virtual ~Figure() { cout << "Une figure de moins." << endl; }
};
class Dessin {
public:
~Dessin() { cout << "Le dessins s'efface..." << endl; }
void ajouteFigure(const Figure& fig) { contenu.push_back(fig.copie()); }
void affiche() const {
cout << "Je contiens :" << endl;
for (auto const& fig : contenu) { fig->affiche(); }
}
private:
vector<unique_ptr<Figure>> contenu;
};
int main()
{
Dessin dessin;
dessin.ajouteFigure(Triangle(3,4));
dessin.ajouteFigure(Carre(2));
dessin.ajouteFigure(Triangle(6,1));
dessin.ajouteFigure(Cercle(12));
Cet exercice correspond à l'exercice n°61 (pages 151 et 342) de l'ouvrage C++
par la pratique (3e édition, PPUR).
#include <iostream>
#include <array>
#include <vector>
#include <string>
using namespace std;
// ====================================================================
enum Couleur { vide = 0, rouge, jaune };
// ====================================================================
class Jeu {
public:
Jeu(size_t taille = 8);
protected:
vector< vector< Couleur > > grille;
private:
unsigned int compte(Couleur, size_t, size_t,
int, int) const;
};
// ------------------------------------------------------------
Jeu::Jeu(size_t taille)
: grille(taille, vector<Couleur>(taille, vide))
{}
// ------------------------------------------------------------
bool Jeu::jouer(size_t i, Couleur c) {
if (c == vide) return false;
if (i >= get_taille()) return false;
size_t j(0);
while ((j < get_taille()) and (grille[i][j] != vide)) ++j;
if (j >= get_taille()) return false;
grille[i][j] = c;
return true;
}
// ------------------------------------------------------------
Couleur Jeu::gagnant() const {
Couleur vainqueur(vide);
for (size_t i(0); i < get_taille(); ++i) {
for (size_t j(0); j < get_taille(); ++j) {
if (grille[i][j] != vide) {
vainqueur = grille[i][j];
// teste dans les 5 directions possibles (+ (0,0) qui ne fait
// rien)
for (int di(0); di <= 1; ++di)
for (int dj(-1); dj <= 1; ++dj)
if (compte(vainqueur, i, j, di, dj) >= 4) return vainqueur;
}
}
}
return vide;
}
// ------------------------------------------------------------
unsigned int Jeu::compte(Couleur couleur,
size_t i, size_t j,
int di, int dj) const
{
unsigned int n(1);
if ((di != 0) or (dj != 0)) {
for (i += di, j += dj;
(i < get_taille()) and (j < get_taille()) and (grille[i][j] == couleur);
i += di, j += dj)
++n;
}
return n;
}
// ------------------------------------------------------------
bool Jeu::fini(Couleur& resultat) const {
resultat = gagnant();
if (resultat == vide) {
// est-ce plein ?
for (auto ligne : grille) {
for (auto kase : ligne) { // "case" est un mot réservé du C++ ;-)
if (kase == vide)
return false;
}
}
}
return true;
}
// ------------------------------------------------------------
ostream& operator<<(ostream& out, const Jeu& jeu) {
return jeu.affiche(out);
}
ostream& Jeu::affiche(ostream& out) const {
if (get_taille() > 0) {
size_t j(get_taille()-1);
do {
for (size_t i(0); i < get_taille(); ++i) {
switch (grille[i][j]) {
case vide : out << ' '; break;
case rouge : out << '#'; break;
case jaune : out << 'O'; break;
}
}
out << endl;
} while (j-- != 0);
for (size_t i(0); i < get_taille(); ++i) out << '-';
out << endl;
for (size_t i(1); i <= get_taille(); ++i) out << i;
out << endl;
}
return out;
}
// ====================================================================
class Joueur {
public:
Joueur(string un_nom, Couleur une_couleur = rouge)
: nom(un_nom), couleur(une_couleur) {}
virtual ~Joueur() {}
virtual void jouer(Jeu&) const = 0;
string get_nom() const { return nom; }
Couleur get_couleur() const { return couleur; }
protected:
string nom;
Couleur couleur;
};
// ====================================================================
class Partie {
public:
Partie(Joueur const*, Joueur const*);
void lancer();
void vider();
protected:
array<Joueur const*, 2> joueurs;
Jeu jeu;
};
// ------------------------------------------------------------
Partie::Partie(Joueur const* j1, Joueur const* j2) {
joueurs[0] = j1;
joueurs[1] = j2;
}
// ------------------------------------------------------------
void Partie::lancer() {
// ------------------------------------------------------------
void Partie::vider() {
for (auto joueur : joueurs) delete joueur;
}
// ====================================================================
class Humain : public Joueur {
public:
Humain(Couleur une_couleur = rouge) : Joueur("quidam", une_couleur) {
cout << "Entrez votre nom : " << flush;
cin >> nom;
}
void jouer(Jeu&) const;
};
// ------------------------------------------------------------
void Humain::jouer(Jeu& jeu) const {
cout << jeu << endl;
size_t lu;
bool valide;
do {
cout << "Joueur " << nom << ", entrez un numéro de colonne" << endl
<< " (entre 1 et " << jeu.get_taille() << ") : ";
cin >> lu; // on pourrait faire ici la validation de la lecture
--lu; // remet entre 0 et taille-1 (indice à la C++)
valide = jeu.jouer(lu, couleur);
if (not valide) cout << "-> Coup NON valide." << endl;
} while (not valide);
}
// ====================================================================
class Ordi : public Joueur {
public:
Ordi(Couleur couleur = jaune) : Joueur("Le programme", couleur) {}
void jouer(Jeu&) const;
};
// ------------------------------------------------------------
void Ordi::jouer(Jeu& jeu) const {
for (size_t i(0); i < jeu.get_taille(); ++i) {
if (jeu.jouer(i, couleur)) {
cout << nom << " a joué en " << i+1 << endl;
return;
}
}
}
// ====================================================================
int main()
{
Partie p(new Ordi(rouge), new Humain(jaune));
p.lancer();
p.vider();
return 0;
}
Exercice 19 : librairie
Cet exercice correspond à l'exercice n°72 (pages 183 et 381) de l'ouvrage C++
par la pratique (3e édition, PPUR).
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/* La classe Livre */
class Livre {
public:
Livre(string, string, int, bool);
virtual ~Livre() {}
virtual double calculer_prix() const ;
virtual void afficher() const ;
protected:
string titre ;
string auteur ;
int nbPages ;
bool bestseller ;
};
// constructeur
/* la sous-classe Roman*/
protected:
bool biographie;
};
{}
};
/* la sous-classe BeauLivre */
/* la classe Librairie */
class Librairie {
public:
void ajouter_livre(Livre* livre) { contenu.push_back(livre); }
void vider_stock();
void afficher() const;
private:
vector <Livre*> contenu;
};
void Librairie::vider_stock()
{
for (auto livre : contenu) { delete livre; }
contenu.clear();
}
int main()
{
Librairie l;
l.afficher();
l.vider_stock();
return 0;
}