Esempi di sovraccarico dell'operatore, che hanno senso [chiuso]


12

Durante l'apprendimento di C #, ho scoperto che C # supporta il sovraccarico dell'operatore. Ho un buon esempio che:

  1. Ha senso (es. Aggiungere classe chiamata pecora e mucca)
  2. Non è un esempio di concatenazione di due stringhe

Sono benvenuti esempi tratti dalla Biblioteca di classi di base.


10
Definisci "senso", per favore! Scherzi a parte, i dibattiti amari e appassionati su questo punto mostrano che c'è un grande disaccordo su esattamente questo. Molte autorità rifiutano gli operatori sovraccarichi perché possono fare cose assolutamente inaspettate. Altri rispondono che i nomi dei metodi possono anche essere scelti per essere completamente non intuitivi, ma questo non è un motivo per rifiutare i blocchi di codice denominati! Quasi sicuramente non otterrai esempi che sono generalmente considerati sensibili. Gli esempi che sembrano sensibili a voi - forse.
Kilian Foth,

Completamente d'accordo con @KilianFoth. In definitiva il programma che compila, ha senso compilare. Ma se sovraccarico ==per fare moltiplicazione, ha senso per me ma potrebbe non avere senso per gli altri! Questa domanda riguarda la legittimità di quali linguaggi di programmazione delle strutture o stiamo parlando di "codificare le migliori pratiche"?
Dipan Mehta,

Risposte:


27

Gli esempi ovvi di sovraccarico appropriato da parte dell'operatore sono le classi che si comportano nello stesso modo in cui operano i numeri. Quindi le classi di BigInt (come suggerisce Jalayn ), i numeri complessi o le classi di matrici (come suggerisce Superbest ) hanno tutte le stesse operazioni che i numeri ordinari hanno così ben mappato sugli operatori matematici, mentre le operazioni di tempo (come suggerito da svick ) si associano bene su un sottoinsieme di tali operazioni.

Leggermente più astrattamente, gli operatori potrebbero essere utilizzati quando si eseguono operazioni come set , quindi operator+potrebbe essere un'unione , operator-potrebbe essere un complemento ecc. Questo però inizia a estendere il paradigma, soprattutto se si utilizza l'operatore addizione o moltiplicazione per un'operazione che non è t commutativo , come ci si potrebbe aspettare che siano.

C # stesso ha un eccellente esempio di sovraccarico dell'operatore non numerico . Usa +=e -=per aggiungere e sottrarre delegati , cioè registrarli e cancellarli. Funziona bene perché gli operatori +=e -=funzionano come ci si aspetterebbe da loro, e questo si traduce in un codice molto più conciso.

Per il purista, uno dei problemi con l' +operatore stringa è che non è commutativo. "a"+"b"non è lo stesso di "b"+"a". Comprendiamo questa eccezione per le stringhe perché è così comune, ma come possiamo sapere se l'utilizzo operator+su altri tipi sarà commutativo o no? La maggior parte delle persone supporrà che lo sia, a meno che l'oggetto non sia simile a una stringa , ma non si sa mai veramente cosa assumeranno le persone.

Come per le stringhe, anche le debolezze delle matrici sono abbastanza conosciute. È ovvio che si Matrix operator* (double, Matrix)tratta di una moltiplicazione scalare, mentre Matrix operator* (Matrix, Matrix)sarebbe ad esempio una moltiplicazione di matrice (cioè una matrice di moltiplicazioni di punti-prodotto).

Allo stesso modo, l'uso di operatori con delegati è così chiaramente rimosso dalla matematica che è improbabile che tu commetta questi errori.

Per inciso, alla conferenza ACCU del 2011 , Roger Orr e Steve Love hanno presentato una sessione su Alcuni oggetti sono più uguali di altri - uno sguardo ai molti significati di uguaglianza, valore e identità . Le loro diapositive sono scaricabili , così come l' appendice di Richard Harris sull'uguaglianza in virgola mobile . Sommario: Essere molto attenti con operator==, hic sunt leones!

