Lektion 53:

Die Klasse Rational

 Die Klasse Rational stellt Bruchzahlen dar. Der erste Ansatz sieht so aus:

class Rational
{
private:
   int zaehler;
   int nenner;
public:
   Rational(int zaehler,int nenner)
   {
   };
};

Jetzt würde ich zu gerne im Konstruktor schreiben:

   zaehler=zaehler;

Das geht leider nicht. Ich muß irgendwie unterscheiden, welche Variable zaehler ich eigentlich meine. Dazu nenne ich eine um (schweren Herzens). Ich entscheide mich, die Attribute mit "m_" beginnen zu lassen (dabei steht das m für member).

class Rational
{
private:
   int m_zaehler;
   int m_nenner;
public:
   Rational(int zaehler,int nenner)
   {
      m_zaehler=zaehler;
      m_nenner=nenner;
   };
};

Wir wollen die Bruchzahlen immer in gekürzter Form vorliegen haben. Um einen Bruch zu kürzen, teilt man Zähler und Nenner durch den größten gemeinsamen Teiler. Da Brüche immer nur in der gekürzten Form vorliegen sollen, ist die Methode kuerzen() für alles außerhalb der Klasse nicht von Interesse. Darum wird diese Methode in die private-Sektion geschrieben.

   void kuerzen(void)
   {
      int ggt=ggt(m_zaehler,m_nenner);
      m_zaehler/=ggt;
      m_nenner/=ggt;
   };

 Und schon wieder ein Fehler! Die Variable ggt verdeckt die Funktion ggt. Das macht normalerweise nichts, aber hier geht das nicht, weil ich die Funktion ggt brauche. Also nenne ich die Variable ggt um.

   void kuerzen(void)
   {
      int ggtVar=ggt(m_zaehler,m_nenner);
      m_zaehler/=ggtVar;
      m_nenner/=ggtVar;
   };

Bis jetzt sieht das Programm so aus:

#include <iostream.h>
#include "mymath.h" // für ggt()

class Rational
{
private:
   int m_zaehler;
   int m_nenner;
   
   void kuerzen(void)
   {
      int ggtVar=ggt(m_zaehler,m_nenner);
      m_zaehler/=ggtVar;
      m_nenner/=ggtVar;
   };
public:
   Rational(int zaehler,int nenner)
   {
      m_zaehler=zaehler;
      m_nenner=nenner;
      kuerzen();
   };
   void print()
   {
        cout<<m_zaehler<<'/'<<m_nenner;
   };
};
void main()
{
   Rational a(2,4);
   cout<<"a=";
   a.print();
   cout<<endl;

   Rational b(3,5);
   cout<<"b=";
   b.print();
   cout<<endl;
};

Soweit funktioniert die Klasse Rational. Richtig interessant wird es aber erst, wenn man diese Klasse auch zum Rechnen benutzen kann. Es müssen also Methoden geschrieben werden, die für die arithmetischen Operatoren (+,-,* und /) zuständig sind.

   Rational multipliziereDichMit(Rational dieAndereZahl)
   {
      int resultZaehler=m_zaehler* dieAndereZahl.m_zaehler;
      int resultNenner=m_nenner* dieAndereZahl.m_nenner;
      Rational result(resultZaehler,resultNenner);
      return result;
   };

Die main-Funktion kann jetzt um die folgenden Zeilen erweitert werden:

   Rational c=a.multipliziereDichMit(b);
   cout<<"c=";
   c.print();
   cout<<endl;

In der Methode multipliziereDichMit sind zwei Objekte der Klasse Rational verfügbar:

  • das Objekt, auf der die Methode aufgerufen wurde (also die Variable, die beim Aufruf links vom Punkt steht, hier a) und
  • das Objekt, das beim Aufruf in den Klammern übergeben wurde (die andere Zahl, hier b).

In der Zeile

      Rational result(resultZaehler,resultNenner);

wird ein neues Objekt der Klasse Rational erzeugt. Dieses Objekt soll das Ergebnis der Rechnung sein. Zum Erzeugen dieses neuen Objekts wird der Konstruktor der Klasse Rational aufgerufen. Das ist sehr praktisch, weil in diesem Konstruktor auch gleich die kuerzen-Methode aufgerufen wird.

Mit

      return result;

wird das gerade neu erzeugte Objekt zurückgegeben. Im Funktionskopf dieser Methode steht ja vor multipliziereDichMit der Typ Rational. Diese Methode gibt also Objekte vom Typ Rational zurück. Genau, wie es sein soll.

Doch leider ist das Rechnen mit solchen unhandlichen Funktionen unangenehm. Viel angenehmer wäre es doch, wenn man in der main-Funktion

   Rational c=a*b;

schreiben könnte. Und das geht sogar!

 Dazu werden die arithmetischen Operatoren überladen. In C++ kann man nämlich Funktionen schreiben, die aufgerufen werden, wenn ein Operator verwendet werden soll. Wie die Methode zu funktionieren hat, ist klar, denn die Implementierung ist natürlich die der multipliziereDichMit-Methode. Nur der Name ist etwas besonderes. Hier ist das Syntax-Highlighting etwas verwirrend. Sehen Sie "operator*" als ein Wort an.

   Rational operator*(Rational rhs)
   {
      int resultZaehler=m_zaehler*rhs.m_zaehler;
      int resultNenner=m_nenner*rhs.m_nenner;
      Rational result(resultZaehler,resultNenner);
      return result;
   };

Der Name operator* ist ganz speziell dafür reserviert, den Operator * zu überladen. Wenn dies geschehen ist, dann macht der Compiler automatisch aus dem Ausdruck

a*b

den Ausdruck

a.operator*(b)

und dieser Ausdruck unterscheidet sich in nichts (außer dem Namen) von einem ganz normalen Funktionsaufruf wie

a.multipliziereDichMit(b);

Ab jetzt kann man mit der neuen Klasse Rational angenehm arbeiten.

void main()
{
   Rational a(2,4);
   cout<<"a=";
   a.print();
   cout<<endl;

   Rational b(1,3);
   cout<<"b=";
   b.print();
   cout<<endl;

   Rational c=a*b;
   cout<<"c=";
   c.print();
   cout<<endl;
};

Natürlich wäre es genauso möglich, weitere Operatoren zu überladen. Sie müssen sich nur vorstellen, ganz normale Methoden zu schreiben, die eben den besonderen Namen haben und darum auch auf besondere Weise aufgerufen werden dürfen.

Einsendungen:

Was bedeutet rhs?

Übung:

Vervollständigen Sie die Klasse Rational so, daß sie auch die Operatoren +, -, und / hat.

Einsendungen:

Lösungsvorschlag


Falls Ihnen Fehler im Text auffallen oder Sie Verbesserungsvorschläge haben, dann schicken Sie mir bitte eine Mail. Ich werde mich dann sofort darum kümmern.
[aktuelle Version] [inhalt] [index]      [Fehlerkorrektur, Verbesserungsvorschlag]

© Volkard Henkel <volkard@normannia.de>, last update: 08/25/00