Aller au menu - Aller au contenu

Icône TP: La POO en pratique avec ZFraction

Mise à jour : 27/05/2011
Difficulté : Facile Facile Durée d'étude : 1 jour Creative Commons BY-NC-SA
75 639 visites depuis 7 jours, dont 596 sur ce chapitre classé 5/786
Vous avez appris dans les chapitres précédents à manipuler des classes et même à en créer par vous-même. Il est donc grand temps de mettre tout ça en pratique avec un TP. Vous allez devoir écrire une classe complète, vous allez voir, c'est le meilleur moyen de digérer toutes ces nouvelles notions.

C'est le premier TP sur la POO, il porte donc sur les bases. C'est donc le bon moment d'arrêter un peu la lecture du cours, de souffler un peu et d'essayer de réaliser cet exercice par vous-même. Vous aurez aussi l'occasion de vérifier vos connaissances et donc de retourner lire les chapitres sur les éléments qui vous ont manqués.

Le sujet de ce TP devrait vous rappeler vos cours de mathématiques du collège. Nous allons Vous allez écrire une classe représentant la notion de fraction. Le C++ permet d'utiliser des nombres entiers via le type int, des nombres réels via le type double, mais il n'offre rien pour les nombre rationnels. À vous de pallier ce manque ! ;)


\begin{array}{rcl}        \dfrac{1}{2} + \dfrac{5}{3} &=& \dfrac{13}{6} \Bigg. \\       \dfrac{1}{2} \times \dfrac{5}{3} &=& \dfrac{5}{6}\end{array}
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Préparatifs et conseils

La classe que nous allons réaliser n'est pas très compliquée. Et il est assez aisé d'imaginer quelles méthodes et opérateurs nous allons utiliser. Cet exercice va en particulier tester vos connaissances sur :
  • Les attributs et leurs droits d'accès.
  • Les constructeurs.
  • La surcharge des opérateurs.

C'est donc le dernier moment de réviser ! :pirate:

Description de la classe ZFraction



Commençons par choisir un nom pour notre classe. Ce serait bien qu'il contienne le mot "fraction" et comme vous êtes sur le site du zéro, je vous propose d'ajouter un "Z" au début. Ce sera donc ZFraction. Ce n'est pas super original, mais au moins on sait directement à quoi on a affaire. :)

Utiliser des int ou des double est très simple. On les déclare, on les initialise et on utilise ensuite les opérateurs comme sur une calculatrice. Ce serait vraiment super de pouvoir faire la même chose pour des fractions.

On aimerait donc bien que le main() suivant compile et fonctionne correctement :

Code : C++ - Utilisation de ZFraction
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include "ZFraction.h"
using namespace std;

int main()
{
    ZFraction a(4,5);      //Déclare une fraction valant 4/5
    ZFraction b(2);        //Déclare une fraction valant 2/1 (ce qui vaut 2)
    ZFraction c,d;         //Déclare deux fractions valant 0

    c = a+b;               //Calcule 4/5 + 2/1 = 14/5

    d = a*b;               //Calcule 4/5 * 2/1 = 8/5

    cout << a << " + " << b << " = " << c << endl;

    cout << a << " * " << b << " = " << d << endl;

    if(a > b)
        cout << "a est plus grand que b." << endl;
    else if(a==b)
        cout << "a est egal a b." << endl;
    else
        cout << "a est plus petit que b." << endl;

    return 0;
}


Et voici le résultat espéré :

Code : Console
4/5 + 2 = 14/5
4/5 * 2 = 8/5
a est plus petit que b.


Pour arriver à cela, il nous faudra donc :
  • Écrire la classe avec ses attributs.
  • Réfléchir aux constructeurs à implémenter.
  • Surcharger les opérateurs +, *, <<, < et == (au moins).

En maths, lorsque l'on manipule des fractions, on utilise toujours des fractions simplifiées. C'est-à-dire que l'on écrira \frac{4}{5} plutôt que \frac{8}{10} même si ces deux fractions ont la même valeur. Il faudra donc faire en sorte que notre classe ZFraction respecte cette règle.


Si vous vous sentez prêt, alors allez-y ! Je n'ai rien de plus à ajouter concernant la donnée. Vous pourrez trouver certains rappels sur les calculs avec les nombres rationnels dans le cours de maths disponible sur ce site.

Si par contre vous avez peur de vous lancer seul, je vous propose de vous accompagner pour les premiers pas.

Créer un nouveau projet



Pour faire ce TP, vous allez devoir créer un nouveau projet. Utilisez l'IDE que vous voulez, moi pour ma part vous savez que j'utilise Code::Blocks ;)

