In C++ will man, dass sich Objekte wie ganz normale Variablen verhalten. Dies führt dazu, dass man für Klassen auch eigene Operatoren definieren kann. Naja, eigene Operatoren ist übertrieben, aber man darf alle vorhandenen für seine Klasse verwenden.
#include <iostream>
#include <algorithm> //für swap
using namespace std;
class Rational
{
private:
int numerator;
int denominator;
public:
Rational(int numerator, int denominator=1)
: numerator(numerator), denominator(denominator)
{}
Rational(Rational const& other)
: numerator(other.numerator), denominator(other.denominator)
{}
void swap(Rational& other)
{
std::swap(numerator, other.numerator); //std::swap aus algorithm
std::swap(denominator, other.denominator);
}
Rational& operator=(Rational const& other)
{
Rational temp(other);
swap(temp);
return *this;
}
double asDouble() const
{
return static_cast<double>(numerator)/denominator;
}
void invert()
{
std::swap(numerator, denominator);
}
Rational& operator++() //++i;
{
numerator+=denominator; //erhöhe den wert des bruches um 1
return *this;
}
Rational const operator++(int) //i++;
{
Rational temp(*this);
++*this;
return temp;
}
Rational& operator--() //--i;
{
numerator-=denominator;
return *this;
}
Rational const operator--(int) //i--;
{
Rational temp(*this);
--*this;
return temp;
}
Rational& operator+=(int other)
{
numerator += (other*denominator); //um other Ganze erhöhen
return *this;
}
Rational& operator+=(Rational const& other)
{
int temp=denominator;
denominator*=other.denominator;
numerator*=other.denominator;
numerator+= (other.numerator*temp);
return *this;
}
Rational& operator-=(int other)
{
numerator -= (other*denominator); //um other Ganze verringern
return *this;
}
Rational& operator-=(Rational const& other)
{
int temp=denominator;
denominator*=other.denominator;
numerator*=other.denominator;
numerator-= (other.numerator*temp);
return *this;
}
bool equal(Rational const& other) const
{
return asDouble() == other.asDouble();
}
bool less(Rational const& other) const
{
return asDouble() < other.asDouble();
}
bool greater(Rational const& other) const
{
return asDouble() > other.asDouble();
}
};
Rational const operator+(Rational const& a, Rational const& b)
{
Rational temp(a);
temp+=b;
return temp;
}
Rational const operator-(Rational const& a, Rational const& b)
{
Rational temp(a);
temp-=b;
return temp;
}
bool operator==(Rational const& a, Rational const& b)
{
return a.equal(b);
}
bool operator<(Rational const& a, Rational const& b)
{
return a.less(b);
}
bool operator>(Rational const& a, Rational const& b)
{
return a.greater(b);
}
bool operator<=(Rational const& a, Rational const& b)
{
return a.equal(b) || a.less(b);
}
bool operator>=(Rational const& a, Rational const& b)
{
return a.equal(b) || a.greater(b);
}
bool operator!=(Rational const& a, Rational const& b)
{
return !a.equal(b);
}
int main()
{
Rational a(2,7);
Rational b(4, 14);
cout<< (a==b) <<'\n'; //Klammerung ist wichtig, da << eine
//höhere priorität als == hat
cout<<a.asDouble()<<'\n';
a+=b;
cout<<a.asDouble()<<'\n';
Rational c(a-b);
cout<<c.asDouble()<<'\n';
++c;
cout<<c.asDouble()<<'\n';
c=c-1;
cout<<c.asDouble()<<'\n';
}
Wir sehen: Man kann wirklich recht einfach Operatoren überladen. Doch wir sehen auch: einige Operatoren sind Methoden und andere Funktionen. Woher weiss man, ob ein Operator Member oder Non-Member ist? Hier gibt es diese einfache Regel: Wenn der linke Operand auch etwas anderes als ein Objekt dieser Klasse sein kann, dann muss eine Funktion herhalten. Wenn der linke Operand nur ein Objekt dieser Klasse sein kann, dann muss eine Member-Funktion herhalten.
Angenommen, wir hätten den operator+ zu einer Methode gemacht, dann würde Folgendes funktionieren: Rational r(2); r+2;. Aber Folgendes nicht: 2+r;, denn der Compiler sieht f+2; als r.operator*(2);. Wenn er nun versucht 2.operator+(r); aufzurufen, gibt es einen Fehler. Wenn operator+ aber eine Funktion ist, dann kann der Compiler aus der 2 mit Hilfe des Konstruktors von Rational ein Rational Objekt machen (deshalb ist der Konstruktor nicht als explicit deklariert) operator+(2, r); ergibt das selbe wie operator+(r, 2); da der Compiler 2 durch Rational(2); ersetzt. Würden wir den Konstruktor von Rational als explicit deklarieren, dann müssten wir neben Rational+Rational auch noch Operatoren für int+Rational und Rational+int schreiben.
Der operator= verdient eine genauere Beachtung: Wenn wir keinen operator= selber schreiben, generiert der Compiler (wie beim Kopierkonstruktor) ihn selber. Der compilergenerierte operator= macht allerdings nur eine 'flache' Kopie (genau wie der Kopierkonstruktor). Er ruft für jede Membervariable den operator= auf - sollten wir also Zeiger verwenden, müssen wir ihn selber schreiben. Bei unserem Beispiel mit Rational hätten wir keinen eigenen schreiben müssen - denn der vom Compiler generierte hätte auch gereicht.
Man kann alle Operatoren überladen: &&, ||, *, ->,... Ausnahmen sind: ->* und ,
Allerdings sollte man immer darauf achten, dass ein überladener Operator auch immer das tut, was man erwarten würde! Ein operator+ der subtrahiert würde nur verwirren...
Weiters sehen wir an Rational wie man sich elegant vor friends drücken kann. Statt die Vergleichsoperatoren zu Freunden zu machen, bietet Rational einfach public Vergleichsmethoden an.