Beispiele für sinnvolle Überladungen von Operatoren


12

Während ich C # lernte, stellte ich fest, dass das C # das Überladen von Operatoren unterstützt. Ich habe ein Problem mit einem guten Beispiel, welches:

  1. Sinnvoll (zB Hinzufügen der Klasse "Schaf und Kuh")
  2. Ist kein Beispiel für die Verkettung von zwei Zeichenfolgen

Beispiele aus der Basisklassenbibliothek sind willkommen.


10
Definieren Sie bitte "Sinn"! Im Ernst, die erbitterten und leidenschaftlichen Debatten über genau diesen Punkt zeigen, dass es darüber große Meinungsverschiedenheiten gibt. Viele Behörden lehnen überlastete Bediener ab, weil sie dazu gebracht werden können, völlig unerwartete Dinge zu tun. Andere antworten, dass Methodennamen ebenfalls völlig uninteressant gewählt werden können, aber das ist kein Grund, benannte Codeblöcke abzulehnen! Sie werden mit ziemlicher Sicherheit keine Beispiele erhalten, die allgemein als sinnvoll angesehen werden. Beispiele, die Ihnen sinnvoll erscheinen - vielleicht.
Kilian Foth

Stimmen Sie voll und ganz mit @KilianFoth überein. Letztendlich macht das Programm, das kompiliert, für den Compiler Sinn. Aber wenn es überladen ist ==, eine Multiplikation durchzuführen, ist dies für mich sinnvoll, für andere jedoch möglicherweise nicht ! Ist diese Frage zur Legitimität welcher Facility-Programmiersprachen oder handelt es sich um "Coding Best Practices"?
Dipan Mehta

Antworten:


27

Die offensichtlichen Beispiele für eine angemessene Überladung von Operatoren sind Klassen, die sich genauso verhalten wie Zahlen. So BigInt Klassen (wie Jalayn schon sagt), komplexe Zahlen oder Matrix - Klassen (wie Superbest schon sagt) alle die gleichen Operationen haben , dass gewöhnliche Zahlen so wirklich gut auf mathematischen Operatoren Karte haben, während der Zeit - Operationen (wie vorgeschlagen svick ) schön auf eine Teilmenge der Karte dieser Operationen.

Etwas abstrakter könnten Operatoren verwendet werden, wenn set-ähnliche Operationen ausgeführt werden, also operator+eine Vereinigung , operator-eine Ergänzung usw. Dies erweitert jedoch das Paradigma, insbesondere wenn Sie den Additions- oder Multiplikationsoperator für eine Operation verwenden, die nicht ' t kommutativ , wie Sie vielleicht erwarten.

C # selbst bietet ein hervorragendes Beispiel für das Überladen nicht numerischer Operatoren. Es verwendet +=und -=, um Delegierte hinzuzufügen und zu subtrahieren , dh sie zu registrieren und die Registrierung aufzuheben. Dies funktioniert gut, da die Operatoren +=und so -=funktionieren, wie Sie es erwarten würden, und dies führt zu wesentlich präziserem Code.

Für den Puristen ist eines der Probleme mit dem String- +Operator, dass er nicht kommutativ ist. "a"+"b"ist nicht dasselbe wie "b"+"a". Wir verstehen diese Ausnahme für Strings, weil sie so häufig vorkommt, aber wie können wir feststellen, ob die Verwendung operator+für andere Typen kommutativ ist oder nicht? Die meisten Menschen gehen davon aus, dass dies der Fall ist, es sei denn, das Objekt ist stringartig , aber Sie wissen nie wirklich, was die Menschen davon ausgehen.

Wie bei Saiten sind auch bei Matrizen die Schwächen bekannt. Es ist offensichtlich, dass Matrix operator* (double, Matrix)es sich um eine Skalarmultiplikation handelt, wohingegen Matrix operator* (Matrix, Matrix)es sich beispielsweise um eine Matrixmultiplikation (dh eine Matrix aus Skalarmultiplikationen) handeln würde .

In ähnlicher Weise ist die Verwendung von Operatoren mit Delegierten so weit von der Mathematik entfernt, dass Sie diese Fehler wahrscheinlich nicht machen werden.

Im Übrigen an der 2011 ACCU Konferenz , Roger Orr & Steve Liebe präsentiert eine Sitzung auf einige Objekte als andere gleich sind - ein Blick auf die vielen Bedeutungen der Gleichheit, der Wert und Identität . Ihre Folien können heruntergeladen werden , ebenso wie der Anhang von Richard Harris zur Gleitkomma-Gleichheit . Fazit: Sei sehr vorsichtig mit operator==, hier seid Drachen!