Demandez à créer un nouveau projet console C++.
Ce projet sera constitué de 3 fichiers que vous pouvez déjà créer :
  • main.cpp : ce fichier contiendra uniquement la fonction main. Dans la fonction main, nous créerons des objets basés sur notre classe ZFraction pour tester son fonctionnement. A la fin, votre fonction main() devra ressembler à celle que je vous ai montré plus haut.
  • ZFraction.h : ce fichier contiendra le prototype de notre classe ZFraction avec la liste de ses attributs et les prototypes de ses méthodes.
  • ZFraction.cpp : ce fichier contiendra l'implémentation des méthodes de la classe ZFraction, c'est-à-dire le "code" à l'intérieur des méthodes.

Faites attention aux noms des fichiers et en particulier aux majuscules et minuscules. Les fichiers ZFraction.h et ZFraction.cpp commencent par 2 lettres majuscules, si vous écrivez "zfraction" ou encore "Zfraction" ça ne marchera pas et vous aurez des problèmes.


Le code de base des fichiers



Nous allons écrire un peu de code dans chacun de ces fichiers. Juste le strict minimum pour pouvoir commencer.

main.cpp


Bon, celui-là, je vous l'ai déjà donné. ;)
Mais pour commencer en douceur, je vous propose de simplifier l'intérieur de la fonction main() et d'y ajouter des instructions petit-à-petit au fur et à mesure de l'avancement de votre classe.

Code : C++ - main.cpp
1
2
3
4
5
6
7
8
9
#include <iostream>
#include "ZFraction.h"
using namespace std;
 
int main()
{
    ZFraction a(1,5); // Crée une fraction valant 1/5
    return 0;
}


Pour l'instant, on se contente d'un appel au constructeur de ZFraction. Pour le reste, on verra plus tard.

ZFraction.h



Ce fichier contiendra la définition de la classe ZFraction. Il inclut aussi iostream pour nos besoins futurs (nous aurons besoin de faire des cout dans la classe les premiers temps, ne serait-ce que pour débugger notre classe).

Code : C++ - ZFraction.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#ifndef DEF_FRACTION
#define DEF_FRACTION

#include <iostream>

class ZFraction
{
public:

private:

};

#endif


Pour l'instant, la classe est encore vide. Je ne vais pas non plus tout vous faire hein ! J'y ai quand même mis une partie privée et une partie publique. Souvenez-vous de la règle principale de la POO qui veut que tous les attributs soient dans la partie privée. Je vous en voudrais beaucoup si vous ne la respectiez pas. :colere2:

Comme tous les fichiers .h, ZFraction.h contient deux lignes commençant par # au début du fichier et une autre tout à la fin. Code::Blocks crée automatiquement ces lignes. Si votre IDE ne le fait pas, pensez à les ajouter. Elles évitent bien des soucis de compilation.


ZFraction.cpp



C'est le fichier qui va contenir les définitions des méthodes. Comme notre classe est encore vide, il n'y a donc rien à y écrire. Il faut juste penser à inclure l'entête ZFraction.h.

Code : C++ - ZFraction.cpp
1
#include "ZFraction.h"


Nous voilà enfin prêt à attaquer la programmation !

Choix des attributs de la classe



La première étape de la création d'une classe est souvent le choix des attributs. Il faut se demander de quelles briques de base notre classe est constituée. Avez-vous une petite idée ?

Voyons ça ensemble. Un nombre rationnel est composé de deux nombres entiers appelés le numérateur (celui qui est au-dessus de la barre de fraction) et le dénominateur (celui du dessous). Cela nous fait donc deux constituants. Les nombres entiers en C++ s'appellent des int. Ajoutons donc deux int à notre classe :

Code : C++ - ZFraction.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#ifndef DEF_FRACTION
#define DEF_FRACTION

#include <iostream>

class ZFraction
{
public:

private:

    int m_numerateur;      //Le numérateur de la fraction
    int m_denominateur;    //Le dénominateur de la fraction

};

#endif


Nos attributs commencent toujours par le préfixe "m_". C'est une bonne habitude de programmation que je vous ai enseignée dans les chapitres précédents ;)
Cela nous permettra par la suite de savoir si on est en train de manipuler un attribut de la classe ou une simple variable "locale" à une méthode.

Les constructeurs



Je ne vais pas tout vous dire non plus, mais dans le main() d'exemple que je vous ai présenté tout au début, on utilisait trois constructeurs différents :
  • Le premier recevait deux entiers comme argument. Ils représentaient respectivement le numérateur et le dénominateur de la fraction. C'est sans doute le plus intuitif des trois à écrire.
  • Le deuxième constructeur prend un seul entier en argument et construit une fraction égale à ce nombre entier. Cela veut dire que le dénominateur vaut 1 dans ce cas.
  • Finalement, le dernier constructeur ne prend aucun argument (constructeur par défaut) et crée une fraction valant 0.


