Choisissez votre style : colorisé, impression

Correction 16
Premiers pas en POO

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 1 : la classe Cercle (POO, niveau 1)

Cet exercice se fait de façon assez similaire à l'exercice d'introduction (niveau 0) sur les rectangles.

Comment commencer ? À ce niveau, il suffit de suivre l'enoncé :

Remarque : les meilleurs analystes peuvent faire remaquer que le centre est un "point" et écrire, de façon encore plus propre, le code suivant :

struct Point {
    double x;  // abscisse
    double y;  // ordonnée
};

class Cercle {
private:
    double rayon;
    Point centre;
};

Remarque 2 : les meilleurs analystes et programmeurs « objet » en feront bien sûr une classe plutôt qu'une struct...

On continue ensuite à suivre l'énoncé à la lettre : Déclarez ensuite les méthodes «get» et «set» correspondantes :

class Cercle {
    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
};

Attention à bien faire la différence entre x en tant qu'argument de la méthode et x en tant qu'attribut de l'instance. Dans les cas ambigus comme ci-dessus, il faut lever l'ambiguité en utilisant le pointeur this (pour indiquer l'attribut).

Ah! QUESTION : ces méthodes sont-elles privées ou publiques ?

Publiques évidemment car on doit pouvoir les utiliser hors de la classe (elles font partie de l'interface).

class Cercle {
public: //ICI
    void getCentre(double &x, double &y) const {
    ... // comme avant
};

Et on termine notre classe de façon très similaire à ce qui précède :

#include <cmath> // pour M_PI

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 {
		... // comme avant
	};

Reste plus qu'à tester :

#include <iostream> // pour cout et endl
#include <cmath> // pour M_PI et sqrt()
using namespace std;

// ... la classe Cercle comme avant

int main () {
    Cercle c1, c2;

    c1.setCentre(1.0, 2.0);
    c1.setRayon(sqrt(5.0)); // passe par (0, 0)
    c2.setCentre(-2.0, 1.0);
    c2.setRayon(2.25);  // 2.25 > sqrt(5) => inclus le point (0, 0)

    cout << "Surface de C1 : " << c1.surface() << endl;
    cout << "Surface de C2 : " << c2.surface() << endl;

    cout << "position du point (0, 0) : ";
    if (c1.estInterieur(0.0, 0.0)) cout << "dans";
    else                           cout << "hors de";
    cout << " C1 et ";
    if (c2.estInterieur(0.0, 0.0)) cout << "dans";
    else                           cout << "hors de";
    cout << " C2." << endl;

    return 0;
}

On pourrait aussi faire une fonction pour tester les points :

void testePoint(const double x, const double y, Cercle c1, Cercle c2) {
    cout << "position du point (" << x << ", " << y << ") : ";
    if (c1.estInterieur(x, y)) cout << "dans";
    else                       cout << "hors de";
    cout << " C1 et ";
    if (c2.estInterieur(x, y)) cout << "dans";
    else                       cout << "hors de";
    cout << " C2." << endl;
}

int main () {
    ... // comme avant

    testePoint(0.0, 0.0, c1, c2);
    testePoint(1.0, 1.0, c1, c2);
    testePoint(2.0, 2.0, c1, c2);

    return 0;
}

Solutions finales

Version suffisante
#include <iostream> // pour cout et endl
#include <cmath> // pour M_PI et sqrt()

using namespace std;

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
};

int main () {
    Cercle c1, c2;

    c1.setCentre(1.0, 2.0);
    c1.setRayon(sqrt(5.0)); // passe par (0, 0)
    c2.setCentre(-2.0, 1.0);
    c2.setRayon(2.25);  // 2.25 > sqrt(5) => inclus le point (0, 0)

    cout << "Surface de C1 : " << c1.surface() << endl;
    cout << "Surface de C2 : " << c2.surface() << endl;

    cout << "position du point (0, 0) : ";
    if (c1.estInterieur(0.0, 0.0)) cout << "dans";
    else                           cout << "hors de";
    cout << " C1 et ";
    if (c2.estInterieur(0.0, 0.0)) cout << "dans";
    else                           cout << "hors de";
    cout << " C2." << endl;

    return 0;
}
Version perfectionnée
#include <iostream> // pour cout et endl
#include <cmath> // pour M_PI et sqrt()
using namespace std;
   
// -----=====----- un point dans le plan -----=====-----
struct Point {
    double x;  // abscisse
    double y;  // ordonnée
};

// -----=====----- la classe Cercle -----=====-----
class Cercle {
// --- interface ---
public:
    inline double surface() const { return M_PI * rayon * rayon; }

   inline bool estInterieur(const Point p) const {
        return (((p.x-centre.x) * (p.x-centre.x) +
                 (p.y-centre.y) * (p.y-centre.y))
                <= rayon * rayon);
    }

    // interface des attributs
    inline Point getCentre() const { return centre; }
    inline void setCentre(const Point centre) { this->centre = centre; }
    inline double getRayon() const { return rayon; }
    void setRayon(double r) {
       if (r < 0.0) r = 0.0;
       rayon = r;
    }
// --- -------- ---
private:
    double rayon;
    Point centre;
};

// ======================================================================
inline void dans(bool oui) { if (oui) cout << "dans"; else cout << "hors de"; }
   
// ======================================================================
void test(Point p, Cercle c1, Cercle c2) {
    cout << "position du point (" << p.x << ", " << p.y << ") : ";
    dans(c1.estInterieur(p));
    cout << " C1 et ";
    dans(c2.estInterieur(p));
    cout << " C2." << endl;
}
   
// ======================================================================
int main () {
    Cercle c1, c2;
    Point p;

    p.x = 1.0; p.y = 2.0;
    c1.setCentre(p);
    c1.setRayon(sqrt(5.0)); // passe par (0, 0)

    p.x = -2.0; p.y = 1.0;
    c2.setCentre(p);
    c2.setRayon(2.25);  // 2.25 > sqrt(5) => inclus le point (0, 0)

    cout << "Surface de C1 : " << c1.surface() << endl;
    cout << "Surface de C2 : " << c2.surface() << endl;

    p.x=0.0; p.y=0.0;
    test(p,c1,c2);  

    p.x=1.0; p.y=1.0;
    test(p,c1,c2);  

    return 0;
}

Exercice 2 : Géométrie (conception OO, niveau 2)

Code de triangle.cc :

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

class Point {

public:
  // par défaut intialisé à zéro
  Point (): x(0.), y(0.)
  {}

  void lire_point() {
    cout <<"Contruction d'un nouveau point" << endl;
    cout <<"Veuillez entrer x: " << endl;
    cin >> x;
    // on pourrait aussi boucler tant que la donnée est invalide.
    if (cin.fail()) {
			
      throw std::invalid_argument("La valeur de la coordonnee x est invalide");
    }
		
    cout <<"Veuillez entrer y: " << endl;
    cin >> y;
    if (cin.fail()) {
			
      throw std::invalid_argument("La valeur de la coordonnee y est invalide");
    }
  }
	
  double calculer_distance (Point p) const {
    /* Calcule la distance entre deux points. Le premier point est
       l'objet actuel (this). Le deuxième point (p) est envoyé en
       paramètre. */
    double xdiff = x - p.x;
    double ydiff = y - p.y;
    double somme = xdiff*xdiff + ydiff*ydiff;
    double distance = sqrt(somme);
    return distance;
  }

private:
	double x, y;
};


/* Classe Triangle
  Les longueurs des côtés sont calculées et stockées dans des attributs.
  Les méthodes calculer_perimetre et tester_isocele peuvent ainsi
  accéder aux valeurs précalculées et nous évitons de les
  recalculer plusieurs fois.
  Ce choix aurait des implications si les points du triangles étaient
  modifiables, via un setter par exemple.
  Il faudrait alors garantir que toute modification d'un sommet
  entraîne un re-calcul des longueurs.
  Ici nous sommes partis de l'idée que les points du triangles
  sont non modifiables, ce qui évite ce problème.
*/
 
class Triangle {
public:
  Triangle(const Point& p1, const Point& p2, const Point& p3) 
    : p1(p1)
    , p2(p2)
    , p3(p3)
    , longueur1(p1.calculer_distance(p2))
    , longueur2(p2.calculer_distance(p3))
    , longueur3(p3.calculer_distance(p1))
  { }
  
  double calculer_perimetre () const  {
    return (longueur1 + longueur2 + longueur3);
  }
  
  bool tester_isocele () const {
    return  ((longueur1 == longueur2) ||
	     (longueur2 == longueur3) ||
	     (longueur3 == longueur1));
	}
	
private:
	const Point p1, p2, p3; // on pourrait aussi avoir un array de Point
	const double longueur1, longueur2, longueur3;	
};

int main (){
  Point p1, p2, p3;
  try {
    p1.lire_point();
    p2.lire_point();
    p3.lire_point();
  }
  catch (const exception& e){
    cout << e.what() << endl;
    return 1;
  }
	 
  Triangle triangle(p1,p2,p3);
  double perimetre = triangle.calculer_perimetre();
  cout << "Périmètre : " << perimetre<< endl;
  bool isocele = triangle.tester_isocele();
  
  cout << "Le triangle " << (isocele ? "est " : "n'est pas ")
       << "isocèle" << endl;
  
  return 0;
}

Exercice 3 : Tour de magie (conception OO, niveau 2)

Voici une solution possible (parmi d'autres) :

#include <iostream>
using namespace std;
 
// Un bout de papier... pour ce tour de magie
class Papier {       
public:
  void ecrire(unsigned int un_age, unsigned int de_l_argent) {
    age    = un_age;
    argent = de_l_argent;
  }
 
  unsigned int lire_age()   const { return age   ; }
  unsigned int lire_somme() const { return argent; }
 
private:
  /* Ces 2 attributs spécifiquement, car d'une façon ou d'une autre
   * le spectateur rend intelligible l'information contenue sur le papier.
   */
  unsigned int age;
  unsigned int argent;
};
 
// --------------------------------------
class Assistant { 
public:
  void lire(const Papier& billet);
  void calculer();
  unsigned int annoncer();
 
private:
  /* l'assistant mémorise dans son cerveau les valeurs lues
   * et le resultat du calcul.
   */
  unsigned int age_lu;
  unsigned int argent_lu;
  unsigned int resultat;
};
 
 
// --------------------------------------
class Spectateur { 
public:
  void arriver();  /* lorsqu'il entre dans la salle (avant 
                    *    il n'"existe" pas pour nous)
                    */
  void ecrire();    // écrit sur le papier
  Papier montrer(); // montre le papier
 
private:
  // ses spécificités
  unsigned int age;
  unsigned int argent;
 
  /* Dans cette version nous faisons l'hypothèse que
   * c'est le spectateur qui a un papier.
   * Dans d'autres modélisations, ce papier pourrait aussi bien
   * appartenir au magicien (variable locale à Magicien::tourDeMagie),
   * à l'assistant ou même "être dans la salle" (i.e. variable du main) 
   */
  Papier paquet_cigarettes;
};
 
 
// --------------------------------------
class Magicien { 
public:
  void tourDeMagie(Assistant& asst, Spectateur& spect);
  /* Pour faire son tour, le magicien a besoin d'au moins
   * un spectateur et d'un assistant.
   */
 
private:
  /* partie privée ici car seul le magicien sait ce qu'il doit
   * faire dans son tour.
   */
  void calculer(unsigned int resultat_recu);
  void annoncer();
 
  unsigned int age_devine;
  unsigned int argent_devine;
};
 
// ======================================================================
int main() 
{
  // L'histoire générale :
  Spectateur thorin;    // Il était une fois un spectateur...
  thorin.arriver();     // ...qui venait voir un spectacle (!!)...
 
  Magicien gandalf;                   // ...où un magicien...
  Assistant bilbo;                    // ...et son assistant...
  gandalf.tourDeMagie(bilbo, thorin); // ...lui firent un tour fantastique.
 
  return 0;
}         
 
// ----------------------------------------------------------------------
void Assistant::lire(const Papier& billet)
{
  cout << "[Assistant] (je lis le papier)" << endl;
  age_lu    = billet.lire_age();
  argent_lu = billet.lire_somme();
}
 
void Assistant::calculer()
{
  cout << "[Assistant] (je calcule mentalement)" << endl;
  resultat = age_lu * 2;
  resultat += 5;
  resultat *= 50;
  resultat += argent_lu;
  resultat -= 365;
}
 
unsigned int Assistant::annoncer()
{
  cout << "[Assistant] J'annonce : " << resultat << " !" << endl;
  return resultat;
}
 
// ----------------------------------------------------------------------
void Spectateur::arriver()
{
  cout << "[Spectateur] (j'entre en scène)" << endl;
  cout << "Quel âge ai-je ? "; 
  cin >> age;
  do {
    cout << "Combien d'argent ai-je en poche (<100) ? "; 
    cin >> argent;
  } while (argent >= 100);
  cout << "[Spectateur] (je suis là)" << endl;
}
 
void Spectateur::ecrire()
{
  cout << "[Spectateur] (j'écris le papier)" << endl;
  paquet_cigarettes.ecrire(age, argent);
}
 
Papier Spectateur::montrer()
{
  cout << "[Spectateur] (je montre le papier)" << endl;
  return paquet_cigarettes;
}
 
// ----------------------------------------------------------------------
void Magicien::tourDeMagie(Assistant& fidele, Spectateur& quidam)
{
  cout << "[Magicien] un petit tour de magie..." << endl;
  // le magicien donne ses instructions :
  quidam.ecrire();
  fidele.lire(quidam.montrer());
  fidele.calculer();
  calculer(fidele.annoncer());
  annoncer();
}
 
void Magicien::calculer(unsigned int resultat_recu) {
  resultat_recu += 115;
  age_devine    = resultat_recu / 100;
  argent_devine = resultat_recu % 100;
}
 
void Magicien::annoncer() {
  cout << "[Magicien] " << endl
       << "  - hum... je vois que vous êtes agé de " << age_devine << " ans" << endl
       << "    et que vous avez " << argent_devine << " francs en poche !" << endl;
}
 

Dernière mise à jour : 2025/03/06 13:48