Überladen

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 12. Juli 2015 um 12:55 Uhr durch 79.243.158.146 (Diskussion) (→‎Operatorüberladung). Sie kann sich erheblich von der aktuellen Version unterscheiden.
Zur Navigation springen Zur Suche springen

Eine Programmiersprache ermöglicht das Überladen (von englisch overloading) eines Bezeichners, wenn mehrere Vereinbarungen mit demselben Bezeichner gleichzeitig sichtbar sein können; bei der Verwendung erfolgt dann die Auswahl anhand des Kontextes. Mit anderen Worten verdeckt eine Vereinbarung nur dann eine andere, wenn nicht nur der Bezeichner, sondern weitere Merkmale übereinstimmen. Meist handelt es sich um Bezeichner von Unterprogrammen, die aufgrund der Parametertypen, bisweilen auch des Resultattyps unterschieden werden. Das Überladen wird, da es sich um einen rein syntaktischen Mechanismus handelt, nach Strachey als Ad-hoc-Polymorphie betrachtet.

Auch Aufzählungswerte und Literale können überladen werden.

Operatorüberladung

Seit jeher unterschieden die meisten Programmiersprachen, der mathematischen Tradition folgend, nicht zwischen den Operatorsymbolen (+, -, …) für Ganzzahl- (Integer‑) und Gleitpunktarithmetik (real, float). Zur bruchlosen Erweiterung einer Sprache um benutzerdefinierte Typen ist es hilfreich, die Operatorsymbole auch für benutzerdefinierte Typen überladen zu können.

Ein Beispiel für das Überladen des Operators + in C++:

 class EinfacheKomplexeZahl
 {
 public:
    EinfacheKomplexeZahl() {}
    EinfacheKomplexeZahl(float real, float imag)
    {
        m_real = real;
        m_imag = imag;
    }
    ~EinfacheKomplexeZahl() {}
 
    EinfacheKomplexeZahl operator+(const EinfacheKomplexeZahl &o) const
    {
        return EinfacheKomplexeZahl(o.m_real + m_real, o.m_imag + m_imag);
    }
 
 private:
    float m_real, m_imag;
 };
 
 int main(int argc, char *argv[])
 {
    EinfacheKomplexeZahl z1(5.0f, 3.14159f);
    EinfacheKomplexeZahl z2(0.0f, -3.14159f);
    EinfacheKomplexeZahl z3 = z1 + z2;
    // z3.m_real = 5 und z3.m_imag = 0
    return 0;
 }

In C++ lassen sich fast alle vorhandenen Operatoren überladen.

Die Parameter und Rückgabewerte der Operatorüberladungen müssen für jeden Operator sorgsam gewählt werden, denn manchmal ist er vordefiniert. So übernimmt der new-Operator einen size_t-Parameter und muss einen void*-Zeiger auf den reservierten Speicherblock zurückgeben. Analog dazu übernimmt der delete-Operator einen Zeiger auf den Speicherblock und muss diesen freigeben. Mit dieser Technik kann man sich sehr leicht einen Mechanismus zur Überwachung des Speichers programmieren, was ohne Operatorüberladung wesentlich umständlicher wäre. Dazu könnte man z. B. mit malloc() mehr Speicher als nötig reservieren und an den Anfang des Speichers eine Struktur mit diversen Informationen über den Speicherblock speichern. Der zurückgegebene Zeiger zeigt auf den Speicher nach der Struktur. Die delete-Funktion ruft free() dann auf den Beginn der Struktur auf. Außerdem könnte man die Menge an reserviertem Speicher zählen und mit der freigegebenen Menge abgleichen:

 static std::size_t nichtFreigegebenerSpeicher = 0;
 struct blockKopf
 {
   int magic;
   std::size_t size;
 };
 
 void * operator new(std::size_t size) throw (std::bad_alloc)
 {
   blockKopf *p = (blockKopf *)malloc(sizeof(blockKopf) + size);
   if (p == NULL)
      throw std::bad_alloc();
   p->magic = 0x0815;
   p->size = size;
   nichtFreigegebenerSpeicher += size;
   return (void *)((char *)p + sizeof(blockKopf));
 }
 
 void operator delete(void *p)
 {
   blockKopf *block = (blockKopf*)((char *)p - sizeof(blockKopf));
   if (block->magic != 0x0815)
     return;
   nichtFreigegebenerSpeicher -= block->size;
   free(block);
 }
 
 #include <iostream>
 int main(int argc, char *argv[])
 {
   // Objekte mit new erstellen und mit delete löschen
   // .....
 
   std::cout << nichtFreigegebenerSpeicher << " nicht freigegebener Speicher!" << std::endl;
 }

Operatoren können sowohl als globale Funktion als auch als Methode überladen werden. Als Beispiel bedeutet eine globale Überladung für new, dass alle mit new angelegten Objekte über diese Funktion erstellt werden, während dies bei Überladung in einer Methode nur bei Anlegen von Objekten dieser einen Klasse mit new geschieht. Ist ein Operator sowohl global als auch als Methode einer Klasse definiert, wird letztere der globalen Überladung vorgezogen.

Ziel der Operatorüberladung ist immer ein einfach lesbarer Quellcode, wobei genau dies bei hoher Abstraktion von Klassen durch die Benutzung von Operatorüberladungen erschwert werden kann. Bei primitiven numerischen Datentypen ist die Überladung von * selbsterklärend. Überlädt man diesen Operator allerdings für dreidimensionale Vektoren, kann durchaus uneindeutig sein, inwiefern das Skalarprodukt oder das Kreuzprodukt ausgeführt wird. In diesem Fall sind zwei Methoden namens Skalarprodukt() und Kreuzprodukt() vorzuziehen. Sogar die Konkatenation von zwei Strings mit dem Operator +, wie sie von der std::string-Klasse aus der C++-Standardbibliothek geliefert wird, ist ein Grenzfall.

Anzumerken ist, dass Operatorenüberladung normalerweise nur als syntaktischer Zucker verwendet werden soll, das heißt die dem Programmierer zur Verfügung gestellte Funktionalität auch über namentliche Funktionen bzw. Methoden zur Verfügung gestellt werden sollte.

Abgrenzung des Begriffs

Beim dynamischen Binden wird statt des deklarierten Typs der zur Laufzeit tatsächlich angetroffene Typ herangezogen. Dieser Unterschied zwischen dynamischem Binden und Überladen führt oft zu subtilen Programmfehlern.

Beispiel in C++:

 // Funktion 1
 int quadrat(int i) // Übernimmt einen int
 {
   return i * i;
 }
 
 // Funktion 2
 int quadrat(const std::string& str) // Übernimmt eine Referenz auf ein String-Objekt mit konstanten Member-Variablen aus der STL
 {
    int t = atoi(str.c_str());
    return t * t;
 }
  
 int main()
 {
    std::string s("2");
    int  i   = 3;
    int  k   = quadrat(s); // ruft die überladene Funktion für String-Parameter auf => k == 4
    int  m   = quadrat(i); // ruft die Funktion für Parameter vom Typ int auf => m == 9
 }

Siehe auch