Je ne vais rien expliquer de plus. Je vous propose de commencer par écrire au moins le premier de ces trois constructeurs. Les autres suivront rapidement, j'en suis sûr.

Les opérateurs



La part la plus importante de ce TP sera l'implémentation des opérateurs. Il faut bien réfléchir à la manière de les écrire. Vous pouvez bien sûr vous inspirer de ce qui a été fait pour la classe Duree du chapitre précédent. Par exemple, utiliser la méthode operator+= pour définir l'opérateur +. Ou écrire une méthode estEgalA() pour l'opérateur d'égalité.

Une bonne chose à faire est de commencer par l'opérateur <<. Vous pourrez alors facilement tester vos autres opérateurs.

Simplifier les fractions



L'important avec les fractions est de toujours manipuler des fractions simplifiées. C'est-à-dire que l'on va préférer écrire \frac{2}{5} que \frac{4}{10} par exemple. Il serait bien que notre classe fasse de même et simplifie elle-même la fraction qu'elle représente.

Il nous faut donc un moyen mathématique de le faire puis traduire le tout en C++. Si l'on a une fraction \frac{a}{b}, il faut calculer le plus grand commun diviseur de a et b puis diviser a et b par ce nombre. Par exemple, le pgcd de 4 et 10 est 2, ce qui veut dire que l'on peut simplifier les numérateurs et dénominateurs de \frac{4}{10} par 2, ce qui nous fait bien \frac{2}{5}.

Calculer le pgcd n'est pas une opération facile. Je vous propose donc une fonction pour le faire. Je vous invite à l'ajouter dans votre fichier ZFraction.cpp.

Code : C++ - Fonction de calcul du pgcd
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int pgcd(int a, int b)
{
    while (b != 0)
    {
        const int t = b;
        b = a%b;
        a=t;
    }
    return a;
}


Et à ajouter le prototype correspondant dans ZFraction.h :

Code : C++ - ZFraction.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#ifndef DEF_FRACTION
#define DEF_FRACTION

#include <iostream>

class ZFraction
{
   //Contenu de la classe...
};

int pgcd(int a, int b);

#endif


Vous pourrez alors utiliser cette fonction dans les méthodes de la classe.

Allez au boulot ! ;)

Correction

TEEERRRRMINNÉÉÉÉ ! Lâchez vos claviers, le temps imparti est écoulé. :'(

Il est temps de passer à la phase de correction. Vous avez certainement passé pas mal de temps à réfléchir aux différentes méthodes, opérateurs et autres horreurs joyeusetés du C++. Si vous n'avez pas réussi à tout faire, ce n'est pas grave. Lire la correction pour saisir les grands principes devrait vous aider. Et puis vous saurez peut-être vous rattraper avec les améliorations proposées en fin de chapitre.

Je vous propose de passer tranquillement en revue les différentes étapes de création de la classe.

Les constructeurs



Je vous avais suggéré de commencer par le constructeur prenant en argument deux entiers, le numérateur et le dénominateur. Voici ma version.

Code : C++ - Constructeur prenant deux entiers
1
2
3
4
ZFraction::ZFraction(int num, int den)
    :m_numerateur(num), m_denominateur(den)
{
}


On utilise la liste d'initialisation pour remplir les attributs m_numerateur et m_denominateur de la classe. Jusque-là, rien de sorcier.

En continuant sur cette lancée, on peut écrire les deux autres constructeurs :

Code : C++ - Les autres constructeurs
1
2
3
4
5
6
7
8
9
ZFraction::ZFraction(int entier)
    :m_numerateur(entier), m_denominateur(1)
{
}

ZFraction::ZFraction()
    :m_numerateur(0), m_denominateur(1)
{
}


Il fallait se rappeler que le nombre 5 s'écrit comme la fraction \frac{5}{1} et 0 comme \frac{0}{1}.
Le cahier des charges est donc rempli dans ce domaine. Avant de commencer à faire des choses compliquées, écrivons l'opérateur << pour l'affichage de notre fraction. On pourra ainsi facilement voir ce qui se passe dans notre classe en cas d'erreur.

Afficher une fraction



Comme nous l'avons vu dans le chapitre sur les opérateurs, la meilleure solution est d'utiliser une méthode affiche() dans la classe et d'appeler cette méthode dans la fonction operator<<. Un "copier-coller" du chapitre précédent nous donne donc directement le code de l'opérateur.

