Grundlagen zur Operatorüberlastung. Grundlagen zum Überladen von Operatoren. Casting-Operatoren

Operatorüberladung in C++. Anwendungsmethoden

In haben wir uns mit den grundlegenden Aspekten der Verwendung der Operatorüberladung befasst. In diesem Artikel werden Ihnen überladene C++-Operatoren vorgestellt. Jeder Abschnitt ist durch Semantik gekennzeichnet, d.h. erwartetes Verhalten. Darüber hinaus werden typische Möglichkeiten zur Deklaration und Implementierung von Operatoren aufgezeigt.

In den Codebeispielen gibt X den benutzerdefinierten Typ an, für den der Operator implementiert ist. T ist ein optionaler Typ, entweder benutzerdefiniert oder integriert. Die Parameter des Binäroperators werden lhs und rhs genannt. Wenn ein Operator als Klassenmethode deklariert ist, wird seiner Deklaration das Präfix X:: vorangestellt.

Operator=

  • Definition von rechts nach links: Im Gegensatz zu den meisten Operatoren ist Operator= rechtsassoziativ, d. h. a = b = c bedeutet a = (b = c) .

Kopieren

  • Semantik: Zuweisung a = b . Der Wert oder Zustand von b wird an a übergeben. Zusätzlich wird eine Referenz auf a zurückgegeben. Dadurch können Sie Ketten wie c = a = b erstellen.
  • Typische Werbung: X& X::operator= (X const& rhs) . Andere Arten von Argumenten sind möglich, werden jedoch nicht oft verwendet.
  • Typische Implementierung: X& )

Verschieben (seit C++11)

  • Semantik: Zuweisung a = temporary() . Durch Verschieben des Inhalts wird einem ein Wert bzw. Zustand des richtigen Wertes zugewiesen. Es wird eine Referenz auf a zurückgegeben.
  • : X&
  • Compiler generiert Operator=: Der Compiler kann nur zwei Arten dieses Operators erstellen. Wenn der Operator nicht in der Klasse deklariert ist, versucht der Compiler, öffentliche Kopier- und Verschiebungsoperatoren zu erstellen. Seit C++11 kann der Compiler einen Standardoperator erstellen: X& X::operator= (X const& rhs) = default;

    Die generierte Anweisung kopiert/verschiebt einfach das angegebene Element, wenn eine solche Operation zulässig ist.

