Cet exercice correspond à l'exercice «pas à pas» page 122 de l'ouvrage C++ par la pratique (3e édition, PPUR).

Le but de cet exercice est de reprendre en détail un exemple illustrant 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 tel que, par exemple, 3X²+2x+1.

Nous représentons un polynôme simplement par le tableau de ses coefficients : le polynome 3X²+2x+1 sera représenté par le tableau 1, 2, 3 (on commence pas le coefficient de degré le plus bas, 1 ici).

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.

Ajoutez y la méthode publique degre().

Cliquez ici pour voir/cacher le code.



Constructeurs

Il nous faut maintenant définir les constructeurs. Il nous faut au moins le constructeur par défaut : choisisons que ce soit le polynôme nul qui soit construit.

Cliquez ici pour voir/cacher le code.



Note : concernant le constructeur de copie, celui fourni automatiquement par le langage/le compilateur convient très bien. Il n'y a en effet rien de particulier à faire ici, autre que la copie usuelle des attributs ; ce que fait justement le constructeur de copie fourni par défaut.

On peut aussi ajouter d'autres constructeurs plus « 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 défaut
  Polynome(double); // plongement du corps des réels
  ...

mais on peut faire mieux et utiliser la valeur par défaut des arguments, pour fusionner le constructeur par défaut et ce dernier constructeur.

Cliquez ici pour voir/cacher le code.



Pour finir avec les constructeurs, on peut aussi ajouter une façon de déclarer un monôme de degré quelconque. Par exemple le polynôme 3X^2 pourrait s'écrire :

Polynome p(3.0, 2);

3.0 est le coefficient et 2 le degré.

Ceci est possible en complétant le constructeur par défaut :

Cliquez ici pour voir/cacher le code.



Opérateurs

Ajoutons maintenant à quelques opérateurs simples à nos Polynomes. Nous ne présenterons ici que la multiplication et l'affichage.

Affichage

Commençons par l'affichage, c'est-à-dire l'opérateur << de la classe ostream (comme 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 que nous ne souhaitons pas : mis à part ici, il n'y a pas besoin d'avoir accès à tout le tableau des valeurs), il nous faut soit déclarer cet opérateur comme friend de la classe Polynome, soit développer une méthode, publique, d'affichage que cet opérateur appelera. Choisissons cette dernière solution qui est préférable.

Essayez de le faire par vous même sans regarder la solution. Cliquez ici pour voir/cacher la solution.



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

Multiplication

Passons maintenant la multiplication.
Il y a plusieurs cas qui nous intéressent :

  1. multiplication de 2 polynômes ;
  2. multiplication par un réel (sans passer par la précédente).

Pour chacune 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.
en choisissant la surcharge externe et le prototype conseillé dans la vidéo, nous avons :

const Polynome operator*(Polynome p, const Polynome& q)

La définition de cet opérateur est ensuite sans difficulté pour ceux qui connaissent la multiplication de polynômes. Comme ce n'est pas le but ici de réviser (découvrir ?) les mathématiques, nous vous le donnons directement :

const Polynome operator*(Polynome p, const Polynome& q) {
  const Degre dp(p.degre());
  const Degre dq(q.degre());

  // Prépare la place pour le polynome résultat (de degre p.degre()+q.degre()).
  Polynome r(0.0, dp + dq);

  // fait le calcul
  for (Degre i(0); i <= dp; ++i)
    for (Degre j(0); j <= dq; ++j)
      r.p[i+j] += p.p[i] * q.p[j];

  // retourne le resultat
  return r;
}

Cet opérateur ayant par contre besoin d'accéder au contenu des Polynomes, il est nécessaire ici de le déclarer comme friend :

class Polynome {
  //...

  // la multiplication aura besoin d'accéder à p
  friend const Polynome operator*(Polynome p, const Polynome& q);
};

C'est une des raisons pour lesquelle usuellement on préfère définir l'opérateur externe (* ici) à partir de sa version interne d'auto-affectation (*= ici). Mais dans le cas présent, l'algorithme utilisé pour multiplier des polynômes, même en interne, nécessiterait de toutes façons un polynôme supplémentaire (r du code ci-dessus). Nous avons donc choisi de commencer par la version externe de la multiplication, peut être plus naturelle.

Pour le second opérateur, *=, il nous permet d'écrire p *= q;, mais doit aussi nous permettre (norme du langage 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*=(Polynome const&);

La définition de cet opérateur peut ensuite utiliser l'opérateur * défini ci-dessus :

Polynome& Polynome::operator*=(Polynome const& q) {
  return (*this = *this * q);
}

Ce code :

  • *this * q : fait la multiplication de l'instance courante (*this) par l'instance q en utilisant la multiplication externe précédemment définie ;
  • *this = .. : affecte le résultat de cette multiplication à l'instance courante ;
  • return (...) : et retourne la «valeur» (Polynome) en question.

Terminons maintenant par la multiplication par les réels.
Nous allons pour cela définir l'opérateur interne

  Polynome& operator*=(double);

et en externe :

  const Polynome operator*(double, Polynome const&);
  const Polynome operator*(const Polynome, double);

qui sont utilisés respectivement pour des opérations de type p *= x, q = p * x et q = x * p, où p et q sont des polynômes et x est un double.

Leurs définitions ne présentent pas de problème majeur.

Essayez de le faire par vous même sans regarder la solution. Cliquez ici pour voir/cacher la solution.



Voilà ! Ceci conclut ce tutoriel un peu technique. Pour que nos polynômes soient pleinement intéressants, il faudrait au minimum leur ajouter l'addition... mais j'ai peur que cela fasse un peu trop ;-)

Code source complet

Cliquez ici pour voir/cacher le code.




Dernière mise à jour le 10 mars 2014
Last modified: Mon Mar 10, 2014