Il sovraccarico dell'operatore è una tecnica semantica molto potente, ma è facile da usare eccessivamente. Idealmente, dovresti usarlo solo in situazioni in cui è molto chiaro dal contesto quale sia l'effetto di un operatore sovraccarico. In molti modi a.union(b)è più chiaro di a+b, ed a*bè molto più oscuro di a.cartesianProduct(b), soprattutto perché il risultato di un prodotto cartesiano sarebbe un SetLike<Tuple<T,T>>piuttosto che un SetLike<T>.

I veri problemi con il sovraccarico dell'operatore arrivano quando un programmatore presume che una classe si comporti in un modo, ma in realtà si comporti in un altro. Questo tipo di scontro semantico è ciò che sto suggerendo che è importante cercare di evitare.


1
Dici che gli operatori sulle matrici mappano davvero bene, ma la moltiplicazione delle matrici non è neanche commutativa. Anche gli operatori delegati sono ancora più forti. Puoi farlo d1 + d2per due delegati dello stesso tipo.
svick,

1
@Mark: il "prodotto punto" è definito solo sui vettori; moltiplicare due matrici si chiama semplicemente "moltiplicazione di matrici". La distinzione è molto più che semantica: il prodotto punto restituisce uno scalare, mentre la moltiplicazione della matrice restituisce una matrice (ed è, a proposito, non commutativa) .
BlueRaja - Danny Pflughoeft,

26

Sono sorpreso che nessuno abbia menzionato uno dei casi più interessanti in BCL: DateTimee TimeSpan. Puoi:

  • aggiungere o sottrarre due TimeSpans per ottenerne un altroTimeSpan
  • usa meno unario su a TimeSpanper ottenere un negatoTimeSpan
  • sottrarre due DateTimes per ottenere aTimeSpan
  • aggiungi o sottrai TimeSpanda a DateTimeper ottenerne un altroDateTime

Un'altra serie di operatori che potrebbero hanno senso su un sacco di tipi sono <, >, <=, >=. Nel BCL, ad esempio, Versionli implementa.


Esempio molto reale piuttosto che teorie pedanti!
SIslam,

7

Il primo esempio che mi viene in mente è l'implementazione di BigInteger , che ti consente di lavorare con numeri interi con segno di grandi dimensioni. Controlla il collegamento MSDN per vedere quanti operatori sono stati sovraccaricati (vale a dire, c'è un grande elenco e non ho verificato se tutti gli operatori sono stati sovraccaricati, ma sembra certamente così)

Inoltre, poiché faccio anche Java e Java non consente agli operatori di sovraccarico, è incredibilmente più dolce scrivere

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

Rispetto a Java:

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

5

Sono contento di averlo visto perché stavo scherzando con Irony e ha un grande utilizzo del sovraccarico dell'operatore. Ecco un esempio di cosa può fare.

Quindi Irony è un ".NET Language Implementation Kit" ed è un generatore di parser (che genera un parser LALR). Invece di dover imparare una nuova sintassi / lingua come generatori di parser come yacc / lex, scrivi la grammatica in C # con il sovraccarico dell'operatore. Ecco una semplice grammatica BNF

// 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

Quindi è una piccola grammatica semplice (per favore scusate se ci sono incoerenze mentre sto solo imparando BNF e costruendo grammatiche). Ora diamo un'occhiata al C #:

  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("+") | "-" | "*" | "/" | "**";

Come puoi vedere, con l'operatore in sovraccarico, scrivere la grammatica in C # significa quasi esattamente scrivere la grammatica in BNF. Per me, questo non ha solo senso, ma è anche un ottimo uso del sovraccarico dell'operatore.


3

L'esempio chiave è operator == / operator! =.

Se vuoi confrontare facilmente due oggetti con i valori dei dati anziché i riferimenti, ti consigliamo di sovraccaricare .Equals (e.GetHashCode!) E potresti voler fare anche gli operatori! = E == per coerenza.

Non ho mai visto alcun sovraccarico selvaggio di altri operatori in C # (immagino ci siano casi limite in cui potrebbe essere utile).



0

Un buon uso del sovraccarico può essere raro, ma succede.

