C++-Programmierung/ Brüche/ Die Rechenoperationen

Aus Wikibooks
Zur Navigation springen Zur Suche springen


Addition, Subtraktion, Multiplikation und Division, das sind 4 Rechenoperationen. C++ bietet aber die mit der Zuweisung kombinierten Kurzschreibweisen, womit wir insgesamt auf 8 kommen.

Addition[Bearbeiten]

Um zwei Brüche zu addieren, müssen die Nenner gleich sein. Wenn wir bereits Brüche haben, die sich nicht weiter kürzen lassen, (und dafür sorgt unsere Klasse,) dann erhalten wir wiederum einen unkürzbaren Bruch, wenn wir mit dem kgV erweitern. Das Ganze sieht also so aus:

Nuvola-inspired-terminal.svg

Pseudocode


1 ErgebnisNenner  = kgV(Bruch1Nenner, Bruch2Nenner);
2 ErgebnisZaehler = Bruch1Zaehler * (ErgebnisNenner / Bruch1Nenner) +
3     Bruch2Zaehler * (ErgebnisNenner / Bruch2Nenner);

Subtraktion[Bearbeiten]

Für die Subtraktion gelten die gleichen Regeln wie bei der Addition.

Nuvola-inspired-terminal.svg

Pseudocode


1 ErgebnisNenner  = kgV(Bruch1Nenner, Bruch2Nenner);
2 ErgebnisZaehler = Bruch1Zaehler * (ErgebnisNenner / Bruch1Nenner) -
3     Bruch2Zaehler * (ErgebnisNenner / Bruch2Nenner);

Multiplikation[Bearbeiten]

Bei der Multiplikation werden einfach die Zähler und die Nenner multipliziert. Danach muss der Bruch wieder gekürzt werden.

Nuvola-inspired-terminal.svg

Pseudocode


1 ErgebnisZaehler = Bruch1Zaehler * Bruch2Zaehler;
2 ErgebnisNenner  = Bruch1Nenner  * Bruch2Nenner;
3 kuerzen();

Division[Bearbeiten]

Die Division stimmt mit der Multiplikation fast überein, aber statt die Zähler und die Nenner miteinander zu multiplizieren, werden sie gegeneinander multipliziert.

Nuvola-inspired-terminal.svg

Pseudocode


1 ErgebnisZaehler = Bruch1Zaehler * Bruch2Nenner;
2 ErgebnisNenner  = Bruch1Nenner  * Bruch2Zaehler;
3 kuerzen();

Kombination[Bearbeiten]

Da C++ wie schon gesagt neben den normalen Rechenoperatoren noch die mit der Zuweisung kombinierten zur Verfügung stellt, werden wir einen kleinen Trick anwenden, um uns doppelte Arbeit zu ersparen. Wir werden die eigentlichen Rechenoperationen in den Zuweisungskombioperatoren implementieren und dann innerhalb der normalen Rechenoperatoren temporäre Objekte anlegen, für welche wir die Kombinationsoperatoren aufrufen. Das ist ein übliches und viel angewandtes Verfahren, welches einige Vorteile zu bieten hat. Sie sparen sich die doppelte Schreibarbeit und müssen sich bei Veränderungen nur um die Kombioperatoren kümmern, da sich die anderen ja genauso verhalten.

Die umgekehrte Variante, also von den Kombioperatoren die normalen aufrufen zu lassen, ist übrigens nicht zu empfehlen, da die Kombinationsoperatoren immer schneller sind, sie benötigen schließlich keine temporären Objekte. Außerdem ist es in vielen Klassen nötig, die normalen Rechenoperatoren außerhalb der Klasse zu deklarieren. Wenn sie nicht als friend deklariert sind, haben sie keinen Zugriff auf die privaten Member der Klasse, rufen Sie dagegen die Kombioperatoren auf, brauchen Sie gar keinen Zugriff.

Die normalen Rechenoperatoren werden als Nicht-Member implementiert. Dies ist nötig, damit für beide Parameter eine implizite Typumwandlung erfolgen kann. Andernfalls könnten Sie zwar Bruch(1, 2)+3 schreiben, aber nicht 3+Bruch(1, 2) und das ist natürlich nicht gewollt. Die Deklaration als Nicht-Member hat zur Folge, das wir zwei Parameter übergeben müssen. Der erste wird als Kopie („Call by value“) übergeben, da wir ja sowieso eine temporäre Kopie benötigen. Den Zweiten übergeben wir wie üblich als Referenz auf const. Da die Implementierung so kurz ist, sind diese Funktionen natürlich inline. Der Rückgabetyp lautet immer Bruch const. Dies verhindert, dass Sie so etwas wie a + b = 4 schreiben können, obwohl doch a + b == 4 gemeint war.