Das Überladen von Operatoren ist eine sehr leistungsfähige semantische Technik, die jedoch leicht zu überbeanspruchen ist. Idealerweise sollten Sie es nur in Situationen verwenden, in denen aus dem Kontext klar hervorgeht, wie sich ein überladener Operator auswirkt. In vielerlei Hinsicht a.union(b)ist klarer als a+b, und a*bist viel dunkler als a.cartesianProduct(b), zumal das Ergebnis eines kartesischen Produktes SetLike<Tuple<T,T>>eher ein a als ein a wäre SetLike<T>.

Die wirklichen Probleme beim Überladen von Operatoren treten auf, wenn ein Programmierer davon ausgeht, dass sich eine Klasse auf die eine oder andere Weise verhält. Ich schlage vor, dass diese Art von semantischem Konflikt vermieden werden sollte.


1
Sie sagen, Operatoren auf Matrizen lassen sich sehr gut abbilden, aber die Matrixmultiplikation ist auch nicht kommutativ. Auch Betreiber von Delegierten sind noch stärker. Sie können d1 + d2zwei beliebige Delegaten desselben Typs angeben.
Svick

1
@Mark: Das "Skalarprodukt" wird nur für Vektoren definiert. Das Multiplizieren von zwei Matrizen wird einfach "Matrixmultiplikation" genannt. Die Unterscheidung ist mehr als nur semantisch: Das Skalarprodukt liefert einen Skalarwert, während die Matrixmultiplikation eine Matrix liefert (und übrigens nicht kommutativ ist) .
BlueRaja - Danny Pflughoeft

26

Ich bin überrascht, dass niemand einen der interessanteren Fälle in BCL erwähnt hat: DateTimeund TimeSpan. Du kannst:

  • Addiere oder subtrahiere zwei TimeSpans, um eine andere zu erhaltenTimeSpan
  • benutze unary minus auf a TimeSpanum ein negiertes zu erhaltenTimeSpan
  • subtrahiere zwei DateTimes, um a zu erhaltenTimeSpan
  • addiere oder subtrahiere TimeSpanvon a DateTime, um ein anderes zu erhaltenDateTime

Ein weiterer Satz von Operatoren , die Sinne auf eine Menge von Typen machen könnte <, >, <=, >=. In der BCL Versionimplementiert sie zum Beispiel .


Sehr reales Beispiel statt pedantischer Theorien!
Islam

7

Das erste Beispiel, das mir in den Sinn kommt, ist die Implementierung von BigInteger , mit der Sie mit großen Ganzzahlen mit Vorzeichen arbeiten können. Überprüfen Sie den MSDN-Link, um festzustellen, wie viele Operatoren überlastet wurden (das heißt, es gibt eine große Liste, und ich habe nicht überprüft, ob alle Operatoren überlastet wurden, aber es scheint so).

Da ich auch Java verwende und Java das Überladen von Operatoren nicht zulässt, ist das Schreiben unglaublich süßer

BigInteger bi = new BigInteger(0);
bi += 10;

Dann in Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));

5

Ich bin froh, dass ich das gesehen habe, weil ich mit Irony rumgespielt habe und es eine GROSSARTIGE Verwendung von Operatorüberladung hat. Hier ist ein Beispiel dafür, was es tun kann.

Irony ist also ein ".NET Language Implementation Kit" und ein Parser-Generator (der einen LALR-Parser generiert). Anstatt eine neue Syntax / Sprache wie Parser-Generatoren wie yacc / lex lernen zu müssen, schreiben Sie die Grammatik in C # mit der Operatorüberladung. Hier ist eine einfache BNF-Grammatik

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Es ist also eine einfache kleine Grammatik (entschuldigen Sie bitte, wenn es Unstimmigkeiten gibt, da ich nur BNF lerne und Grammatiken konstruiere). Schauen wir uns nun das C # an:

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

Wie Sie sehen, wird beim Überladen des Operators beim Schreiben der Grammatik in C # fast genau die Grammatik in BNF geschrieben. Für mich macht das nicht nur Sinn, sondern ist auch eine hervorragende Möglichkeit, den Operator zu überladen.


3

Das Schlüsselbeispiel ist operator == / operator! =.

Wenn Sie zwei Objekte einfach anhand von Datenwerten anstatt anhand von Verweisen vergleichen möchten, müssen Sie .Equals (und .GetHashCode!) Überladen und möglicherweise auch die Operatoren! = Und == verwenden, um die Konsistenz zu gewährleisten.

Ich habe noch nie wilde Überladungen anderer Operatoren in C # gesehen (ich stelle mir vor, dass es Edge-Fälle gibt, in denen dies nützlich sein könnte).


1

Dieses Beispiel von MSDN zeigt, wie komplexe Zahlen implementiert werden und der normale Operator + verwendet wird.

Ein weiteres Beispiel zeigt, wie es für das Hinzufügen einer Matrix gemacht wird, und erklärt, wie es nicht verwendet wird, um ein Auto zu einer Garage hinzuzufügen (lesen Sie den Link).


0

Guter Gebrauch von Überlastung kann selten sein, aber es kommt vor.