Code : C++ - Opérateur d'injection dans un flux
1
2
3
4
5
ostream& operator<<(ostream& flux, ZFraction const& fraction)
{
    fraction.affiche(flux);
    return flux;
}


Et pour la méthode affiche(), je vous propose cette version :

Code : C++ - Méthode d'affichage d'une fraction
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void ZFraction::affiche(ostream& flux) const
{
    if(m_denominateur == 1)
    {
        flux << m_numerateur;
    }
    else
    {
        flux << m_numerateur << '/' << m_denominateur;
    }
}


Notez le const dans le prototype de la méthode. Il montre que affiche() ne modifiera pas l'objet. Normal, puisque nous ne faisons qu'afficher ses propriétés.


Vous avez certainement écrit quelque chose d'approchant. J'ai distingué le cas où le dénominateur vaut 1. Une fraction dont le dénominateur vaut 1 est un nombre entier. On a donc pas besoin d'afficher la barre de fraction et le dénominateur. Mais c'est juste une question d'esthétique. ;)

L'opérateur d'addition



Comme pour <<, le mieux est d'employer la recette du chapitre précédent. Définir une méthode operator+=() dans la classe et l'utiliser dans la fonction operator+().

Code : C++ - Opérateur d'addition raccourci
1
2
3
4
5
6
ZFraction operator+(ZFraction const& a, ZFraction const& b)
{
    ZFraction copie(a);
    copie+=b;
    return copie;
}


La difficulté réside dans l'implémentation de l'opérateur d'addition raccourci. Comme toujours. ^^

En ressortant mes cahiers de maths, j'ai retrouvé la formule d'addition de deux fractions :

\frac{a}{b} + \frac{c}{d} = \frac{a\cdot d + b\cdot c}{b\cdot d}

Ce qui donne en C++ :

Code : C++ - Opérateur d'addition
1
2
3
4
5
6
7
ZFraction& ZFraction::operator+=(ZFraction const& autre)
{
    m_numerateur = autre.m_denominateur * m_numerateur + m_denominateur * autre.m_numerateur;
    m_denominateur = m_denominateur * autre.m_denominateur;

    return *this;    
}


Comme tous les opérateurs raccourcis, l'opérateur += doit renvoyer une référence sur *this. C'est une convention.


L'opérateur de multiplication



La formule de multiplication de deux fractions est plus simple encore que l'addition :

\frac{a}{b} \cdot \frac{c}{d} = \frac{a \cdot c}{b \cdot d}

Je vais garder mes livres maths à portée de main je crois... :)

Et je ne vais pas vous surprendre si je vous dis qu'il faut utiliser la méthode operator*=() et la fonction operator*(). Je crois qu'on commence à comprendre le truc. ;)

Code : C++ - Opérateurs de multiplication
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ZFraction operator*(ZFraction const& a, ZFraction const& b)
{
    ZFraction copie(a);
    copie*=b;
    return copie;
}

ZFraction& ZFraction::operator*=(ZFraction const& autre)
{
    m_numerateur *= autre.m_numerateur;
    m_denominateur *= autre.m_denominateur;

    return *this;
}


Les opérateurs de comparaison



Comparer des fractions pour tester si elles sont identiques revient à tester si leurs numérateurs et dénominateurs sont égaux. L'algorithme est donc à nouveau relativement simple. Je vous propose, comme toujours, de passer par une méthode de la classe puis d'utiliser cette méthode dans les opérateurs externes.

Code : C++ - Opérateurs de comparaison
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool ZFraction::estEgal(ZFraction const& autre) const
{
    if(m_numerateur == autre.m_numerateur && m_denominateur == autre.m_denominateur)
        return true;
    else
        return false;
}

bool operator==(ZFraction const& a, ZFraction const& b)
{
    if(a.estEgal(b))
        return true;
    else
        return false;
}

bool operator!=(ZFraction const& a, ZFraction const& b)
{
    if(a.estEgal(b))
        return false;
    else
        return true;
}


Une fois que la méthode estEgal() est implémentée, on a deux opérateurs pour le prix d'un seul. Parfait, je n'avais pas envie de réfléchir deux fois. :p

Les opérateurs d'ordre



Il ne nous reste plus qu'à écrire un opérateur permettant de vérifier si une fraction est plus petite que l'autre. Il y a plusieurs moyens de faire ça. Toujours dans mes livres de maths, j'ai retrouvé une vieille relation intéressante :

\frac{a}{b} < \frac{c}{d} \Longleftrightarrow a\cdot d < b \cdot c