Abschluss[Bearbeiten]

So, nun haben Sie wirklich genug Theorie gehört, es wird Zeit zu zeigen, wie das Ganze im Quelltext aussieht. Der Code lässt sich zwar noch nicht ausführen, weil der Konstruktor noch nicht definiert ist, aber es lohnt sich trotzdem, schon mal einen Blick darauf zu werfen.

Nuvola-inspired-terminal.svg
 1 unsigned int ggT(unsigned int a, unsigned int b){
 2     if(b == 0)                 // Wenn b gleich 0
 3         return a;              // ggT gefunden
 4     else return ggT(b, a % b); // andernfalls weitersuchen
 5 }
 6 
 7 unsigned int kgV(unsigned int a, unsigned int b){
 8     // Das kgV zweier Zahlen, ist ihr Produkt geteilt durch ihren ggT
 9     return a * b / ggT(a, b);
10 }
11 
12 class Bruch{
13 public:
14     Bruch(int zaehler = 0, unsigned int nenner = 1); // noch nicht definiert
15 
16     int          zaehler()const { return zaehler_; }  // Gibt Zähler zurück
17     unsigned int nenner()const  { return nenner_; }   // Gibt Nenner zurück
18 
19     Bruch& operator+=(Bruch const& lvalue);
20     Bruch& operator-=(Bruch const& lvalue);
21     Bruch& operator*=(Bruch const& lvalue);
22     Bruch& operator/=(Bruch const& lvalue);
23 
24 private:
25     void kuerzen();                                  // kürzt weitestmöglich
26 
27     int          zaehler_;
28     unsigned int nenner_;
29 };
30 
31 
32 // Diese Methoden erstellen eine temporäre Kopie (lhs) ihres Objekts, führen
33 // die Rechenoperation auf ihr aus und geben sie dann zurück
34 inline Bruch const operator+(Bruch lhs, Bruch const &rhs){ return lhs += rhs; }
35 inline Bruch const operator-(Bruch lhs, Bruch const &rhs){ return lhs -= rhs; }
36 inline Bruch const operator*(Bruch lhs, Bruch const &rhs){ return lhs *= rhs; }
37 inline Bruch const operator/(Bruch lhs, Bruch const &rhs){ return lhs /= rhs; }
38 
39 void Bruch::kuerzen(){
40     const unsigned int tmp = ggT(zaehler_, nenner_);     // ggT in tmp speichern
41     zaehler_ /= tmp; // Zähler durch ggT teilen
42     nenner_  /= tmp; // Nenner durch ggT teilen
43 }
44 
45 Bruch& Bruch::operator+=(Bruch const &lvalue){
46     const unsigned int tmp = kgV(nenner_, lvalue.nenner_);
47     zaehler_ = zaehler_ * (tmp / nenner_) + lvalue.zaehler_ * (tmp / lvalue.nenner_);
48     nenner_  = tmp;
49     return *this; // Referenz auf sich selbst zurückgeben
50 }
51 
52 Bruch& Bruch::operator-=(Bruch const &lvalue){
53     const unsigned int tmp = kgV(nenner_, lvalue.nenner_);
54     zaehler_ = zaehler_ * (tmp / nenner_) - lvalue.zaehler_ * (tmp / lvalue.nenner_);
55     nenner_  = tmp;
56     return *this; // Referenz auf sich selbst zurückgeben
57 }
58 
59 Bruch& Bruch::operator*=(Bruch const &lvalue){
60     zaehler_ *= lvalue.zaehler_;
61     nenner_  *= lvalue.nenner_;
62     kuerzen();    // Bruch wieder kürzen
63     return *this; // Referenz auf sich selbst zurückgeben
64 }
65 
66 Bruch& Bruch::operator/=(Bruch const &lvalue){
67     zaehler_ *= lvalue.nenner_;
68     nenner_  *= lvalue.zaehler_;
69     kuerzen();    // Bruch wieder kürzen
70     return *this; // Referenz auf sich selbst zurückgeben
71 }