next up previous contents index
Next: Schablonen Up: Überladen von Funktionen und Previous: Überladen von Funktionen

Überladen von Operatoren

  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:

. hat schon eine vordefinierte Bedeutung für alle Klassen.
.* dto.
:: beeinflußt Namensräume des Compilers.
?: erschien nicht lohnend.
sizeof muß vom Compiler statisch auswertbar sein.

Die Operatoren für eingebaute Typen dürfen nicht umdefiniert werden

tex2html_wrap_inline5173 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 tex2html_wrap_inline5241 a+=1 tex2html_wrap_inline5241 a=a+1
v->m tex2html_wrap_inline5241 (*v).m tex2html_wrap_inline5241 v[0].m

müssen für selbstdefinierte Operatoren nicht gelten.

tex2html_wrap5249

Benutzerdefinierte Operatoren sind entweder

Einige Operatoren müssen jedoch Methoden sein:    

= Wertzuweisung
() Funktionsaufruf
[] Indizierung
-> Memberzugriff über Zeiger

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
}

Ein vollständiges Beispiel

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.

Überladen der Wertzuweisung

    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 +=).

Überladen der Indizierung

  

    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).

Überladen des Elementzugriffs

   Der Operator -> wird als unärer Postfix-Operator überladen, d.h. x->m wird interpretiert als

    (x.operator ->())->m;

Das bedeutet, daß -> entweder

zurückgeben muß.

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 tex2html_wrap_inline5151 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;
}

Überladen des Funktionsaufrufs

  

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;
    }

Anwendung: Iteratoren  

    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;
    }

Überladen von new und delete

   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

Warnung

Überladen von Operatoren scheint verlockend, da man insbesondere bei kleinen Beispielen hübsche Effekte erzielen kann. Man sollte es jedoch überlegt einsetzen:


next up previous contents index
Next: Schablonen Up: Überladen von Funktionen und Previous: Überladen von Funktionen

Peter Pfahler, 1997