Operator+, -, *, /, %

  • Semantik: Operationen der Addition, Subtraktion, Multiplikation, Division, Division mit Rest. Ein neues Objekt mit dem resultierenden Wert wird zurückgegeben.
  • Typische Deklaration und Implementierung: X-Operator+ (X const lhs, X const rhs) ( X tmp(lhs); tmp += rhs; return tmp; )

    Wenn „operator+“ vorhanden ist, ist es normalerweise sinnvoll, auch „operator+=“ zu überladen, um die Notation „a += b“ anstelle von „a = a + b“ zu verwenden. Wenn „operator+=“ nicht überladen ist, sieht die Implementierung etwa so aus:

    X-Operator+ (X const& lhs, X const& rhs) ( // ein neues Objekt erstellen, das die Summe von lhs und rhs darstellt: return lhs.plus(rhs); )

Unärer Operator+, –

  • Semantik: positives oder negatives Vorzeichen. Operator+ macht in der Regel nichts und wird daher kaum genutzt. Operator – gibt das Argument mit dem umgekehrten Vorzeichen zurück.
  • Typische Deklaration und Implementierung: X X::operator- () const ( return /* eine negative Kopie von *this */; ) X X::operator+ () const ( return *this; )

Operator<<, >>

  • Semantik: In integrierten Typen werden Operatoren verwendet, um das linke Argument bitweise zu verschieben. Das Überladen dieser Operatoren mit genau dieser Semantik kommt selten vor; der einzige, der mir in den Sinn kommt, ist std::bitset . Für die Arbeit mit Streams wurden jedoch neue Semantiken eingeführt, und eine Überladung von I/O-Anweisungen kommt häufig vor.
  • Typische Deklaration und Implementierung: Da Sie keine Methoden zu Standard-Iostream-Klassen hinzufügen können, müssen Verschiebungsoperatoren für von Ihnen definierte Klassen als freie Funktionen überladen werden: ostream&-Operator<< (ostream& os, X const& x) { os << /* the formatted data of rhs you want to print */; return os; } istream& operator>> (istream& is, X& x) ( SomeData sd; SomeMoreData smd; if (is >> sd >> smd) ( rhs.setSomeData(sd); rhs.setSomeMoreData(smd); ) return lhs; )

    Darüber hinaus kann der Typ des linken Operanden eine beliebige Klasse sein, die sich wie ein E/A-Objekt verhalten soll, d. h. der rechte Operand kann ein integrierter Typ sein.

    MyIO& MyIO::operator<< (int rhs) { doYourThingWith(rhs); return *this; }

Binärer Operator&, |, ^

  • Semantik: Bitoperationen „und“, „oder“, „exklusiv oder“. Diese Operatoren sind sehr selten überlastet. Auch hier ist das einzige Beispiel std::bitset .

Operator+=, -=, *=, /=, %=

  • Semantik: a += b bedeutet normalerweise dasselbe wie a = a + b . Das Verhalten anderer Betreiber ist ähnlich.
  • Typische Definition und Implementierung: Da die Operation den linken Operanden ändert, ist eine versteckte Typumwandlung nicht wünschenswert. Daher müssen diese Operatoren als Klassenmethoden überladen werden. X& X::operator+= (X const& rhs) ( //Änderungen auf *this return *this; anwenden)

Operator&=, |=, ^=,<<=, >>=

  • Semantik: ähnlich zu Operator+= , aber für logische Operationen. Diese Operatoren werden ebenso selten überladen wie Operator| usw. Operator<<= и operator>>= werden aufgrund des Operators nicht für E/A-Operationen verwendet<< и operator>> ändert bereits das linke Argument.

Operator==, !=

  • Semantik: Test auf Gleichheit/Ungleichheit. Die Bedeutung von Gleichheit ist je nach Klasse sehr unterschiedlich. Berücksichtigen Sie auf jeden Fall die folgenden Eigenschaften von Gleichheiten:
    1. Reflexivität, d.h. ein == ein .
    2. Symmetrie, d.h. wenn a == b , dann b == a .
    3. Transitivität, d.h. wenn a == b und b == c , dann a == c .
  • Typische Deklaration und Implementierung: bool-Operator== (X const& lhs,

    Die zweite Implementierung von „operator!=“ vermeidet Wiederholungen des Codes und eliminiert mögliche Unklarheiten bezüglich zweier Objekte.

Operator<, <=, >, >=

  • Semantik: Überprüfen Sie das Verhältnis (mehr, weniger usw.). Es wird üblicherweise verwendet, wenn die Reihenfolge der Elemente eindeutig definiert ist, das heißt, es macht keinen Sinn, komplexe Objekte mit mehreren Merkmalen zu vergleichen.
  • Typische Deklaration und Implementierung: Bool-Operator< (X const& lhs, X const& rhs) { return /* compare whatever defines the order */ } bool operator>(X const& lhs, X const& rhs) ( return rhs< lhs; }

    Operator implementieren > Operator verwenden< или наоборот обеспечивает однозначное определение. operator<= может быть реализован по-разному, в зависимости от ситуации . В частности, при отношении строго порядка operator== можно реализовать лишь через operator< :

    Bool-Operator== (X const& lhs, X const& rhs) ( return !(lhs< rhs) && !(rhs < lhs); }

Operator++, –

  • Semantik: a++ (Post-Inkrement) erhöht den Wert um 1 und gibt zurück alt Bedeutung. ++a (Vorinkrement) wird zurückgegeben neu Bedeutung. Mit dem Dekrementoperator ist alles ähnlich.
  • Typische Deklaration und Implementierung: X& +(*this); return oldValue; )

Operator()

  • Semantik: Ausführung eines Funktionsobjekts (Functor). Wird normalerweise nicht verwendet, um ein Objekt zu ändern, sondern um es als Funktion zu verwenden.
  • Keine Einschränkungen hinsichtlich der Parameter: Im Gegensatz zu früheren Operatoren gibt es in diesem Fall keine Einschränkungen hinsichtlich der Anzahl und Art der Parameter. Ein Operator kann nur als Klassenmethode überladen werden.
  • Beispielanzeige: Foo X::operator() (Bar br, Baz const& bz);

Operator

  • Semantik: Greifen Sie auf Elemente eines Arrays oder Containers zu, zum Beispiel in std::vector , std::map , std::array .
  • Bekanntmachung: Der Parametertyp kann beliebig sein. Der Rückgabetyp ist normalerweise ein Verweis auf das, was im Container gespeichert ist. Oft wird der Operator in zwei Versionen überladen, const und non-const: Element_t& X::operator(Index_t const& index); const Element_t& X::operator(Index_t const& index) const;

Operator!

  • Semantik: Negation im logischen Sinne.
  • Typische Deklaration und Implementierung: bool X::operator!() const ( return !/*eine Auswertung von *this*/; )

expliziter Operator bool

  • Semantik: Verwendung in einem logischen Kontext. Wird am häufigsten mit intelligenten Zeigern verwendet.
  • Implementierung: explizit X::operator bool() const ( return /* wenn dies wahr oder falsch ist */; )

Operator&&, ||

  • Semantik: logisches „und“, „oder“. Diese Operatoren sind nur für den integrierten booleschen Typ definiert und arbeiten verzögert, d. h. das zweite Argument wird nur berücksichtigt, wenn das erste das Ergebnis nicht bestimmt. Bei Überladung geht diese Eigenschaft verloren, sodass diese Operatoren selten überladen werden.

Unärer Operator*

  • Semantik: Zeiger-Dereferenzierung. Typischerweise überlastet für Klassen mit intelligenten Zeigern und Iteratoren. Gibt einen Verweis darauf zurück, wohin das Objekt zeigt.
  • Typische Deklaration und Implementierung: T& X::operator*() const ( return *_ptr; )

Operator->

  • Semantik: Greifen Sie per Zeiger auf ein Feld zu. Wie der vorherige ist dieser Operator für die Verwendung mit intelligenten Zeigern und Iteratoren überlastet. Wenn der ->-Operator in Ihrem Code auftritt, leitet der Compiler Aufrufe an „operator->“ um, wenn das Ergebnis eines benutzerdefinierten Typs zurückgegeben wird.
  • Übliche Implementierung: T* X::operator->() const ( return _ptr; )

Operator->*

  • Semantik: Zugriff auf ein Zeiger-auf-Feld per Zeiger. Der Operator nimmt einen Zeiger auf ein Feld und wendet ihn auf alles an, worauf *this zeigt, sodass objPtr->*memPtr dasselbe ist wie (*objPtr).*memPtr . Sehr selten verwendet.
  • Mögliche Umsetzung: Vorlage T& X::operator->*(T V::* memptr) ( return (operator*()).*memptr; )

    Dabei ist X der intelligente Zeiger, V der Typ, auf den X zeigt, und T der Typ, auf den der Feldzeiger zeigt. Es überrascht nicht, dass dieser Operator selten überlastet ist.

Unärer Operator&

  • Semantik: Adressoperator. Dieser Operator ist sehr selten überlastet.

Operator

  • Semantik: Der integrierte Kommaoperator, der auf zwei Ausdrücke angewendet wird, wertet beide in schriftlicher Reihenfolge aus und gibt den Wert des zweiten zurück. Es wird nicht empfohlen, es zu überladen.

Betreiber~

  • Semantik: Bitweiser Inversionsoperator. Einer der am seltensten verwendeten Operatoren.

Casting-Operatoren

  • Semantik: Ermöglicht die implizite oder explizite Umwandlung von Klassenobjekten in andere Typen.
  • Bekanntmachung: //Konvertierung nach T, explizit oder implizit X::operator T() const; //explizite Konvertierung in U const& expliziter X::operator U const&() const; //Konvertierung nach V& V& X::operator V&();

    Diese Deklarationen sehen seltsam aus, weil ihnen ein Rückgabetyp fehlt. Es ist Teil des Operatornamens und wird nicht zweimal angegeben. Es sei daran erinnert, dass eine große Anzahl versteckter Umwandlungen zu unerwarteten Fehlern im Programm führen kann.

Operator neu, neu, löschen, löschen

Diese Operatoren unterscheiden sich grundlegend von allen oben genannten, da sie nicht mit benutzerdefinierten Typen funktionieren. Ihre Überlastung ist sehr komplex und wird daher hier nicht betrachtet.

Abschluss

Der Grundgedanke ist dieser: Überlasten Sie Operatoren nicht, nur weil Sie wissen, wie es geht. Überladen Sie sie nur in den Fällen, in denen es natürlich und notwendig erscheint. Denken Sie jedoch daran: Wenn Sie einen Operator überlasten, müssen Sie auch andere überlasten.

Guten Tag!

Der Wunsch, diesen Artikel zu schreiben, entstand nach dem Lesen des Beitrags, da viele wichtige Themen darin nicht behandelt wurden.

Das Wichtigste, an das Sie denken sollten, ist, dass die Überladung von Operatoren nur eine bequemere Möglichkeit zum Aufrufen von Funktionen ist. Lassen Sie sich also nicht von der Überlastung von Operatoren mitreißen. Es sollte nur verwendet werden, wenn es das Schreiben von Code erleichtert. Aber nicht so sehr, dass es das Lesen erschwert. Wie Sie wissen, wird Code schließlich viel häufiger gelesen als geschrieben. Und vergessen Sie nicht, dass Sie niemals Operatoren zusammen mit integrierten Typen überladen dürfen; die Möglichkeit der Überladung besteht nur für benutzerdefinierte Typen/Klassen.

Syntax überladen

Die Syntax für das Überladen von Operatoren ist der Definition einer Funktion namens „operator@“ sehr ähnlich, wobei @ die Operator-ID ist (zum Beispiel +, -,<<, >>). Schauen wir uns ein einfaches Beispiel an:
class Integer ( private: int value; public: Integer(int i): value(i) () const Integer Operator+(const Integer& rv) const ( return (value + rv.value); ) );
In diesem Fall wird der Operator als Mitglied einer Klasse eingerahmt, das Argument bestimmt den Wert, der sich auf der rechten Seite des Operators befindet. Im Allgemeinen gibt es zwei Möglichkeiten, Operatoren zu überladen: globale Funktionen, die für die Klasse geeignet sind, oder Inline-Funktionen der Klasse selbst. Wir werden am Ende des Themas überlegen, welche Methode für welchen Operator besser ist.

In den meisten Fällen geben Operatoren (außer bedingte) ein Objekt oder einen Verweis auf den Typ zurück, zu dem seine Argumente gehören (wenn die Typen unterschiedlich sind, entscheiden Sie, wie Sie das Ergebnis der Operatorauswertung interpretieren).

Überladen unärer Operatoren

Schauen wir uns Beispiele für die Überladung unärer Operatoren für die oben definierte Integer-Klasse an. Definieren wir sie gleichzeitig in Form benutzerfreundlicher Funktionen und betrachten wir die Dekrementierungs- und Inkrementierungsoperatoren:
class Integer ( private: int value; public: Integer(int i): value(i) () //unär + Freund const Integer& Operator+(const Integer& i); //unär - Freund const Integer Operator-(const Integer& i) ; //Präfix Inkrement Freund const Integer& Operator++(Integer& i); //Postfix Inkrement Freund Const Ganzzahl Operator++(Integer& i, int); //Präfix Dekrement Freund Const Integer& Operator--(Integer& i); //Postfix Dekrement Freund Const Ganzzahloperator--(Integer& i, int); ); //unary plus macht nichts. const Integer& Operator+(const Integer& i) ( Return i.value; ) const Integer Operator-(const Integer& i) ( Return Integer(-i.value); ) //Präfixversion gibt Wert nach Inkrement zurück const Integer& Operator++(Integer& i) ( i.value++; return i; ) //Postfix-Version gibt den Wert vor dem Inkrement zurück const Integer-Operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //Präfix-Version gibt zurück der Wert nach der Dekrementierung const Integer& Operator--(Integer& i) ( i.value--; return i; ) //Postfix-Version gibt den Wert vor der Dekrementierung zurück const Integer Operator--(Integer& i, int) ( Integer oldValue(i. value); i .value--;return oldValue; )
Jetzt wissen Sie, wie der Compiler zwischen Präfix- und Postfix-Versionen von Dekrement und Inkrement unterscheidet. Falls es den Ausdruck ++i sieht, wird die Funktion Operator++(a) aufgerufen. Wenn i++ angezeigt wird, wird der Operator++(a, int) aufgerufen. Das heißt, die überladene Funktion „operator++“ wird aufgerufen, und dafür wird der Dummy-int-Parameter in der Postfix-Version verwendet.

Binäre Operatoren

Schauen wir uns die Syntax zum Überladen binärer Operatoren an. Überladen wir einen Operator, der einen L-Wert zurückgibt, einen bedingten Operator und einen Operator, der einen neuen Wert erstellt (definieren wir sie global):
Klasse Integer ( privat: int value; public: Integer(int i): value(i) () Freund const Ganzzahl Operator+(const Ganzzahl& links, const Ganzzahl& rechts); Freund Ganzzahl& Operator+=(Integer& links, const Ganzzahl& rechts); Freund bool Operator==(const Integer& left, const Integer& right); ); const Integer Operator+(const Integer& left, const Integer& right) ( return Integer(left.value + right.value); ) Integer& Operator+=(Integer& left, const Integer& right) ( left.value += right.value; return left; ) bool Operator==(const Integer& left, const Integer& right) ( return left.value == right.value; )
In allen diesen Beispielen werden Operatoren für denselben Typ überladen, dies ist jedoch nicht notwendig. Sie können beispielsweise die Addition unseres Typs „Integer“ und „Float“ überladen, die durch ihre Ähnlichkeit definiert ist.

Argumente und Rückgabewerte

Wie Sie sehen können, verwenden die Beispiele unterschiedliche Methoden zur Übergabe von Argumenten an Funktionen und zur Rückgabe von Operatorwerten.
  • Wenn das Argument nicht durch einen Operator geändert wird, beispielsweise im Fall eines unären Plus, muss es als Referenz auf eine Konstante übergeben werden. Im Allgemeinen gilt dies für fast alle arithmetischen Operatoren (Addition, Subtraktion, Multiplikation...)
  • Die Art des Rückgabewerts hängt von der Art des Operators ab. Wenn der Operator einen neuen Wert zurückgeben muss, muss ein neues Objekt erstellt werden (wie im Fall von Binärplus). Wenn Sie verhindern möchten, dass ein Objekt als L-Wert geändert wird, müssen Sie es als Konstante zurückgeben.
  • Zuweisungsoperatoren müssen einen Verweis auf das geänderte Element zurückgeben. Wenn Sie den Zuweisungsoperator auch in Konstrukten wie (x=y).f() verwenden möchten, bei denen die Funktion f() für die Variable x aufgerufen wird, nachdem Sie sie y zugewiesen haben, geben Sie keinen Verweis auf zurück Konstante, geben Sie einfach eine Referenz zurück.
  • Logische Operatoren sollten im schlimmsten Fall einen Int und im besten Fall einen Bool zurückgeben.

Rückgabewertoptimierung

Wenn Sie neue Objekte erstellen und von einer Funktion zurückgeben, sollten Sie eine Notation verwenden, die dem oben beschriebenen Beispiel des binären Plus-Operators ähnelt.
return Integer(left.value + right.value);
Ehrlich gesagt weiß ich nicht, welche Situation für C++11 relevant ist; alle weiteren Argumente gelten für C++98.
Auf den ersten Blick ähnelt dies der Syntax zum Erstellen eines temporären Objekts, d. h. es scheint keinen Unterschied zwischen dem obigen Code und diesem zu geben:
Integer temp(left.value + right.value); Rücklauftemperatur;
Tatsächlich wird in diesem Fall jedoch in der ersten Zeile der Konstruktor aufgerufen, dann wird der Kopierkonstruktor aufgerufen, der das Objekt kopiert, und dann wird beim Abwickeln des Stapels der Destruktor aufgerufen. Bei Verwendung des ersten Eintrags erstellt der Compiler zunächst das Objekt im Speicher, in den es kopiert werden muss, und erspart so Aufrufe an den Kopierkonstruktor und -destruktor.

Spezialoperatoren

C++ verfügt über Operatoren mit spezifischer Syntax und Überladungsmethoden. Zum Beispiel der Indexierungsoperator. Es wird immer als Mitglied einer Klasse definiert und da sich das indizierte Objekt wie ein Array verhalten soll, sollte es eine Referenz zurückgeben.
Komma-Operator
Zu den „speziellen“ Operatoren gehört auch der Komma-Operator. Es wird für Objekte aufgerufen, vor denen ein Komma steht (jedoch nicht für Funktionsargumentlisten). Es ist nicht einfach, einen sinnvollen Anwendungsfall für diesen Operator zu finden. Habrauser in den Kommentaren zum vorherigen Artikel zum Thema Überlastung.
Zeiger-Dereferenzierungsoperator
Das Überladen dieser Operatoren kann für Smart-Pointer-Klassen gerechtfertigt sein. Dieser Operator ist notwendigerweise als Klassenfunktion definiert und unterliegt einigen Einschränkungen: Er muss entweder ein Objekt (oder eine Referenz) oder einen Zeiger zurückgeben, der den Zugriff auf das Objekt ermöglicht.
Aufgabenverwalter
Der Zuweisungsoperator ist notwendigerweise als Klassenfunktion definiert, da er intrinsisch mit dem Objekt links vom „=" verknüpft ist. Durch die globale Definition des Zuweisungsoperators wäre es möglich, das Standardverhalten des „="-Operators zu überschreiben. Beispiel:
class Integer ( private: int value; public: Integer(int i): value(i) () Integer& Operator=(const Integer& right) ( //auf Selbstzuweisung prüfen if (this == &right) ( return *this; ) value = right.value; return *this; ) );

Wie Sie sehen, wird zu Beginn der Funktion eine Prüfung auf Selbstzuweisung durchgeführt. Im Allgemeinen ist Selbstaneignung in diesem Fall harmlos, aber die Situation ist nicht immer so einfach. Wenn das Objekt beispielsweise groß ist, können Sie viel Zeit durch unnötiges Kopieren oder die Arbeit mit Zeigern verschwenden.

Nicht überladbare Operatoren
Einige Operatoren in C++ sind überhaupt nicht überladen. Offenbar geschah dies aus Sicherheitsgründen.
  • Auswahloperator für Klassenmitglieder „.“
  • Operator zum Dereferenzieren eines Zeigers auf ein Klassenmitglied „.*“
  • In C++ gibt es keinen Potenzierungsoperator (wie in Fortran) „**“.
  • Es ist verboten, eigene Operatoren zu definieren (es kann zu Problemen bei der Festlegung der Prioritäten kommen).
  • Bedienerprioritäten können nicht geändert werden
Wie wir bereits herausgefunden haben, gibt es zwei Arten von Operatoren – als Klassenfunktion und als benutzerfreundliche globale Funktion.
Rob Murray definiert in seinem Buch C++ Strategies and Tactics die folgenden Richtlinien für die Auswahl der Operatorform:

Warum so? Erstens sind einige Betreiber zunächst eingeschränkt. Wenn es keinen semantischen Unterschied in der Definition eines Operators gibt, ist es im Allgemeinen besser, ihn als Klassenfunktion zu entwerfen, um die Verbindung hervorzuheben. Außerdem ist die Funktion inline. Darüber hinaus kann es manchmal erforderlich sein, den linken Operanden als Objekt einer anderen Klasse darzustellen. Das wohl auffälligste Beispiel ist die Neudefinition<< и >> für I/O-Streams.

Manchmal möchten Sie kreativ sein und Ihren Code für sich und andere einfacher machen. Für sich selbst schreiben, für andere verstehen. Nehmen wir an, wenn wir in unserem Programm häufig auf die Funktion stoßen, eine Zeile am Ende einer anderen hinzuzufügen, können wir dies natürlich auf unterschiedliche Weise implementieren. Und wenn wir in einem Teil unseres Codes beispielsweise Folgendes schreiben:

Char str1 = "Hallo"; char str2 = "Welt!"; str1 + str2;

und als Ergebnis erhalten wir die Zeichenfolge „Hallo Welt!“ Wäre das nicht toll? Nun, bitte! Heute lernen Sie, dem Computer zu „erklären“, dass Sie mit dem +-Operator nicht zwei Zahlen, sondern zwei Zeichenfolgen addieren möchten. Und die Arbeit mit Strings ist meiner Meinung nach eines der erfolgreichsten Beispiele, um das Thema „Operator Overloading“ zu verstehen.

Beginnen wir mit dem Üben. In diesem Beispiel überladen wir den Operator „+“ und zwingen ihn, den Inhalt einer anderen Zeile an eine Zeile anzuhängen. Nämlich: Wir werden aus vier einzelnen Zeilen einen Teil eines uns allen bekannten Gedichts von A. S. Puschkin zusammenstellen. Ich rate Ihnen, Ihre Entwicklungsumgebung zu öffnen und dieses Beispiel neu zu schreiben. Wenn Sie nicht alles im Code verstehen, machen Sie sich keine Sorgen, detaillierte Erklärungen finden Sie weiter unten.

#enthalten #enthalten Verwenden des Namensraums std; Klasse StringsWork ( private: char str;//String, der für die Klasse verfügbar ist public: StringsWork()//Konstruktor, in dem wir den Klassenstring aus Müll befreien ( for(int i = 0; i< 256; i++) str[i] = "\0"; } void operator +(char*);//прототип метода класса в котором мы перегрузим оператор + void getStr();//метод вывода данных на экран }; void StringsWork::operator +(char *s) //что должен выполнить оператор + { strcat(str, s); //сложение строк } void StringsWork::getStr() { cout << str << endl << endl;//вывод символьного массива класса на экран } int main() { setlocale(LC_ALL, "rus"); char *str1 = new char ; //выделим память для строк char *str2 = new char ; char *str3 = new char ; char *str4 = new char ; strcpy(str1,"У лукоморья дуб зелёный;\n");//инициализируем strcpy(str2,"Всё ходит по цепи кругом;\n"); strcpy(str3,"И днём и ночью кот учёный\n"); strcpy(str4,"Златая цепь на дубе том:\n"); cout << "1) " << str1; cout << "2) " << str2; cout << "3) " << str3; cout << "4) " << str4 << endl; StringsWork story;//создаем объект и добавяем в него строки с помощью перегруженного + story + str1; story + str4; story + str3; story + str2; cout << "========================================" << endl; cout << "Стих, после правильного сложения строк: " << endl; cout << "========================================" << endl << endl; story.getStr(); //Отмечу, что для числовых типов данных оператор плюс будет складывать значения, как и должен int a = 5; int b = 5; int c = 0; c = a + b; cout << "========================================" << endl << endl; cout << "a = " << a << endl << "b = " << b << endl; cout << "c = " << a << " + " << b << " = " << c << endl << endl; delete str4;//освободим память delete str3; delete str2; delete str1; return 0; }

Lass es uns herausfinden:

Wir haben etwas Neues im Code gesehen Zeile 16 Void-Operator +(char*); Hier haben wir einen Prototyp einer Klassenmethode deklariert, in der wir unseren +-Operator überladen. Um einen Operator zu überladen, müssen Sie den reservierten Wortoperator verwenden. Es sieht so aus, als würden Sie eine reguläre Funktion definieren: void Operator+ () (//Code) Im Hauptteil dieser Funktion platzieren wir Code, der dem Compiler mitteilt, was der +-Operator (oder ein anderer Operator) tun wird. Ein überladener Operator führt die für ihn angegebenen Aktionen nur im Rahmen der Klasse aus, in der er definiert ist. Unten, in Zeilen 20 - 23 Wir legen bereits fest, welche Rolle + in unserer Klasse spielen wird. Nämlich mit der Funktion strcat(str, s); Es wird den Inhalt der Zeichenfolge s, die wir per Zeiger übergeben haben, an das Ende der Zeichenfolge str anhängen. Zeilen 17, 25 – 28 Dies ist eine reguläre Klassenmethode, die die Klassenzeichenfolge auf dem Bildschirm anzeigt. Wenn Sie nicht wissen, wie Sie Klassenmethoden außerhalb des Klassenkörpers definieren, d. h. einen solchen Moment wie void StringsWork::getStr() (//definition), dann sollten Sie zuerst hierher gehen. Weiter bereits in der Hauptfunktion main() , in Zeilen 34 - 37, erstellen wir vier Zeiger auf Strings und weisen jedem von ihnen die erforderliche Menge an Speicher zu, wobei wir nicht vergessen, dass wir für das Zeichen „\0“ auch eine Zelle reservieren müssen char *str1 = new char ; . Dann kopieren wir den Text mit der Funktion strcpy() hinein und zeigen ihn auf dem Bildschirm an - Zeilen 39 - 47. Und in Zeile 49 Erstellen Sie ein Klassenobjekt. Beim Erstellen funktioniert der Klassenkonstruktor und die Klassenzeile wird von unnötigen Daten befreit. Jetzt müssen wir nur noch die Strings mit dem überladenen Operator + - in der richtigen Reihenfolge hinzufügen. Zeilen 50 - 53 und sehen, was passiert ist - Zeile 58.

Ergebnis des Programms:

1) In der Nähe von Lukomorye gibt es eine grüne Eiche;
2) Alles dreht sich in einer Kette im Kreis;
3) Tag und Nacht ist die Katze eine Wissenschaftlerin
4) Goldene Kette an der Eiche:

========================================
Vers, nachdem die Zeilen korrekt hinzugefügt wurden:

In der Nähe von Lukomorye gibt es eine grüne Eiche;
Goldene Kette an der Eiche:
Tag und Nacht ist die Katze eine Wissenschaftlerin
Alles dreht sich in einer Kette im Kreis;
========================================

a = 5
b = 5
c = 5 + 5 = 10

Grenzwerte für die Überlastung des Bedieners

  • Fast jeder Operator kann überlastet werden, mit Ausnahme der folgenden:

. Punkt (Klassenelement auswählen);

* Sternchen (Definition oder Dereferenzierung eines Zeigers);

:: Doppelpunkt (Methodenumfang);

?: Fragezeichen mit Doppelpunkt (ternärer Vergleichsoperator);

# scharf (Präprozessorsymbol);

## Doppelkreuz (Präprozessorsymbol);

Größe von Operator zum Ermitteln der Größe eines Objekts in Bytes;

  • Durch Überladung ist es unmöglich, neue Symbole für Operationen zu erstellen.
  • Eine Überlastung des Operators ändert nichts an der Reihenfolge der Operationen und ihrer Priorität.
  • Der unäre Operator kann nicht verwendet werden überschreiben einen binären Operator auf die gleiche Weise, wie ein binärer Operator einen unären Operator nicht überschreiben wird.

Vergessen Sie nicht, dass es beim Programmieren sehr wünschenswert ist, alles zu tun, um Ihren Code so klar wie möglich zu gestalten. Dieses Prinzip gilt für alles: die Namen, die Sie Variablen, Funktionen, Strukturen, Klassen geben, sowie die Aktionen, die ein überladener Operator ausführt. Versuchen Sie, diese Aktionen so nah wie möglich an der logischen Bedeutung der Operatoren zu definieren. Beispiel: + zum Hinzufügen von Zeichenfolgen oder anderen Klassenobjekten, - zum Löschen einer Zeichenfolge usw.

Es ist zu beachten, dass viele Programmierer eine negative Einstellung gegenüber der Überlastung von Operatoren haben. Die Möglichkeit einer Operatorüberlastung wird bereitgestellt, um das Verständnis und die Lesbarkeit des Programmcodes zu erleichtern. Gleichzeitig kann es im Gegenteil dazu führen, dass Ihr Programm komplizierter wird, und es wird für viele Programmierer schwierig sein, es zu verstehen. Denken Sie an die „goldene Mitte“ und verwenden Sie Überlastung nur dann, wenn sie Ihnen und anderen tatsächlich zugute kommt. Auf eine Überlastung des Bedieners kann durchaus verzichtet werden. Dies bedeutet jedoch nicht, dass Sie dieses Thema ignorieren können. Sie sollten es verstehen, und sei es nur, weil Sie eines Tages mit der Überlastung des Codes einer anderen Person zu kämpfen haben und leicht herausfinden können, was was ist.

Hier haben wir uns ganz kurz mit der Operatorüberladung in C++ vertraut gemacht. Wir haben sozusagen die Spitze des Eisbergs gesehen. Und Ihre Hausaufgabe (JA – HAUSAUFGABE!) wird darin bestehen, das Programm zu ändern, indem Sie eine Operatorüberladung hinzufügen, um eine Zeile zu löschen. Wählen Sie selbst aus, welchen Operator Sie überlasten möchten. Oder bieten Sie Ihre eigene Version des Code-Upgrades an und fügen Sie ihm das hinzu, was Sie für notwendig und interessant halten. Sie können Ihre „Werke“ in den Kommentaren zu diesem Artikel hinzufügen. Wir sind an Ihren Lösungen interessiert. Viel Glück!

Jede Wissenschaft verfügt über Standardnotationen, die das Verständnis von Ideen erleichtern. In der Mathematik sind dies beispielsweise Multiplikation, Division, Addition und andere symbolische Notationen. Der Ausdruck (x + y * z) ist viel einfacher zu verstehen als „y, c, z multiplizieren und zu x addieren“. Stellen Sie sich vor, bis zum 16. Jahrhundert gab es in der Mathematik keine symbolischen Notationen; alle Ausdrücke wurden verbal geschrieben, als wäre es ein literarischer Text mit einer Beschreibung. Und die uns bekannten Operationsbezeichnungen tauchten noch später auf. Die Bedeutung einer kurzen symbolischen Notation kann kaum überschätzt werden. Basierend auf solchen Überlegungen wurde den Programmiersprachen die Operatorüberladung hinzugefügt. Schauen wir uns ein Beispiel an.

Beispiel für eine Operatorüberladung

Fast wie jede andere Sprache unterstützt C++ viele Operatoren, die mit im Sprachstandard integrierten Datentypen arbeiten. Die meisten Programme verwenden jedoch benutzerdefinierte Typen, um bestimmte Probleme zu lösen. Beispielsweise werden komplexe Mathematikaufgaben in einem Programm implementiert, indem komplexe Zahlen oder Matrizen als benutzerdefinierte C++-Typen dargestellt werden. Eingebaute Operatoren wissen nicht, wie sie ihre Arbeit verteilen und die erforderlichen Prozeduren für Benutzerklassen ausführen sollen, egal wie offensichtlich sie auch erscheinen mögen. Daher wird zum Beispiel für das Hinzufügen von Matrizen meist eine eigene Funktion erstellt. Offensichtlich ist der Aufruf von sum_matrix(A, B) im Code weniger klar als der Aufruf von A + B.

Betrachten wir eine Beispielklasse komplexer Zahlen:

//eine komplexe Zahl als Paar von Gleitkommazahlen darstellen. Klasse complex ( double re, im; public: complex (double r, double i) :re(r), im(i) () //Konstruktor komplexer Operator+(complex); //Additionsüberladung komplexer Operator*(complex); //Multiplikationsüberladung); void main() ( complex a( 1, 2 ), b( 3, 4 ), c(0, 0); c = a + b; c = a.operator+(b); ////Operatorfunktion kann sein Dieser Eintrag wird wie jede Funktion aufgerufen und entspricht a+b c = a*b + complex(1, 3); //Es werden die üblichen Regeln für die Priorität von Additions- und Multiplikationsoperationen befolgt)

Auf ähnliche Weise können Sie beispielsweise Ein-/Ausgabeoperatoren in C++ überladen und an die Darstellung komplexer Strukturen wie Matrizen anpassen.

Für die Überladung stehen Operatoren zur Verfügung

Eine vollständige Liste aller Operatoren, für die der Überladungsmechanismus verwendet werden kann:

Wie Sie der Tabelle entnehmen können, ist eine Überladung für die meisten Sprachoperatoren akzeptabel. Eine Überlastung des Bedieners kann nicht erforderlich sein. Dies geschieht ausschließlich aus Bequemlichkeitsgründen. Daher gibt es beispielsweise in Java keine Operatorüberladung. Und nun zum nächsten wichtigen Punkt.

Betreiber, deren Überlastung verboten ist

  • Bereichsauflösung – „::“;
  • Mitgliedsauswahl – „.“;
  • Auswählen eines Mitglieds durch einen Zeiger auf ein Mitglied – „.*“;
  • Ternärer bedingter Operator – „?:“;
  • Größe des Operators;
  • Typeid-Operator.

Der rechte Operand dieser Operatoren ist der Name, nicht der Wert. Daher könnte eine Überlastung dazu führen, dass viele mehrdeutige Konstrukte geschrieben werden, und würde das Leben von Programmierern erheblich erschweren. Obwohl es viele Programmiersprachen gibt, die eine Überladung aller Operatoren ermöglichen – zum Beispiel Überladung

Einschränkungen

Beschränkungen für Betreiberüberlastung:

  • Sie können einen binären Operator nicht in einen unären Operator ändern und umgekehrt, und Sie können keinen dritten Operanden hinzufügen.
  • Sie können keine neuen Operatoren erstellen, außer denen, die bereits vorhanden sind. Diese Einschränkung trägt dazu bei, viele Unklarheiten zu beseitigen. Wenn ein neuer Operator benötigt wird, können Sie für diese Zwecke eine Funktion verwenden, die die erforderliche Aktion ausführt.
  • Eine Operatorfunktion kann entweder Mitglied einer Klasse sein oder mindestens ein benutzerdefiniertes Typargument haben. Ausnahmen bilden die Operatoren new und delete. Diese Regel verbietet das Ändern der Bedeutung von Ausdrücken, wenn diese keine Objekte benutzerdefinierter Typen enthalten. Insbesondere können Sie keine Operatorfunktion erstellen, die ausschließlich mit Zeigern arbeitet, oder dafür sorgen, dass der Additionsoperator wie eine Multiplikation funktioniert. Ausnahmen bilden die Operatoren „=", „&" und „"," für Klassenobjekte.
  • Eine Operatorfunktion, deren erstes Mitglied einer der integrierten C++-Datentypen ist, kann kein Mitglied der Klasse sein.
  • Der Name jeder Operatorfunktion beginnt mit dem Schlüsselwort „operator“, gefolgt von der symbolischen Bezeichnung des Operators selbst.
  • Eingebaute Operatoren werden so definiert, dass zwischen ihnen eine Beziehung besteht. Beispielsweise sind die folgenden Operatoren äquivalent zueinander: ++x; x + = 1; x = x + 1. Nach der Neudefinition bleibt die Verbindung zwischen ihnen nicht erhalten. Der Programmierer muss sich gesondert um die Aufrechterhaltung der Zusammenarbeit mit neuen Typen in ähnlicher Weise kümmern.
  • Der Compiler kann nicht denken. Die Ausdrücke z + 5 und 5 +z (wobei z eine komplexe Zahl ist) werden vom Compiler unterschiedlich behandelt. Das erste ist „komplex + Zahl“ und das zweite ist „Zahl + komplex“. Daher muss jeder Ausdruck seinen eigenen Additionsoperator definieren.
  • Bei der Suche nach einer Operatordefinition bevorzugt der Compiler weder Klassenmitgliedsfunktionen noch Hilfsfunktionen, die außerhalb der Klasse definiert sind. Für den Compiler sind sie gleich.

Interpretationen binärer und unärer Operatoren.

Ein binärer Operator wird als Memberfunktion mit einer Variablen oder als Funktion mit zwei Variablen definiert. Für jeden binären Operator @ im Ausdruck a@b, @ gelten die folgenden Konstruktionen:

a.operator@(b) oder Operator@(a, b).

Betrachten wir am Beispiel der Klasse der komplexen Zahlen die Definition von Operationen als Klassenmitglieder und Hilfsoperationen.

Klasse complex ( double re, im; public: complex& Operator+=(Complex Z); Complex& Operator*=(Complex Z); ); //Hilfsfunktionen komplexer Operator+(komplex z1, komplex z2); komplexer Operator+(komplex z, doppelt a);

Welcher Operator gewählt wird und ob er überhaupt gewählt wird, wird durch die internen Mechanismen der Sprache bestimmt, auf die weiter unten eingegangen wird. Dies geschieht normalerweise durch Typvergleich.

Die Entscheidung, ob eine Funktion als Mitglied einer Klasse oder außerhalb einer Klasse beschrieben werden soll, ist im Allgemeinen Geschmackssache. Im obigen Beispiel war das Auswahlprinzip wie folgt: Wenn die Operation den linken Operanden ändert (z. B. a + = b), schreiben Sie ihn in die Klasse und verwenden Sie die Übergabe einer Variablen an die Adresse, um ihn direkt zu ändern. Wenn die Operation nichts ändert und einfach einen neuen Wert zurückgibt (z. B. a + b), verschieben Sie ihn aus dem Bereich der Klassendefinition.

Die Definition der Überladung unärer Operatoren in C++ erfolgt auf ähnliche Weise, mit dem Unterschied, dass sie in zwei Typen unterteilt werden:

  • Der vor dem Operanden platzierte Präfixoperator ist @a, zum Beispiel ++i. o ist definiert als a.operator@() oder Operator@(aa);
  • Der Postfix-Operator nach dem Operanden ist b@, zum Beispiel i++. o ist definiert als b.operator@(int) oder Operator@(b, int)

Genau wie bei binären Operatoren wird die Auswahl durch die C++-Mechanismen getroffen, wenn die Operatordeklaration sowohl innerhalb als auch außerhalb der Klasse erfolgt.

Regeln für die Betreiberauswahl

Der binäre Operator @ soll auf Objekte x der Klasse X und y der Klasse Y angewendet werden. Die Regeln zum Auflösen von x@y lauten wie folgt:

  1. Wenn X eine Klasse ist, suchen Sie darin nach der Definition von „operator@“ als Mitglied von X oder als Basisklasse von X.
  2. Sehen Sie sich den Kontext an, in dem sich der Ausdruck x@y befindet.
  3. Wenn X im Namespace N liegt, suchen Sie nach der Operatordeklaration in N;
  4. Wenn Y im Namespace M liegt, suchen Sie nach der Operatordeklaration in M.

Wurden in 1-4 mehrere Operator@-Deklarationen gefunden, erfolgt die Auswahl nach den Regeln zur Auflösung überladener Funktionen.

Die Suche nach Deklarationen unärer Operatoren erfolgt auf genau die gleiche Weise.

Klarstellung der Definition der komplexen Klasse

Lassen Sie uns nun die Klasse der komplexen Zahlen detaillierter konstruieren, um einige der zuvor genannten Regeln zu demonstrieren.

Klasse complex ( double re, im; public: complex& Operator+=(complex z) ( //funktioniert mit Ausdrücken wie z1 += z2 re += z.re; im += z.im; return *this; ) complex& Operator+= (double a) ( //funktioniert mit Ausdrücken wie z1 += 5; re += a; return *this; ) complex (): re(0), im(0) () //Konstruktor für die Standardinitialisierung. Also So , alle deklarierten komplexen Zahlen haben Anfangswerte (0, 0) komplex (doppeltes r): re(r), im(0) () // Der Konstruktor ermöglicht es, die Form komplex z = 11; äquivalent auszudrücken Notation z = complex( 11); complex (double r, double i): re(r), im(i) () //constructor ); komplexer Operator+(komplex z1, komplexer z2) ( //funktioniert mit Ausdrücken wie z1 + z2 komplexer res = z1; return res += z2; //unter Verwendung eines als Mitgliedsfunktion definierten Operators) komplexer Operator+(komplex z, doppeltes a) ( //verarbeitet Ausdrücke der Form z+2 complex res = z; return res += a; ) complex Operator+(double a, complex z) ( //verarbeitet Ausdrücke der Form 7+z complex res = z; return res += a; ) //…

Wie Sie dem Code entnehmen können, handelt es sich bei der Operatorüberladung um einen sehr komplexen Mechanismus, der stark wachsen kann. Dieser granulare Ansatz ermöglicht jedoch eine Überladung auch für sehr komplexe Datenstrukturen. Beispiel: Überladung eines C++-Operators in einer Vorlagenklasse.

Das Erstellen solcher Funktionen für alle kann mühsam und fehleranfällig sein. Wenn Sie beispielsweise einen dritten Typ zu den betrachteten Funktionen hinzufügen, müssen Sie Operationen berücksichtigen, die auf der Kombination der drei Typen basieren. Sie müssen 3 Funktionen mit einem Argument schreiben, 9 mit zwei und 27 mit drei. Daher kann in einigen Fällen die Implementierung all dieser Funktionen und eine erhebliche Reduzierung ihrer Anzahl durch den Einsatz der Typkonvertierung erreicht werden.

Spezialoperatoren

Der Indexierungsoperator „“ muss immer als Klassenmitglied definiert werden, da er das Verhalten eines Objekts auf ein Array reduziert. In diesem Fall kann das Indexargument einen beliebigen Typ haben, sodass Sie beispielsweise assoziative Arrays erstellen können.

Der Funktionsaufrufoperator „()“ kann als binäre Operation betrachtet werden. Beispielsweise ist in der Konstruktion „Ausdruck (Liste von Ausdrücken)“ der linke Operand der binären Operation () „Ausdruck“ und der rechte Operand eine Liste von Ausdrücken. Die Funktion „operator()()“ muss Mitglied der Klasse sein.

Der Sequenzoperator "," (Komma) wird für Objekte aufgerufen, wenn neben ihnen ein Komma steht. Der Operator ist jedoch nicht an der Auflistung der Funktionsargumente beteiligt.

Der Dereferenzierungsoperator „->“ muss ebenfalls als Mitglied der Funktion definiert werden. In seiner Bedeutung kann es als unärer Postfix-Operator definiert werden. Gleichzeitig muss es unbedingt entweder einen Link oder einen Zeiger zurückgeben, der den Zugriff auf das Objekt ermöglicht.

Aufgrund seiner Verknüpfung mit dem linken Operanden wird es auch nur als Mitglied einer Klasse definiert.

Die Zuweisungsoperatoren „=", Adressen „&" und Sequenzen „,“ müssen im öffentlichen Block definiert sein.

Endeffekt

Das Überladen von Operatoren hilft bei der Implementierung eines der Schlüsselaspekte von OOP in Bezug auf Polymorphismus. Es ist jedoch wichtig zu verstehen, dass Überladung nichts anderes ist als eine andere Art, Funktionen aufzurufen. Das Ziel der Operatorüberladung besteht häufig eher darin, das Codeverständnis zu verbessern, als in der Verbesserung bestimmter Probleme.

Und das ist noch nicht alles. Sie sollten sich auch darüber im Klaren sein, dass die Überlastung von Bedienern ein komplexer Mechanismus mit vielen Fallstricken ist. Daher ist es sehr leicht, einen Fehler zu machen. Dies ist der Hauptgrund, warum die meisten Programmierer von der Operatorüberladung abraten und sie nur als letzten Ausweg und mit vollem Vertrauen in Ihre Aktionen verwenden.

  1. Überladen Sie Operatoren nur, um die vertraute Notation zu simulieren. Um den Code besser lesbar zu machen. Wenn der Code in seiner Struktur oder Lesbarkeit komplexer wird, sollten Sie auf die Überladung von Operatoren verzichten und Funktionen verwenden.
  2. Um Platz zu sparen, verwenden Sie bei großen Operanden Argumente des Referenztyps const, um sie zu übergeben.
  3. Rückgabewerte optimieren.
  4. Lassen Sie den Kopiervorgang in Ruhe, wenn er für Ihre Klasse geeignet ist.
  5. Wenn die Standardkopieroption nicht geeignet ist, ändern Sie die Kopieroption oder deaktivieren Sie sie explizit.
  6. Member-Funktionen sollten Nicht-Member-Funktionen vorgezogen werden, wenn die Funktion Zugriff auf eine Klassendarstellung erfordert.
  7. Geben Sie den Namespace an und geben Sie die Zuordnung von Funktionen zu ihrer Klasse an.
  8. Verwenden Sie Nicht-Member-Funktionen für symmetrische Operatoren.
  9. Verwenden Sie den Operator () für Indizes in mehrdimensionalen Arrays.
  10. Verwenden Sie implizite Konvertierungen mit Vorsicht.

Letzte Aktualisierung: 12.08.2018

Neben Methoden können wir auch Operatoren überladen. Nehmen wir zum Beispiel an, wir haben die folgende Counter-Klasse:

Klassenzähler (öffentlicher int-Wert (get; set;))

Diese Klasse stellt einen Zähler dar, dessen Wert in der Value-Eigenschaft gespeichert wird.

Nehmen wir an, wir haben zwei Objekte der Klasse Counter – zwei Zähler, die wir basierend auf ihrer Value-Eigenschaft vergleichen oder hinzufügen möchten, indem wir Standardvergleichs- und Additionsoperationen verwenden:

Zähler c1 = neuer Zähler (Wert = 23); Zähler c2 = neuer Zähler (Wert = 45); bool result = c1 > c2; Zähler c3 = c1 + c2;

Derzeit ist jedoch weder die Vergleichs- noch die Additionsoperation für Counter-Objekte verfügbar. Diese Operationen können für eine Reihe primitiver Typen verwendet werden. Beispielsweise können wir standardmäßig numerische Werte hinzufügen, aber der Compiler weiß nicht, wie er Objekte komplexer Typen – Klassen und Strukturen – hinzufügt. Und dafür müssen wir die benötigten Operatoren überlasten.

Das Überladen von Operatoren besteht darin, eine spezielle Methode in der Klasse zu definieren, für deren Objekte wir einen Operator definieren möchten:

Öffentlicher statischer Return_Type-Operator (Parameter) ()

Diese Methode muss über öffentliche statische Modifikatoren verfügen, da die Operatorüberladung für alle Objekte dieser Klasse verwendet wird. Als nächstes kommt der Name des Rückgabetyps. Der Rückgabetyp stellt den Typ dar, dessen Objekte wir empfangen möchten. Als Ergebnis des Hinzufügens von zwei Counter-Objekten erwarten wir beispielsweise, dass wir ein neues Counter-Objekt erhalten. Und als Ergebnis des Vergleichs der beiden möchten wir ein Objekt vom Typ bool erhalten, das angibt, ob der bedingte Ausdruck wahr oder falsch ist. Abhängig von der Aufgabe können die Rückgabetypen jedoch beliebig sein.

Dann gibt es anstelle des Namens der Methode den Schlüsselwortoperator und den Operator selbst. Und dann werden die Parameter in Klammern aufgeführt. Binäre Operatoren benötigen zwei Parameter, unäre Operatoren einen Parameter. Und in jedem Fall muss einer der Parameter den Typ darstellen – die Klasse oder Struktur, in der der Operator definiert ist.

Lassen Sie uns beispielsweise eine Reihe von Operatoren für die Counter-Klasse überladen:

Klassenzähler ( öffentlicher int Wert ( get; set; ) öffentlicher statischer Zähleroperator +(Zähler c1, Zähler c2) ( neuer Zähler zurückgeben ( Wert = c1.Wert + c2.Wert ); ) öffentlicher statischer Bool-Operator >(Zähler c1, Zähler c2) ( return c1.Value > c2.Value; ) öffentlicher statischer Bool-Operator<(Counter c1, Counter c2) { return c1.Value < c2.Value; } }

Da alle überladenen Operatoren binär sind, also auf zwei Objekte angewendet werden, gibt es für jede Überladung zwei Parameter.

Da wir bei der Additionsoperation zwei Objekte der Klasse Counter hinzufügen möchten, akzeptiert der Operator zwei Objekte dieser Klasse. Und da wir durch die Addition ein neues Counter-Objekt erhalten möchten, wird diese Klasse auch als Rückgabetyp verwendet. Alle Aktionen dieses Operators laufen auf die Erstellung eines neuen Objekts hinaus, dessen Value-Eigenschaft die Werte der Value-Eigenschaft beider Parameter kombiniert:

Öffentlicher statischer Zähleroperator +(Zähler c1, Zähler c2) ( neuen Zähler zurückgeben ( Wert = c1.Wert + c2.Wert ); )

Außerdem wurden zwei Vergleichsoperatoren neu definiert. Wenn wir einen dieser Vergleichsoperatoren überschreiben, müssen wir auch den zweiten dieser Operatoren überschreiben. Die Vergleichsoperatoren selbst vergleichen die Werte der Value-Eigenschaften und geben je nach Ergebnis des Vergleichs entweder true oder false zurück.

Jetzt verwenden wir überladene Operatoren im Programm:

Static void Main(string args) ( Counter c1 = new Counter ( Value = 23 ); Counter c2 = new Counter ( Value = 45 ); bool result = c1 > c2; Console.WriteLine(result); // false Counter c3 = c1 + c2; Console.WriteLine(c3.Value); // 23 + 45 = 68 Console.ReadKey(); )

Es ist erwähnenswert, dass wir diese Methode auch überladen können, d. h. eine andere Version dafür erstellen, da es sich bei der Operatordefinition im Wesentlichen um eine Methode handelt. Fügen wir beispielsweise der Counter-Klasse einen weiteren Operator hinzu:

Öffentlicher statischer int-Operator +(Counter c1, int val) ( return c1.Value + val; )

Diese Methode addiert den Wert der Value-Eigenschaft und eine bestimmte Zahl und gibt deren Summe zurück. Und wir können auch diesen Operator verwenden:

Zähler c1 = neuer Zähler (Wert = 23); int d = c1 + 27; // 50 Console.WriteLine(d);

Es ist zu berücksichtigen, dass sich beim Überladen die Objekte, die dem Operator über Parameter übergeben werden, nicht ändern dürfen. Beispielsweise können wir einen Inkrementoperator für die Counter-Klasse definieren:

Öffentlicher statischer Zähleroperator ++(Counter c1) ( c1.Value += 10; return c1; )

Da der Operator unär ist, benötigt er nur einen Parameter – ein Objekt der Klasse, in der der Operator definiert ist. Dies ist jedoch eine falsche Definition von Inkrement, da der Bediener die Werte seiner Parameter nicht ändern sollte.

Und eine korrektere Überladung des Inkrementoperators würde so aussehen:

Öffentlicher statischer Zähleroperator ++(Zähler c1) ( neuen Zähler zurückgeben ( Wert = c1.Wert + 10 ); )

Das heißt, es wird ein neues Objekt zurückgegeben, das einen erhöhten Wert in der Value-Eigenschaft enthält.

In diesem Fall müssen wir keine separaten Operatoren für die Präfix- und Postfix-Inkrementierung (sowie die Dekrementierung) definieren, da eine Implementierung in beiden Fällen funktioniert.

Zum Beispiel verwenden wir die Präfix-Inkrementierungsoperation:

Counter counter = new Counter() ( Value = 10 ); Console.WriteLine($"(counter.Value)"); // 10 Console.WriteLine($"((++counter).Value)"); // 20 Console.WriteLine($"(counter.Value)"); // 20

Konsolenausgabe:

Jetzt verwenden wir das Postfix-Inkrement:

Counter counter = new Counter() ( Value = 10 ); Console.WriteLine($"(counter.Value)"); // 10 Console.WriteLine($"((counter++).Value)"); // 10 Console.WriteLine($"(counter.Value)"); // 20

Konsolenausgabe:

Es ist auch erwähnenswert, dass wir die wahren und falschen Operatoren überschreiben können. Definieren wir sie beispielsweise in der Counter-Klasse:

Klassenzähler ( public int Value ( get; set; ) public static bool Operator true(Counter c1) ( return c1.Value != 0; ) public static bool Operator false(Counter c1) ( return c1.Value == 0; ) // der Rest des Klasseninhalts)

Diese Operatoren werden überladen, wenn wir ein Objekt eines Typs als Bedingung verwenden möchten. Zum Beispiel:

Counter counter = new Counter() ( Value = 0 ); if (Zähler) Console.WriteLine(true); sonst Console.WriteLine(false);

Beim Überladen von Operatoren müssen Sie berücksichtigen, dass nicht alle Operatoren überladen werden können. Insbesondere können wir die folgenden Operatoren überladen:

    unäre Operatoren +, -, !, ~, ++, --

    binäre Operatoren +, -, *, /, %

    Vergleichsoperationen ==, !=,<, >, <=, >=

    logische Operatoren &&, ||

    Zuweisungsoperatoren +=, -=, *=, /=, %=

Und es gibt eine Reihe von Operatoren, die nicht überladen werden können, zum Beispiel der Gleichheitsoperator = oder der ternäre Operator?: sowie eine Reihe anderer.

Eine vollständige Liste der überladenen Operatoren finden Sie in der MSDN-Dokumentation

Beim Überladen von Operatoren müssen wir auch bedenken, dass wir die Priorität eines Operators oder seine Assoziativität nicht ändern können, wir können keinen neuen Operator erstellen oder die Logik von Operatoren in Typen ändern, die in .NET die Standardeinstellung sind.

gastroguru 2017