Cette relation peut être traduite en C++ pour obtenir le corps de la méthode estPlusPetitQue() :

Code : C++ - Méthode de comparaison de deux fractions
1
2
3
4
5
6
7
bool ZFraction::estPlusPetitQue(ZFraction const& autre) const
{
    if(m_numerateur * autre.m_denominateur < m_denominateur * autre.m_numerateur)
        return true;
    else
        return false;
}


Et cette fois, ce n'est pas un pack "2 en 1", mais "4 en 1". Avec un peu de réflexion, on peut utiliser cette méthode pour les opérateurs <,>, <= et >=. :magicien:

Code : C++ - Les opérateurs de comparaison
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool operator<(ZFraction const& a, ZFraction const& b) //Vrai si a<b donc si a est plus petit que b
{
    if(a.estPlusPetitQue(b))
        return true;
    else
        return false;
}

bool operator>(ZFraction const& a, ZFraction const& b) //Vrai si a>b donc si b est plus petit que a
{
    if(b.estPlusPetitQue(a))
        return true;
    else
        return false;
}

bool operator<=(ZFraction const& a, ZFraction const& b) //Vrai si a<=b donc si b n'est pas plus petit que a
{
    if(b.estPlusPetitQue(a))
        return false;
    else
        return true;
}

bool operator>=(ZFraction const& a, ZFraction const& b) //Vrai si a>=b donc si a n'est pas plus petit que b
{
    if(a.estPlusPetitQue(b))
        return false;
    else
        return true;
}


Avec ces quatre derniers opérateurs, nous avons fait le tour de ce qui était demandé. Ou presque. Il nous reste à voir la partie la plus difficile : le problème de la simplification des fractions.

Simplifier les fractions



Je vous ai expliqué dans la présentation du problème quel algorithme utiliser pour simplifier une fraction. Il faut calculer le pgcd du numérateur et du dénominateur. Puis diviser les deux attributs de la fraction par ce nombre.

Comme c'est une opération qui doit être exécutée à différents endroits, je vous propose d'en faire une méthode de la classe. On aura ainsi pas besoin de récrire l'algorithme à différents endroits. Cette méthode n'a pas à être appelée par les utilisateurs de la classe. C'est de la mécanique interne. Elle va donc dans la partie privée de la classe.

Code : C++ - La méthode simplifie()
1
2
3
4
5
6
7
void ZFraction::simplifie()
{
    int nombre=pgcd(m_numerateur, m_denominateur); //Calcul du pgcd

    m_numerateur /= nombre;     //Et on simplifie
    m_denominateur /= nombre;
}


Quand faut-il utiliser cette méthode ?


Bonne question ! Mais vous devriez avoir la réponse. :)
Il faut simplifier la fraction à chaque fois qu'un calcul est effectué. C'est-à-dire, dans les méthodes operator+=() et operator*=() :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ZFraction& ZFraction::operator+=(ZFraction const& autre)
{
    m_numerateur = autre.m_denominateur * m_numerateur + m_denominateur * autre.m_numerateur;
    m_denominateur = m_denominateur * autre.m_denominateur;

    simplifie();    //On simplifie la fraction
    return *this;    
}

ZFraction& ZFraction::operator*=(ZFraction const& autre)
{
    m_numerateur *= autre.m_numerateur;
    m_denominateur *= autre.m_denominateur;

    simplifie();    //On simplifie la fraction
    return *this;
}


Mais ce n'est pas tout ! Quand l'utilisateur construit une fraction, rien ne garantit qu'il le fait correctement. Il peut très bien initialiser sa ZFraction avec les valeurs \frac{4}{8} par exemple. Il faut donc aussi appeler la méthode dans le constructeur qui prend deux arguments.

Code : C++ - Constructeur prenant deux entiers
1
2
3
4
5
ZFraction::ZFraction(int num, int den)
    :m_numerateur(num), m_denominateur(den)
{
    simplifie(); //On simplifie au cas où l'utilisateur aurait entré de mauvaises informations
}


Et voilà ! En fait, si vous regardez bien, nous avons dû ajouter un appel à la méthode simplifie() dans toutes les méthodes qui ne sont pas déclarées constantes ! Chaque fois que l'objet est modifié, il faut simplifier la fraction. On aurait pu éviter de réfléchir et simplement analyser notre code à la recherche de ces méthodes. Utiliser const est donc un atout de sécurité. On voit tout de suite où il faut faire des vérifications (appeler simplifie()) et où c'est inutile.

Notre classe est maintenant fonctionnelle et respecte les critères que je vous ai imposé. Hip Hip Hip Hourra !

Code complet



