Jeux Libres
       
           

» Les Tutoriels » Apprenez à programmer en C++ ! » TP : La POO en pratique avec Fraction

Note : Vous vous apprêtez à lire un tutoriel de M@teo21 et Nanoc initialement publié à cette adresse sous la licence Creative Commons BY-NC-SA 2.0.

TP : La POO en pratique avec Fraction


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. A vous de palier ce manque ! ;)

[math]\begin{array}{rcl} \frac{1}{2} + \frac{5}{3} &=& \frac{13}{6} \Bigg. \\ \frac{1}{2} \times \frac{5}{3} &=& \frac{5}{6}\end{array}[/math]





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 !

Description de la classe Fraction



Commençons par choisir un nom pour notre classe. Appelons-la Fraction. 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 :
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 "Fraction.h"
using namespace std;
 
int main()
{
   Fraction a(4,5);      //Dclare une fraction valant 4/5
   Fraction b(2);        //Dclare une fraction valant 2/1 (ce qui vaut 2)
   Fraction c,d;         //Dclare 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é :
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 [math=inline]\frac{4}{5}[/math] plutôt que [math=inline]\frac{8}{10}[/math] même si ces deux fractions ont la même valeur. Il faudra donc faire en sorte que notre classe Fraction respecte cette règle.


Si vous vous sentez prêt, alors allez-y ! Je n'ai rien de plus à ajouter concernant la donnée.

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 Fraction pour tester son fonctionnement. A la fin, votre fonction main() devra ressembler à celle que je vous ai montré plus haut.
  • Fraction.h : ce fichier contiendra le prototype de notre classe Fraction avec la liste de ses attributs et les prototypes de ses méthodes.
  • Fraction.cpp : ce fichier contiendra l'implémentation des méthodes de la classe Fraction, 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 Fraction.h et Fraction.cpp commencent par une lettre majuscule, si vous écrivez "fraction" ç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.
1
2
3
4
5
6
7
8
9
#include <iostream>
#include "Fraction.h"
using namespace std;
 
int main()
{
   Fraction a(1,5); // Cre une fraction valant 1/5
   return 0;
}

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

Fraction.h



Ce fichier contiendra la définition de la classe Fraction. 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).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef DEF_FRACTION
#define DEF_FRACTION
 
#include <iostream>
 