overloading operator == and operator! = mostra due scuole di pensiero: quelle per dirlo rende le cose più facili e quelle contro il dire che impedisce il confronto di indirizzi (cioè sto indicando lo stesso posto nella memoria, non solo una copia dello stesso oggetto).

Trovo che i sovraccarichi dell'operatore del cast siano utili in situazioni specifiche. Ad esempio, ho dovuto serializzare / deserializzare in XML un booleano rappresentato come 0 o 1. L'operatore di cast giusto (implicito o esplicito, dimentico) da booleano a int e viceversa ha fatto il trucco.


4
Non impedisce il confronto degli indirizzi: è ancora possibile utilizzare object.ReferenceEquals().
dan04,

@ dan04 Molto, molto buono a sapersi!
MPelletier,

Un altro modo per confrontare gli indirizzi è forzare l'uso degli oggetti ==mediante il cast: (object)foo == (object)barconfronta sempre i riferimenti. Ma preferirei ReferenceEquals(), come menziona @ dan04 perché è più chiaro cosa fa.
svick,

0

Non rientrano nella categoria di cose a cui le persone pensano in genere quando si tratta di sovraccarico dell'operatore, ma penso che uno degli operatori più importanti per essere in grado di sovraccaricare sia l' operatore di conversione .

Gli operatori di conversione sono particolarmente utili per i tipi di valore che possono "eliminare lo zucchero" in un tipo numerico o possono agire come un tipo numerico in alcuni contesti. Ad esempio, potresti definire un Idtipo speciale che rappresenta un determinato identificatore e potresti fornire una conversione implicita in intmodo da poter passare un Idmetodo che accetta un int, ma una conversione esplicita da inta in Idmodo che nessuno possa passare un intin un metodo che prende un Idsenza prima lanciarlo.

Ad esempio al di fuori di C #, il linguaggio Python include molti comportamenti speciali implementati come operatori sovraccaricabili. Questi includono l' inoperatore per il test di appartenenza, l' ()operatore per chiamare un oggetto come se fosse una funzione e l' lenoperatore per determinare la lunghezza o la dimensione di un oggetto.

E poi hai lingue come Haskell, Scala e molti altri linguaggi funzionali, in cui nomi come +sono solo funzioni ordinarie e non operatori (e c'è il supporto linguistico per usare le funzioni in posizione infix).


0

La struct point nel System.Drawing namespace utilizza sovraccarico per confrontare due diverse località utilizzando l'overloading degli operatori.

 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" );

Come puoi vedere, è molto più semplice confrontare le coordinate X e Y di due posizioni usando il sovraccarico.


0

Se hai familiarità con il vettore matematico potresti vedere un uso per sovraccaricare l' +operatore. È possibile aggiungere un vettore a=[1,3]con b=[2,-1]e ottenere c=[3,2].

Anche il sovraccarico degli uguali (==) può essere utile (anche se probabilmente è meglio implementare un equals()metodo). Per continuare gli esempi vettoriali:

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

-2

Immagina un pezzo di codice per disegnare su un modulo

{
  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.
  ..
}

Un altro esempio comune è quando una struttura viene utilizzata per conservare le informazioni sulla posizione sotto forma di un vettore.

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

solo per essere usato successivamente come

{
    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
Aggiungete vettori, non posizioni: \ Questo è un buon esempio di quando nonoperator+ dovrebbe essere sovraccaricato (è possibile implementare un punto in termini di un vettore, ma non si dovrebbe essere in grado di aggiungere due punti)
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft: l' aggiunta di posizioni per produrre un'altra posizione non ha senso, ma sottrarle (per produrre un vettore), così come la media delle stesse. Si potrebbe calcolare la media di p1, p2, p3 e p4 via p1+((p2-p1)+(p3-p1)+(p4-p1))/4, ma sembra un po 'imbarazzante.
supercat,

1
Nella geometria affine è possibile eseguire l'algebra con punti e linee, come addizioni, ridimensionamenti, ecc. L'implementazione richiede però coordinate omogenee, che in genere vengono utilizzate nella grafica 3D. L'aggiunta di due punti si traduce in realtà nella loro media.
Ja72,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.