Pour finir, je vous propose le code complet de la classe.

Code : C++ - ZFraction.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#ifndef DEF_FRACTION
#define DEF_FRACTION

#include <iostream>

class ZFraction
{
public:

    //Constructeurs
    ZFraction(int num, int den);
    ZFraction(int nombre);
    ZFraction();

    //Affichage
    void affiche(std::ostream& flux) const;

    //Opérateurs arithmétiques
    ZFraction& operator+=(ZFraction const& autre);
    ZFraction& operator*=(ZFraction const& autre);

    //Méthodes de comparaison
    bool estEgal(ZFraction const& autre) const;
    bool estPlusPetitQue(ZFraction const& autre) const;

private:

    int m_numerateur;      //Le numérateur de la fraction
    int m_denominateur;    //Le dénominateur de la fraction

    // Simplifie une fraction
    void simplifie();

};

//Opérateur d'injection dans un flux
std::ostream& operator<<(std::ostream& flux, ZFraction const& fraction);

//Opérateurs arithmétiques
ZFraction operator+(ZFraction const& a, ZFraction const& b);
ZFraction operator*(ZFraction const& a, ZFraction const& b);

//Opérateurs de comparaison
bool operator==(ZFraction const& a, ZFraction const& b);
bool operator!=(ZFraction const& a, ZFraction const& b);
bool operator<(ZFraction const& a, ZFraction const& b);
bool operator>(ZFraction const& a, ZFraction const& b);
bool operator>=(ZFraction const& a, ZFraction const& b);
bool operator<=(ZFraction const& a, ZFraction const& b);

//Calcul du pgcd
int pgcd(int a, int b);

#endif


Code : C++ - ZFraction.cpp
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include "ZFraction.h"
using namespace std;

//Constructeurs
ZFraction::ZFraction(int num, int den)
    :m_numerateur(num), m_denominateur(den)
{
    simplifie();
}

ZFraction::ZFraction(int entier)
    :m_numerateur(entier), m_denominateur(1)
{
}

ZFraction::ZFraction()
    :m_numerateur(0), m_denominateur(1)
{
}

//Affichage
void ZFraction::affiche(ostream& flux) const
{
    if(m_denominateur == 1)
    {
        flux << m_numerateur;
    }
    else
    {
        flux << m_numerateur << '/' << m_denominateur;
    }
}

//Opérateur d'injection dans un flux
ostream& operator<<(ostream& flux, ZFraction const& fraction)
{
    fraction.affiche(flux);
    return flux;
}

//Opérateurs arithmétiques
ZFraction& ZFraction::operator+=(ZFraction const& autre)
{
    m_numerateur = autre.m_denominateur * m_numerateur + m_denominateur * autre.m_numerateur;
    m_denominateur = m_denominateur * autre.m_denominateur;

    simplifie();
    return *this;
}

ZFraction& ZFraction::operator*=(ZFraction const& autre)
{
    m_numerateur *= autre.m_numerateur;
    m_denominateur *= autre.m_denominateur;

    simplifie();
    return *this;
}

//Méthodes de comparaison
bool ZFraction::estEgal(ZFraction const& autre) const
{
    if(m_numerateur == autre.m_numerateur && m_denominateur == autre.m_denominateur)
        return true;
    else
        return false;
}

bool ZFraction::estPlusPetitQue(ZFraction const& autre) const
{
    if(m_numerateur * autre.m_denominateur < m_denominateur * autre.m_numerateur)
        return true;
    else
        return false;
}

//Simplification
void ZFraction::simplifie()
{
    int nombre=pgcd(m_numerateur, m_denominateur); //Calcul du pgcd

    m_numerateur /= nombre;     //Et on simplifie
    m_denominateur /= nombre;
}

//Opérateurs externes
ZFraction operator+(ZFraction const& a, ZFraction const& b)
{
    ZFraction copie(a);
    copie+=b;
    return copie;
}

ZFraction operator*(ZFraction const& a, ZFraction const& b)
{
    ZFraction copie(a);
    copie*=b;
    return copie;
}

bool operator==(ZFraction const& a, ZFraction const& b)
{
    if(a.estEgal(b))
        return true;
    else
        return false;
}

bool operator!=(ZFraction const& a, ZFraction const& b)
{
    if(a.estEgal(b))
        return false;
    else
        return true;
}

bool operator<(ZFraction const& a, ZFraction const& b) //Vrai si a<b donc si a est plus petit que b
{
    if(a.estPlusPetitQue(b))
        return true;
    else
        return false;
}

bool operator>(ZFraction const& a, ZFraction const& b) //Vrai si a>b donc si b est plus petit que a
{
    if(b.estPlusPetitQue(a))
        return true;
    else
        return false;
}