class Fraction
{
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, Fraction.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.

Fraction.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 Fraction.h.
1
#include "Fraction.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 :
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 Fraction
{
public:
 
private:
 
   int m_numerateur;      //Le numrateur de la fraction
   int m_denominateur;    //Le dnominateur 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, 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 [math=inline]\frac{2}{5}[/math] que [math=inline]\frac{4}{10}[/math] 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 [math=inline]\frac{a}{b}[/math], il faut calculer le plus grand commun diviseur de [math=inline]a[/math] et [math=inline]b[/math] puis diviser [math=inline]a[/math] et [math=inline]b[/math] par ce nombre. Par exemple, le PGCD de [math=inline]4[/math] et [math=inline]10[/math] est [math=inline]2[/math], ce qui veut dire que l'on peut simplifier les numérateurs et dénominateurs de [math=inline]\frac{4}{10}[/math] par [math=inline]2[/math], ce qui nous fait bien [math=inline]\frac{2}{5}[/math].

Calculer le pgdc n'est pas une opération facile. Je vous propose donc une fonction pour le faire. Je vous invite à l'ajouter dans votre fichier Fraction.cpp.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int pgcd(int a, int b)
{
   if(0 == a)
   {
       return b;
   }
 
   while(a != b)
   {
       if(a > b)
       {
           a -= b;
       }
       else
       {
           b -= a;
       }
   }
   return a;
}

Et à ajouter le prototype correspondant dans Fraction.h :
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef DEF_FRACTION
#define DEF_FRACTION
 
#include <iostream>
 
class Fraction
{
  //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.
1
2
3
4
Fraction::Fraction(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 :
1
2
3
4
5
6
7
8
9
Fraction::Fraction(int entier)
   :m_numerateur(entier), m_denominateur(1)
{
}
 
Fraction::Fraction()
   :m_numerateur(0), m_denominateur(1)
{
}

Il fallait se rappeler que le nombre [math=inline]5[/math] s'écrit comme la fraction [math=inline]\frac{5}{1}[/math] et [math=inline]0[/math] comme [math=inline]\frac{0}{1}[/math].
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.
1
2
3
4
5
ostream& operator<<(ostream& flux, Fraction const& fraction)
{
   fraction.affiche(flux);
   return flux;
}

Et pour la méthode affiche(), je vous propose cette version :
1
2
3
4
5
6
7
8
9
10
11
void Fraction::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+().
1
2
3
4
5
6
Fraction operator+(Fraction const& a, Fraction const& b)
{
   Fraction 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 :

[math]\frac{a}{b} + \frac{c}{d} = \frac{a\cdot d + b\cdot c}{b\cdot d}[/math]
1
2
3
4
5
6
7
Fraction& Fraction::operator+=(Fraction 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 :

[math]\frac{a}{b} \cdot \frac{c}{d} = \frac{a \cdot c}{b \cdot d}[/math]

Je vais garder mes livres de 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. ;)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Fraction operator*(Fraction const& a, Fraction const& b)
{
   Fraction copie(a);
   copie*=b;
   return copie;
}
 
Fraction& Fraction::operator*=(Fraction 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool Fraction::estEgal(Fraction const& autre) const
{
   if(m_numerateur == autre.m_numerateur && m_denominateur == autre.m_denominateur)
       return true;
   else
       return false;
}
 
bool operator==(Fraction const& a, Fraction const& b)
{
   if(a.estEgal(b))
       return true;
   else
       return false;
}
 
bool operator!=(Fraction const& a, Fraction 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 :

[math]\frac{a}{b} < \frac{c}{d} \Longleftrightarrow a\cdot d < b \cdot c[/math]

Cette relation peut être traduite en C++ pour obtenir le corps de la méthode estPlusPetitQue() :
1
2
3
4
5
6
7
bool Fraction::estPlusPetitQue(Fraction 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 >=.
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<(Fraction const& a, Fraction const& b) //Vrai si a<b donc si a est plus petit que a
{
   if(a.estPlusPetitQue(b))
       return true;
   else
       return false;
}
 
bool operator>(Fraction const& a, Fraction 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<=(Fraction const& a, Fraction 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>=(Fraction const& a, Fraction 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 quelle 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.
1
2
3
4
5
6
7
void Fraction::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*=() :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Fraction& Fraction::operator+=(Fraction 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;    
}
 
Fraction& Fraction::operator*=(Fraction 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 Fraction avec les valeurs [math=inline]\frac{4}{8}[/math] par exemple. Il faut donc aussi appeler la méthode dans le constructeur qui prend deux arguments.
1
2
3
4
5
Fraction::Fraction(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.

Fraction.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 Fraction
{
public:
 
   //Constructeurs
   Fraction(int num, int den);
   Fraction(int nombre);
   Fraction();
 
   //Affichage
   void affiche(std::ostream& flux) const;
 
   //Oprateurs arithmtiques
   Fraction& operator+=(Fraction const& autre);
   Fraction& operator*=(Fraction const& autre);
 
   //Mthodes de comparaison
   bool estEgal(Fraction const& autre) const;
   bool estPlusPetitQue(Fraction const& autre) const;
 
private:
 
   int m_numerateur;      //Le numrateur de la fraction
   int m_denominateur;    //Le dnominateur de la fraction
 
   // Simplifie une fraction
   void simplifie();
 
};
 
//Oprateur d'injection dans un flux
std::ostream& operator<<(std::ostream& flux, Fraction const& fraction);
 
//Oprateurs arithmtiques
Fraction operator+(Fraction const& a, Fraction const& b);
Fraction operator*(Fraction const& a, Fraction const& b);
 
//Oprateurs de comparaison
bool operator==(Fraction const& a, Fraction const& b);
bool operator!=(Fraction const& a, Fraction const& b);
bool operator<(Fraction const& a, Fraction const& b);
bool operator>(Fraction const& a, Fraction const& b);
bool operator>=(Fraction const& a, Fraction const& b);
bool operator<=(Fraction const& a, Fraction const& b);
 
//Calcul du PGCD
int pgcd(int a, int b);
 
#endif

Fraction.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
160
161
162
163
164
165
166
167
168
169
#include "Fraction.h"
using namespace std;
 
//Constructeurs
Fraction::Fraction(int num, int den)
   :m_numerateur(num), m_denominateur(den)
{
   simplifie();
}
 
Fraction::Fraction(int entier)
   :m_numerateur(entier), m_denominateur(1)
{
}
 
Fraction::Fraction()
   :m_numerateur(0), m_denominateur(1)
{
}
 
//Affichage
void Fraction::affiche(ostream& flux) const
{
   if(m_denominateur == 1)
   {
       flux << m_numerateur;
   }
   else
   {
       flux << m_numerateur << '/' << m_denominateur;
   }
}
 
//Oprateur d'injection dans un flux
ostream& operator<<(ostream& flux, Fraction const& fraction)
{
   fraction.affiche(flux);
   return flux;
}
 
//Oprateurs arithmtiques
Fraction& Fraction::operator+=(Fraction const& autre)
{
   m_numerateur = autre.m_denominateur * m_numerateur + m_denominateur * autre.m_numerateur;
   m_denominateur = m_denominateur * autre.m_denominateur;
 
   simplifie();
   return *this;
}
 
Fraction& Fraction::operator*=(Fraction const& autre)
{
   m_numerateur *= autre.m_numerateur;
   m_denominateur *= autre.m_denominateur;
 
   simplifie();
   return *this;
}
 
//Mthodes de comparaison
bool Fraction::estEgal(Fraction const& autre) const
{
   if(m_numerateur == autre.m_numerateur && m_denominateur == autre.m_denominateur)
       return true;
   else
       return false;
}
 
bool Fraction::estPlusPetitQue(Fraction const& autre) const
{
   if(m_numerateur * autre.m_denominateur < m_denominateur * autre.m_numerateur)
       return true;
   else
       return false;
}
 
//Simplification
void Fraction::simplifie()
{
   int nombre=pgcd(m_numerateur, m_denominateur); //Calcul du PGCD
 
   m_numerateur /= nombre;     //Et on simplifie
   m_denominateur /= nombre;
}
 
//Oprateurs externes
Fraction operator+(Fraction const& a, Fraction const& b)
{
   Fraction copie(a);
   copie+=b;
   return copie;
}
 
Fraction operator*(Fraction const& a, Fraction const& b)
{
   Fraction copie(a);
   copie*=b;
   return copie;
}
 
bool operator==(Fraction const& a, Fraction const& b)
{
   if(a.estEgal(b))
       return true;
   else
       return false;
}
 
bool operator!=(Fraction const& a, Fraction const& b)
{
   if(a.estEgal(b))
       return false;
   else
       return true;
}
 
bool operator<(Fraction const& a, Fraction const& b) //Vrai si a<b donc si a est plus petit que a
{
   if(a.estPlusPetitQue(b))
       return true;
   else
       return false;
}
 
bool operator>(Fraction const& a, Fraction 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<=(Fraction const& a, Fraction 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>=(Fraction const& a, Fraction 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)
{
   if(0 == a)
   {
       return b;
   }
 
   while(a != b)
   {
       if(a > b)
       {
           a -= b;
       }
       else
       {
           b -= a;
       }
   }
   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.

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 Fraction 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, [math=inline]\frac{1}{-4}[/math] devra être automatiquement converti en [math=inline]\frac{-1}{4}[/math]. 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 :
    1
        Fraction operator-(Fraction 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 Fraction. Pensez à inclure l'en-tête cmath. ;)

Je pense que cela va vous demander pas mal de travail. 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 fournit 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 du 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



Distribué et adapté par David
Consulté 8981 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 300 jours 3 heures 12 minutes
Espace libre : 1518 Mo
Dernière sauvegarde : 19/09/2019
Taille de la sauvegarde : 1115 Mo


5508013 pages ont été consultées sur le site !
Dont 4494 pages pendant les 24 dernières heures.

Page générée en 0.717 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2019 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.