Das Überladen von operator == und operator! = zeigt zwei Denkrichtungen: Die, die es sagen, machen die Sache einfacher, und die, die dagegen sprechen, verhindern das Vergleichen von Adressen (dh, ich zeige auf die exakt gleiche Stelle im Speicher, nicht nur auf eine Kopie derselben) Objekt).

Überladungen von Cast-Operatoren halte ich in bestimmten Situationen für nützlich. Zum Beispiel musste ich einen Booleschen Wert, der als 0 oder 1 dargestellt wird, in XML serialisieren / deserialisieren. Der richtige (implizite oder explizite, ich vergesse) Umsetzungsoperator von Boolesch nach int und zurück hat den Trick gemacht.


4
Es verhindert nicht das Vergleichen von Adressen: Sie können immer noch verwenden object.ReferenceEquals().
Dan04

@ dan04 Sehr sehr gut zu wissen!
MPelletier

Eine andere Möglichkeit, Adressen zu vergleichen, besteht darin, die Verwendung von Objekten ==durch Casting zu erzwingen : (object)foo == (object)barVergleicht immer Referenzen. Aber ich würde es vorziehen ReferenceEquals(), als @ dan04 erwähnt, weil es klarer ist, was es tut.
Svick

0

Sie gehören nicht zu den Dingen, an die die Leute normalerweise denken, wenn sie Operatoren überladen, aber ich denke, einer der wichtigsten Operatoren, um überladen zu können, ist der Conversion-Operator .

Konvertierungsoperatoren sind besonders nützlich für Werttypen, die einen numerischen Typ "entzuckern" oder in einigen Kontexten wie ein numerischer Typ wirken können. Zum Beispiel könnten Sie einen speziellen definieren IdTypen, der eine bestimmte Kennung darstellt, und man konnte eine bieten implizite Konvertierung , intso dass Sie eine passieren können Idein Verfahren , das eines nimmt int, sondern eine explict Umwandlung von intzu Idso kann niemand einen Pass intin eine Methode, die ein dauert, Idohne es zuerst zu werfen.

Als Beispiel außerhalb von C # enthält die Python-Sprache viele spezielle Verhaltensweisen, die als überladbare Operatoren implementiert sind. Dazu gehören der inOperator zum Testen der Zugehörigkeit, der ()Operator zum Aufrufen eines Objekts als ob es eine Funktion wäre und der Operator zum Testen der Zugehörigkeitlen Operator zum Bestimmen der Länge oder Größe eines Objekts.

Und dann haben Sie Sprachen wie Haskell, Scala und viele andere funktionale Sprachen, in denen Namen wie +nur normale Funktionen und überhaupt keine Operatoren sind (und es gibt Sprachunterstützung für die Verwendung von Funktionen in Infix-Position).


0

Die Punktstruktur im System.Drawing- Namespace verwendet die Überladung, um zwei verschiedene Positionen mithilfe der Operatorüberladung zu vergleichen.

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

Wie Sie sehen, ist es mit der Überladung viel einfacher, die X- und Y-Koordinaten zweier Positionen zu vergleichen.


0

Wenn Sie mit dem mathematischen Vektor vertraut sind, wird der +Operator möglicherweise überladen . Sie könnten einen Vektor a=[1,3]mit hinzufügen b=[2,-1]und erhaltenc=[3,2] .

Das Überladen von equals (==) kann ebenfalls nützlich sein (auch wenn es wahrscheinlich besser ist, eine equals()Methode zu implementieren ). So setzen Sie die Vektorbeispiele fort:

v1=[1,3]
v2=[1,3]
v1==v2 // True

-2

Stellen Sie sich einen Code zum Zeichnen in einem Formular vor

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

Ein weiteres bekanntes Beispiel ist die Verwendung einer Struktur zum Speichern von Positionsinformationen in Form eines Vektors.

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

Nur zur späteren Verwendung als

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}

4
Sie fügen Vektoren, nicht Positionen: \ Dies ist ein gutes Beispiel dafür , wann operator+sollte nicht überlastet werden (ein Punkt in Bezug auf einen Vektor implementieren können, aber man sollte hinzufügen zwei Punkte nicht in der Lage sein)
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft: Hinzufügen Stellungen eine andere Position zu ergeben , ist nicht sinnvoll, aber sie Subtrahieren (ein Vektor erhalten wurde ) der Fall ist, wird als Mittelungs ihnen. Man könnte den Durchschnitt von p1, p2, p3 und p4 über berechnen p1+((p2-p1)+(p3-p1)+(p4-p1))/4, aber das scheint etwas umständlich.
Supercat

1
In affiner Geometrie können Sie Algebra mit Punkten und Linien wie Addition, Skalierung usw. ausführen. Die Implementierung erfordert jedoch homogene Koordinaten, die in der Regel ohnehin in 3D-Grafiken verwendet werden. Die Addition von zwei Punkten ergibt tatsächlich ihren Durchschnitt.
Ja72
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.