bool operator<=(ZFraction const& a, ZFraction const& b) //Vrai si a<=b donc si b n'est pas plus petit que a
{
    if(b.estPlusPetitQue(a))
        return false;
    else
        return true;
}

bool operator>=(ZFraction const& a, ZFraction const& b) //Vrai si a>=b donc si a n'est pas plus petit que b
{
    if(a.estPlusPetitQue(b))
        return false;
    else
        return true;
}

//Calcul du pgcd
int pgcd(int a, int b)
{
    while (b != 0)
    {
        const int t = b;
        b = a%b;
        a=t;
    }
    return a;
}


A vous maintenant de lire, tester et modifier ce code pour bien comprendre tout ce qui s'y passe. N'oubliez pas, la pratique est essentielle pour progresser en programmation.

Aller plus loin

Notre classe est terminée. Ou disons qu'elle remplit les conditions posées en début de chapitre. Mais vous en conviendrez, on est encore loin d'avoir fait le tour du sujet. On peut faire beaucoup plus avec des fractions. :lol:

Je vous propose de télécharger le code source du TP si vous le souhaitez avant d'aller plus loin :



Voyons maintenant ce que l'on pourrait ajouter.

  • Ajouter des méthodes numerateur() et denominateur() qui renvoient le numérateur et le dénominateur de la ZFraction sans la modifier.
  • Ajouter une méthode nombreReel() qui convertit notre fraction en un double.
  • Simplifier les constructeurs comme pour la classe Duree. En réfléchissant bien, on peut fusionner les trois constructeurs en un seul avec des valeurs par défaut.
  • Proposer plus d'opérateurs. Nous avons implémenté l'addition et la multiplication. Il nous manque la soustraction et la division.
  • Pour l'instant, notre classe ne gère que les fractions positives. Cela n'est pas suffisant ! Il faudrait permettre des fractions négatives.
    Si vous vous lancez dans cette tâche, il va falloir faire des choix importants. La manière de gérer le signe par exemple. Ce que je vous propose c'est de toujours placer le signe de la fraction au numérateur. Ainsi, \frac{1}{-4} devra être automatiquement converti en \frac{-1}{4}. En plus de simplifier les fractions, vos opérateurs devront donc aussi veiller à placer le signe au bon endroit.
    A nouveau, je vous conseille d'utiliser une méthode privée.
  • Si vous permettez l'utilisation de fractions négatives, alors il serait bien de proposer l'opérateur "moins unaire". C'est l'opérateur qui transforme un nombre positif en nombre négatif comme dans b= -a;. Je ne vous ai pas parlé de cet opérateur. Comme les autres opérateurs arithmétiques, il se déclare en-dehors de la classe. Son prototype est :
    Code : C++
    1
    ZFraction operator-(ZFraction const& a);
    

    C'est nouveau, mais pas très difficile si l'on utilise les bonnes méthodes de la classe. ;)
  • Ajouter des fonctions mathématiques telles que abs(), sqrt(), pow() prenant en argument des ZFraction. Pensez à inclure l'en-tête cmath. ;)

Je pense que cela va vous demander pas mal de travail. :euh: Mais c'est tout bénéfice pour vous. Il faut pas mal d'expérience avec les classes pour arriver à "penser objet" et il n'y a que la pratique qui peut vous aider.

Je ne vais pas vous fournir une correction détaillée pour chacun de ces points. Mais je peux vous proposer une solution possible :



Si vous avez d'autres idées, n'hésitez pas à les ajouter à votre classe.
J'espère que tout c'est bien déroulé. Si ce n'est pas le cas, je vous invite à utiliser le forum C++ pour y poser vos questions. Il y aura forcément quelqu'un qui saura vous répondre. ;)

Ce TP vous a, j'en suis sûr, aidé à bien saisir tout ce qui se cache derrière les classes. Vous avez notamment dû réfléchir au choix des attributs, au choix des méthodes, aux constructeurs, aux opérateurs, ...
En somme, vous en savez déjà beaucoup ! Nous avons effectué un bon bout du chemin.

Dans les prochains chapitres, nous allons aborder des notions un peu plus complexes sur la POO, l'héritage et le polymorphisme notamment.
Chapitre précédent Sommaire Chapitre suivant

Partager

24 commentaires pour "TP: La POO en pratique avec ZFraction"
Note moyenne : 3.85 / 4 (1749 votes)
Pseudo Commentaire
Hors ligne befust # Posté le 22/02/2012 à 21:27:14

