Série 19 :
Surcharge d'opérateurs.
Exercice 0 : illustration de la surcharge dans le cas des polynômes (POO, niveau 0)
Introduction
Le but de cet exercice est d'illustrer les notions de constructeur et de surcharge d'opérateur.
On cherche à définir une classe Polynome permettant de représenter et manipuler des polynômes réels.
Définition de la classe
Commencez par ouvrir le fichier Polynome.cc et définissez y la classe Polynome contenant un tableau dynamique de nombres réels.
Ce tableau stockera les coefficients du polynôme.
Par exemple 2.5 X4 + X + 9.2 sera stocké sous la forme du tableau de coefficients {9.2, 1.0, 0, 0, 2.5}.
Ajoutez y la méthode publique degre() retournant le degré du polynôme :
#include <vector> using namespace std; typedef int Degre; class Polynome { public: Degre degre() const { return p.size()-1; } private: vector<double> p; };
Constructeurs
Il nous faut maintenant définir les constructeurs. Il nous faut au moins le constructeur par défaut et le constructeur de copie :
class Polynome { public: Polynome(); // constructeur par defaut Polynome(Polynome const &); // copy-constructor Degre degre() const { return p.size()-1; } private: vector<double> p; };
Pour le construteur par défaut, choisissons que ce soit le polynôme nul qui soit construit:
Polynome::Polynome() : p(1, 0.0) {}
Concernant le constructeur de copie, pas de surprise, on copie les données en utilisant simplement le constructeur de copie de la classe vector :
Polynome::Polynome(const Polynome& autre) : p(autre.p) {}
On peut maintenant aussi ajouter d'autres constructeurs « pratiques », comme par exemple le plongement du corps des réels dans l'anneau des polynômes réels (c.-à-d. que tout nombre réel peut être vu comme un polynôme de degré 0), ce qui permet par exemple d'écrire :
Polynome un_polynome(2.3);
On écrirait alors pour cela :
class Polynome { public: Polynome(); // constructeur par defaut Polynome(Polynome const &); // copy-constructor Polynome (double); // plongement du corps des reels
class Polynome { public: Polynome (double = 0.0); //ici Polynome(Polynome const &); ...
Sa définition ne change pas fondamentalement du constructeur par défaut précédent :
Polynome::Polynome(double x) : p(1, x) {}
On peut pour finir avec les constructeurs ajouter une façon de déclarer un monôme de degré quelconque. Par exemple p=3X^2 pourrait s'écrire :
Polynome p(3.0, 2);
Ceci est possible en complétant le constructeur par défaut :
class Polynome { public: Polynome(double coef = 0.0, unsigned int degre = 0); Polynome(Polynome const &); // copy-constructor ...
Polynome::Polynome(double coef, unsigned int deg) : p(deg+1, 0.0) // garantit que le degr'e de p est deg { p[deg] = coef; // change la valeur du coef de plus haut degre }
Pour résumer, à ce stade nous avons :
#include <vector> using namespace std; typedef int Degre; class Polynome { public: // constructeurs Polynome(double coef = 0.0, unsigned int degre = 0); Polynome(Polynome const &); // copy-constructor // methodes publiques Degre degre() const { return p.size()-1; } private: // attributs privés vector<double> p; }; Polynome::Polynome(double coef, unsigned int deg) : p(deg+1, 0.0) // garantit que le degr'e de p est deg { p[deg] = coef; // change la valeur du coef de plus haut degre } Polynome::Polynome(const Polynome& autre) : p(autre.p) {}
Opérateurs
Ajoutons maintenant quelques opérateurs simples à nos Polynomes.
Nous ne présenterons ici que la multiplication et l'affichage.
Commençons par l'affichage, c'est-à-dire l'opérateur << de la classe ostream (l'opérateur qui s'applique à cout). Cet opérateur est donc un opérateur externe à la classe Polynome (puisque son opérande de gauche est cout, il devrait être interne à la classe ostream).
De plus, comme cet opérateur doit afficher le contenu du polynôme, il doit avoir accès à l'attribut vector<double> p;. Mais comme cet attribut est privé et que nous n'avons pas fait de méthode «get» correspondante (ce qui serait la meilleure solution, beaucoup plus «propre» !), il nous faut déclarer cet opérateur comme friend de la classe Polynome.
Tout ceci se fait de la façon suivante :
#include <iostream> ... class Polynome { public: ... // comme avant friend ostream& operator<<(ostream&, const Polynome&); ... }; ... void affiche_coef(ostream& out, double c, Degre puissance, bool signe = true) { if (c != 0) { if (signe && (c > 0.0)) out << "+"; out << c; if (puissance > 1) out << "*X^" << puissance; else if (puissance == 1) out << "*X"; } } ostream& operator<<(ostream& sortie, const Polynome& polynome) { // comme la fonction affiche du cours 13 // plus haut degré : pas de signe + devant Degre i(polynome.degre()); affiche_coef(sortie, polynome.p[i], i, false); // degré de N à 0 : +a*X^i for (i--; i >= 0; i--) affiche_coef(sortie, polynome.p[i], i); // degré 0 : afficher quand meme le 0 si rien d'autre if ((polynome.degre() == 0) && (polynome.p[0] == 0.0)) sortie.operator<<(0); return sortie; }
On peut maintenant tester nos développements à ce stade, par exemple en ajoutant la fonction main
int main() { Polynome p(3.2, 4); cout << "p=" << p << endl; return 0; }
Voyons maintenant la multiplication.
Il y a plusieurs cas qui
nous intéressent :
- multiplication de 2 polynômes
- multiplication par un réel (sans passer par 1)
Pour 1 nous disposons de 2 opérateurs : * et *=. Commençons par le premier.
* nous permet d'écrire des choses comme r = p *
q;,
et doit donc prendre un polynôme (de plus : q) en
argument et retourner la valeur du calcul après
l'opération.
Son prototype devient donc tout naturellement :
Polynome operator*(Polynome q)
Plusieurs remarques cependant :
- cet opérateur retourne le résultat du calcul mais ne modifie
pas l'objet concerné :
dans r = p * q;, c'est-à-dire r = p.operator*(q);, p n'est pas modifié
donc c'est une méthodes const :Polynome operator*(Polynome q) const;
- il est beaucoup plus efficace de passer q par référence
que par valeur pour éviter des copies intermédiaire inutiles et coûteuses.
Polynome operator*(Polynome& q) const;
Polynome operator*(Polynome& q) const
- mais q n'est pas modifié par cet opérateur, il est donc
const :
Polynome operator*(const Polynome& q) const;
La définition de cet opérateur est ensuite sans difficulté (définition classique de la multiplication de polynômes) :
Polynome Polynome::operator*(const Polynome& q) const { Polynome r(0.0); // Prépare la place pour le polynôme résultat (de degre degre()+q.degre()). // Notez que r = 0, donc il contient déjà un monome (0.0 de degre 0) // et donc il suffit d'aller jusqu'au degré 1 (et non 0) pour avoir // le bon nombre de coefficients for (Degre i(degre() + q.degre()); i > 1; i--) r.p.push_back(0.0); // fait le calcul for (Degre i(0); i <= degre(); ++i) for (Degre j(0); j <= q.degre(); ++j) r.p[i+j] += p[i] * q.p[j]; // retourne le resultat return r; }
Pour le second opérateur, *=, il nous permet d'écrire p *= q;, mais doit aussi nous permettre (norme ISO C++) d'écrire des choses comme r = s + (p *= q);, bien que je vous déconseille fortement d'utiliser ce genre d'expressions.
Donc *= doit aussi retourner la valeur du calcul après l'opération.
Le plus simple et le plus efficace pour ceci est encore d'utiliser les références et de choisir comme prototype :
Polynome& operator*=(const Polynome&);
La définition de cet opérateur peut ensuite utiliser l'opérateur * défini ci-dessus et l'opérateur = qui nous reste à définir :
Polynome& Polynome::operator*=(const Polynome& q) { return (*this = *this * q); }
Note : dans le cas général on fait plutôt le contraire : on définit l'opérateur Op en utilisant l'opérateur Op= avec la définition suivante
Classe Classe::operatorOp(const Classe& arg) { return (Classe(*this) Op= arg); }
Nous ne l'avons pas fait ici car la multiplication de polynômes nécéssite de toutes façons un polynôme intermédiaire (r ci-dessus) pour le calcul, même dans le cas de l'opérateur *=. On ne gagne donc rien à procéder comme cela ici.
(Si vous ne comprennez pas cette note, laissez tomber. Ce n'est pas fondamental.)
Terminons donc cette première étape par la définition de l'opérateur = (sans difficulté). Nous avons à ce stade :
#include <iostream> #include <vector> using namespace std; typedef int Degre; class Polynome { public: // constructeurs Polynome(double coef = 0.0, unsigned int degre = 0); Polynome(Polynome const &); // copy-constructor // methodes publiques Degre degre() const { return p.size()-1; } // operateurs internes Polynome operator*(const Polynome& q) const; Polynome& operator*=(const Polynome& q); Polynome& operator=(const Polynome& q); // operateurs externes friend ostream& operator<<(ostream&, const Polynome&); private: // attributs privés vector<double> p; }; // ====================================================================== // definition des methodes // ---------------------------------------------------------------------- Polynome::Polynome(double coef, unsigned int deg) : p(deg+1, 0.0) // garantit que le degré de p est deg { p[deg] = coef; // change la valeur du coef de plus haut degre } // ---------------------------------------------------------------------- Polynome::Polynome(Polynome const & autre) : p(autre.p) {} // ---------------------------------------------------------------------- Polynome Polynome::operator*(Polynome const & q) const { Polynome r(0.0); // Prépare la place pour le polynôme résultat (de degre degre()+q.degre()). // Notez que r = 0, donc il contient déjà un monome (0.0 de degre 0) // et donc il suffit d'aller jusqu'au degré 1 (et non 0) pour avoir // le bon nombre de coefficients for (Degre i(degre() + q.degre()); i >= 1; --i) r.p.push_back(0.0); // fait le calcul for (Degre i(0); i <= degre(); ++i) for (Degre j(0); j <= q.degre(); ++j) r.p[i+j] += p[i] * q.p[j]; // retourne le resultat return r; } // ---------------------------------------------------------------------- Polynome& Polynome::operator*=(Polynome const & q) { return (*this = *this * q); } // ---------------------------------------------------------------------- Polynome& Polynome::operator=(Polynome const & q) { if (&q != this) { p = q.p; } return *this; } // ====================================================================== // fonctions intermédiaires et operateurs externes // ---------------------------------------------------------------------- void affiche_coef(ostream& out, double c, Degre puissance, bool signe = true) { if (c != 0) { if (signe && (c > 0.0)) out << "+"; out << c; if (puissance > 1) out << "*X^" << puissance; else if (puissance == 1) out << "*X"; } } // ---------------------------------------------------------------------- ostream& operator<<(ostream& sortie, const Polynome& polynome) { // plus haut degré : pas de signe + devant Degre i(polynome.degre()); affiche_coef(sortie, polynome.p[i], i, false); // degré de N à 0 : +a*X^i for (i--; i >= 0; i--) affiche_coef(sortie, polynome.p[i], i); // degré 0 : afficher quand meme le 0 si rien d'autre if ((polynome.degre() == 0) && (polynome.p[0] == 0.0)) sortie.operator<<(0); return sortie; } // ====================================================================== // ---------------------------------------------------------------------- int main() { Polynome p(3.2, 4); cout << "p=" << p << endl; Polynome q(1.1, 2), r; r = p * q; cout << p << " * " << q << " = " << r << endl; return 0; }
Terminons maintenant par la multiplication par les réels.
Nous allons pour cela définir les opérateurs
Polynome& operator*=(const double); Polynome operator*(const double) const; friend Polynome operator*(const double, const Polynome&);
Les deux premiers sont des opérateurs internes, utilisés respectivement pour des opérations de type p *= x et q = p * x, où p et q sont des polynômes et x est un double.
Le dernier est un opérateur externe permettant d'effectuer des opération du type q = x * p.
Leurs définitions ne présentent pas non plus de problème majeur, les deux derniers utilisant le premier de ces opérateurs :
---------------------------------------------------------------------- Polynome& Polynome::operator*=(const double x) { for (Degre i(0); i <= degre(); ++i) p[i] *= x; return *this; } // ---------------------------------------------------------------------- Polynome Polynome::operator*(const double x) const { return Polynome(*this) *= x; } // ---------------------------------------------------------------------- Polynome operator*(const double x, const Polynome& p) { return Polynome(p) *= x; }
On peut tester en ajoutant
r *= 2.0; cout << " * 2" << " = " << r << endl;
Code source
Pour avoir accès / sauvegarder le programme complet, cliquez ici.