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.