Citation : Jason Mahe
J'ai eu vraiment du mal avec le TP. Sans la correction, je ne l'aurais jamais réussi. C'est assez désespérant. Pourquoi dans ZFraction.h il se trouve des méthodes qui ne sont ni dans private ni dans public?


ce ne sont pas des méthodes mais des fonctions. Elles n'appartiennent pas à la classe.
Donc ce sont des fonctions comme vu au début du cours ( Code : C++
1
ajouteDeux()
par exemple)

L'utilisateur peut utiliser ces fonctions comme bon lui semble. Ce qui est entre les accolades de la classe "appartient à la classe , ce qui est en dehors n'appartient pas à la classe.

On aurait pu créer 2 nouveaux fichiers fonctionsAutre.h et fonctionsAutre.cpp et y placer ces fonctions .

Comme ça dans ton fichier de la classe ZFraction il n'y aurait vraiment que ce qui concernent cette classe.
Hors ligne lio2609 # Posté le 05/03/2012 à 23:43:57

Avis : Bon

Bonjour,

Tres bon tuto, je suis le cour depuis le debut et j'ai ,jusqu'ici, plutot bien compris( ce qui m'etonne moi meme ;) ).

Mais là je dois avouer que je suis completement perdu. et la derniere phrase de la page a fini de m'achever :

"Dans les prochains chapitres, nous allons aborder des notions un peu plus complexes sur la POO, l'héritage et le polymorphisme notamment."

Pardon? "plus complexe" ? :(

D'où ma question.

Pouvez-vous me dire si ,etant novice dans la programmation,cet incompréhension est normale meme apres 2 lecture et avec le code source complet sous le nez?

Autrement dit, dois-je perseverer ou abandonner sachant qu'apparemment la suite ne s'annonce guere plus comprehensible pour moi?

Soyez franc, je suis prêt à tout entendre. ;)

"Ce n'est pas parce qu'ils sont beaucoup à avoir tort qu'ils ont raison" Michel Colucci.
 
Hors ligne fredi125 # Posté le 09/03/2012 à 05:19:28

Avis : Très bon

Excellent Tutoriel!

Ce TP ma réellement aidé à assimiler l'utilisation des operator


Petite remarque, dans la conclusion: "J'espère que tout c'est bien déroulé."

Si je ne me trompe pas, il faudrait dire "J'espère que tout S'est bien déroulé."



Merci infiniment pour ce site web d'une qualité exemplaire!!!
Hors ligne Alexis GROS # Posté le 25/04/2012 à 13:46:01

Citation : lio2609
Bonjour,

Tres bon tuto, je suis le cour depuis le debut et j'ai ,jusqu'ici, plutot bien compris( ce qui m'etonne moi meme ;) ).

Mais là je dois avouer que je suis completement perdu. et la derniere phrase de la page a fini de m'achever :

"Dans les prochains chapitres, nous allons aborder des notions un peu plus complexes sur la POO, l'héritage et le polymorphisme notamment."

Pardon? "plus complexe" ? :(

D'où ma question.

Pouvez-vous me dire si ,etant novice dans la programmation,cet incompréhension est normale meme apres 2 lecture et avec le code source complet sous le nez?

Autrement dit, dois-je perseverer ou abandonner sachant qu'apparemment la suite ne s'annonce guere plus comprehensible pour moi?

Soyez franc, je suis prêt à tout entendre. ;)


Non, sincerement, arrete pas!
j'étais exaxtement dans la meme situation que toi et cette ligne m'avais désemparé... :(
Puis je me suis rapellé:

"Rappelez vous, les programmeurs sont fainéants! :D "

J'ais donc sauté le tp (indigérable) et suis direct passé à léritage, en les survolant sans trop regarder les détails, du coup je savais (juste) à quoi ils servaient!
pour la suite du cours (Qt), je savais alors quelle chapitres réviser!
Et puis au fil du temps, j'ais fini par tout retenir :ange:
Et j'ais fini le tuto sans émoragies cérébrales :soleil:
Hors ligne Armas # Posté le 20/05/2012 à 20:59:49

Avis : Bon

J'ai quelques soucis dans la surcharge d'opérateur ==

Si vous testez 30/2 == 15/1, ça va pas marcher puisque vous avez oublié de réduire les fractions ...

Nanananananere <3

Je tiens à remercier les auteurs : je suis un DUT génie électrique et informatique industrielle, et le seul langage dans lequel on a vraiment eu des cours, c'est le VB.net ... pour entrer en école d'ingé, je pensais que ça faisait juste, mais nan, les langages se ressemblent beaucoup, c'est étonnant.

Merci beaucoup.

Voir tous les commentaires