Esempi di overloading dell'operatore, che ha senso [chiuso]

11

Mentre imparo C #, ho trovato che, il C # supporta l'overloading dell'operatore. Ho un problema con un buon esempio che:

  1. Ha senso (ad esempio aggiungendo classe denominata pecora e mucca)
  2. Non è un esempio di concatenazione di due stringhe

Esempi dalla Base Class Library sono i benvenuti.

    
posta Paweł Sołtysiak 23.02.2012 - 13:48
fonte

11 risposte

26

Gli esempi ovvi di appropriato sovraccarico dell'operatore sono le classi che si comportano nello stesso modo in cui operano i numeri. Quindi classi BigInt (come Jalayn suggerisce), numeri complessi o matrix (come suggerito da Superbest ) hanno tutte le stesse operazioni dei numeri ordinari avere una mappa così bene sugli operatori matematici, mentre le operazioni time (come suggerito da svick ) mappare bene su un sottoinsieme di quelle operazioni.

Un po 'più astrattamente, gli operatori potrebbero essere usati quando si eseguono operazioni impostate come , quindi operator+ potrebbe essere un union , operator- potrebbe essere un complemento ecc. Questo inizia ad allungare il paradigma, specialmente se si usa l'operatore addizione o moltiplicazione per un'operazione che non è commutativa , come potreste aspettarvi che siano.

C # ha un eccellente esempio di sovraccarico dell'operatore non numerico . Utilizza += e -= per aggiungere e sottrarre delegati , vale a dire registrarli e cancellarli. Funziona bene perché gli operatori += e -= funzionano come ci si aspetterebbe, 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 è uguale a "b"+"a" . Comprendiamo questa eccezione per le stringhe perché è così comune, ma come possiamo sapere se usare operator+ su altri tipi sarà commutativo o no? La maggior parte delle persone supporterà che lo sia, a meno che l'oggetto non sia simile a una stringa , ma non si sa mai realmente cosa verrà assunto dalla gente.

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

Allo stesso modo, l'utilizzo di operatori con delegati è talmente lontano dalla matematica che è improbabile che tu faccia quegli errori.

Per inciso, alla conferenza ACCU 2011 , Roger Orr & Steve Love ha 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 , come lo è il Appendice sull'uguaglianza in virgola mobile . Riepilogo: Stai molto attento con operator== , qui si trovano i draghi!

L'overloading dell'operatore è una tecnica semantica molto potente, ma è facile da usare. 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 , e 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 l'overloading dell'operatore arrivano quando un programmatore assume che una classe si comporterà in un modo, ma in realtà si comporta in un altro. Questa sorta di scontro semantico è ciò che sto suggerendo che è importante cercare di evitare.

    
risposta data 23.02.2012 - 14:44
fonte
23

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

  • aggiungi o sottrai due TimeSpan s per ottenere un altro TimeSpan
  • usa un unus meno su un TimeSpan per ottenere un% co_de negato
  • sottrai due TimeSpan s per ottenere un DateTime
  • aggiungi o sottrai TimeSpan da TimeSpan per ottenere un altro DateTime

Un altro gruppo di operatori che potrebbe avere senso su molti tipi sono DateTime , < , > , <= . Nel BCL, ad esempio >= li implementa.

    
risposta data 23.02.2012 - 19:43
fonte
6

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

Inoltre, dato che anche Java e Java non consentono di sovraccaricare gli operatori, è incredibilmente più dolce scrivere

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

Than, in Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));
    
risposta data 23.02.2012 - 13:54
fonte
5

Sono contento di averlo visto perché mi sono divertito con Irony e ha un ottimo utilizzo dell'overloading dell'operatore. Ecco un esempio di ciò che può fare.

Quindi Irony è un ".NET Implementation Kit" ed è un generatore di parser (che genera un parser LALR). Invece di dover imparare una nuova sintassi / linguaggio come i generatori di parser come yacc / lex, si scrive 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 grammatica semplice (scusatelo se ci sono incoerenze perché sto solo imparando BNF e costruendo grammatiche). Ora vediamo il 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'overloading dell'operatore, scrivere la grammatica in C # è quasi esattamente scrivere la grammatica in BNF. Per me, questo non ha solo senso, ma è un ottimo uso dell'overloading dell'operatore.

    
risposta data 24.02.2012 - 15:03
fonte
3

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

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

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

    
risposta data 23.02.2012 - 13:54
fonte
1

Questo esempio di MSDN mostra come implementare complessi numeri e farli usare il normale operatore +.

Un altro esempio mostra come eseguirlo per l'aggiunta della matrice e spiega anche come non usarlo per aggiungere una macchina a un garage (leggi il link).

    
risposta data 23.02.2012 - 13:59
fonte
0

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

operatore di overload == e operatore! = mostra due scuole di pensiero: quelle per dire che rende le cose più facili, e quelle contro dicendo che impedisce il confronto degli indirizzi (cioè sto indicando lo stesso identico 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 corretto (implicito o esplicito, ho dimenticato) da booleano a int e back ha fatto il trucco.

    
risposta data 23.02.2012 - 16:14
fonte
0

Non rientrano nella categoria di cose a cui la gente di solito pensa quando si tratta di overloading dell'operatore, ma penso che uno degli operatori più importanti da sovraccaricare sia operatore di conversione .

Gli operatori di conversione sono particolarmente utili per i tipi di valore che possono "decolorare" in un tipo numerico o possono agire come un tipo numerico in alcuni contesti. Ad esempio, potresti definire un tipo speciale Id che rappresenta un determinato identificatore e potresti fornire una conversione implicita a int in modo che tu possa passare un Id a un metodo che richiede un int , ma una conversione explict da int a Id così nessuno può passare un int in un metodo che prende un Id senza prima lanciarlo.

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

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

    
risposta data 23.02.2012 - 20:45
fonte
0

La Point Struct nello spazio dei nomi System.Drawing utilizza l'overloading per confrontare due diverse posizioni utilizzando l'overloading dell'operatore.

 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ù facile confrontare le coordinate X e Y di due posizioni usando il sovraccarico.

    
risposta data 09.03.2012 - 11:22
fonte
0

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

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

v1=[1,3]
v2=[1,3]
v1==v2 // True
    
risposta data 09.03.2012 - 11:55
fonte
-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 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 };
    }
}

da utilizzare solo in seguito

{
    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;
}
    
risposta data 23.02.2012 - 17:35
fonte

Leggi altre domande sui tag