Ziel der Einführung benutzerdefinierter überladener Operatoren war es, C++ erweiterbar zu machen.
Erweiterbar heißt nicht änderbar:
Alle Operatoren können überladen werden außer:
Die Operatoren für eingebaute Typen dürfen nicht umdefiniert werden
jeder benutzerdefinierte Operator muß
mindestens ein Argument vom Typ Klasse (oder Referenz darauf)
haben (oder als Methode definiert sein):
typedef char* string; string operator+(string, string);
ist nicht erlaubt.
Operatoren können auf zwei Arten benutzt werden:
class date { ... date operator+(int); }; date today(14,6,1993); date tomorrow = today + 1; // Infix-Notation date thedayaftertomorrow = today.operator+(2); // Funktions-Notation
Identitäten, wie sie für Operatoren eingebauter Typen existieren:
++a
a+=1
a=a+1
v->m
(*v).m
v[0].m
müssen für selbstdefinierte Operatoren nicht gelten.
Benutzerdefinierte Operatoren sind entweder
Einige Operatoren müssen jedoch Methoden sein:
Status | Syntax | tatsächlicher Aufruf |
Methode | Op X | X.operator Op() |
Methode | X Op | X.operator Op() |
Methode | X Op Y | X.operator Op(Y) |
Funktion | Op X | operator Op(X) |
Funktion | X Op | operator Op(X) |
Funktion | X Op Y | operator Op(X,Y) |
Da sie auf diese Art nicht unterscheidbar wären, gibt es für
Inkrement ++
und Dekrement --
eine Sonderregel:
Funktion | operator ++(T) | Pre-Inkrement |
Funktion | operator ++(T,int) | Post-Inkrement |
Elementfunktion | operator ++() | Pre-Inkrement |
Elementfunktion | operator ++(int) | Post-Inkrement |
Analoges gilt für --
.
class date { int jul; public: date(int i) :jul(i) {} date operator ++() // Prefix { jul++; return *this; } date operator ++(int) // Postfix { date before= *this; jul++; return before; } void print() { cout << jul << endl; } }; // class date int main() { date today(14141414); today++.print(); // schreibt 14141414 ++today.print(); // ERROR: void operand for ++ (++today).print(); // schreibt 14141416 }
Implementierung einer String-Klasse:
#include <iostream.h> #include <string.h> #include <stdlib.h> #define TEST(text) {clog << text << endl;} void checkptr(void *p) { if (!p) { cerr << "new() failed\n"; exit(99);} } class string { char *s; int len; public: string(); string(char*); string(const string&); ~string() {delete [] s;} string& operator = (const string&); string& operator += (const string&); friend string operator + (const string&, const string&); friend ostream& operator << (ostream&, const string&); friend istream& operator >> (istream&, string&); };
string::string() : len(0), s(0) { TEST("default constructor"); } string::string(char *str) { TEST("constructor"); s=new char[1 + (len=strlen(str))]; checkptr(s); strcpy(s,str); } string::string(const string& ori) { TEST("copy constructor"); s=new char[1+(len=ori.len)]; checkptr(s); strcpy(s,ori.s); } string& string::operator = (const string& right) { TEST("operator ="); if (this != &right) { delete [] s; s=new char[1+(len=right.len)]; checkptr(s); strcpy(s,right.s); } return *this; }
string& string::operator += (const string& right) { TEST("operator +="); char *news = new char[1+(len += right.len)]; checkptr(news); strcpy(news,s); strcat(news,right.s); delete [] s; s = news; return *this; } string operator + (const string& left, const string& right) { TEST("operator +"); string result=left; result+=right; return result; } ostream& operator << (ostream& os, const string& str) { TEST("operator <<"); os << str.s; return os; } istream& operator >> (istream& is, string& str) { TEST("operator >>"); char buf[128]; is >> buf; str.s=new char[1+(str.len=strlen(buf))]; checkptr(str.s); strcpy(str.s,buf); return is; }
int main() { string anf("Peter"), ende; cin >> ende; ende = ende + "."; anf += " " + ende; cout << anf << endl; }
erzeugt mit Eingabe ``Hase'':
constructor default constructor operator >> Hase constructor operator + copy constructor operator += copy constructor operator = constructor operator + copy constructor operator += copy constructor operator += operator << Peter Hase.
Der Wertzuweisungsoperator = hat eine Default-Definition (elementweises Kopieren) für alle Klassen. Er ist der einzige Operator, der nicht an Unterklassen vererbt wird (später).
class window { char data[4096]; public: window() { for (int i=0; i < 4096; i++) data[i]=0; } operator = (const window &w) { if (this != &w) for (int i= 0 ; i < 4096; i++) data[i] = w.data[i]; } }; ... window shell1, shell2; shell2 = shell2;
Die anderen Wertzuweisungsoperatoren (+=, *=, usw. haben keine vordefinierte Bedeutung. Sie sollten definiert werden, wenn die zugrundeliegenden Operatoren definiert werden (z.B. kein + ohne das dazugehörende +=).
class string { ... char& operator [] (const int); } char& string::operator [](const int index) { if (index<0 || index >= len) { cerr << "string index out of range\n"; exit(1); } return s[index]; } string anf; anf[0] = 'X';
Das zweite Argument (der Index), darf natürlich von beliebigem Typ sein (assoziative Arrays).
Der Operator -> wird als unärer Postfix-Operator überladen, d.h. x->m wird interpretiert als
(x.operator ->())->m;
Das bedeutet, daß -> entweder
Der Ablauf für x->m sieht wie folgt aus:
Wenn x ein Zeiger auf eine Klasse ist, wende den normalen Elementzugriff (x->m) an.
Wenn x ein Objekt einer Klasse ist, rufe den Operator ->
für diese Klasse auf. Wenn der nicht existiert Fehler.
Wende 1. und 2. rekursiv auf das Ergebnis an.
Anwendung: Delegation von Aufgaben an andere Klassen:
class mystring { private: char *s; public: mystring(char *s); char *getmystring() {return s;} }; mystring::mystring(char *c) { s = new char[strlen(c)+1]; strcpy(s,c); } class mystring_op { private: mystring *s; public: mystring* operator -> (); }; mystring* mystring_op::operator ->() { if (!s) s = new mystring("undef"); return s; } int main() { mystring_op a; cout << a->getmystring() << endl; }
Der Funktionsaufrufsoperator () ist ein binärer Operator mit einer Ausdrucksliste als zweites Element (evtl. leer).
class string { ... string operator() (int position, int length); } string string::operator() (int pos, int length) { TEST("operator ()"); if (pos <0 || pos > len) { cerr << "position out of range\n"; exit(1); } string result; result.s = new char[1+(result.len=length)]; for (int i=0; i<length && (pos+i)<len; i++) result.s[i] = s[pos+i]; result.s[i]='\0'; return result; }
class string { ... friend class string_iterator; } class string_iterator { int currelem; string *str; public: string_iterator(string& whatstring) { str = &whatstring; currelem=0; } char operator () (void) { if (currelem == str->len) return 0; else return str->s[currelem++]; } }; int main() { string s("This is my string"); string_iterator next(s); char c; while (c=next()) cout << c; }
Die Operatoren new und delete sind standardmäßig für alle Klassen definiert. Man kann sie überladen, wenn man eine spezielle Speicherverwaltung benötigt. Die Standardversion ist dann unter ::new bzw. ::delete erreichbar.
#include <stddef.h> class string { ... static int newsize; void* operator new(size_t size) { newsize+=size; void *p = ::new string; return p; } }; int string::newsize = 0; ... string* sp=new string("Pader"); cout << *sp << " size: " << string::newsize << endl;
schreibt
Pader size: 8
Überladen von Operatoren scheint verlockend, da man insbesondere bei kleinen Beispielen hübsche Effekte erzielen kann. Man sollte es jedoch überlegt einsetzen:
Beispiel: Überladen des Operators ! zur Ausgabe von Warnmeldungen:
if (!success) !"computation failed";
Beispiel: Überladen von ^
als Exponentiation.
a ^ b + c
ist nicht das, was man erwartet.