Exemples de surcharge d'opérateur, qui ont du sens [fermé]


12

Pendant que j'apprenais le C #, j'ai trouvé que le C # prend en charge la surcharge de l'opérateur. J'ai un problème avec un bon exemple qui:

  1. Faire sens (ex. Ajouter une classe nommée moutons et vaches)
  2. N'est pas un exemple de concaténation de deux chaînes

Les exemples de la bibliothèque de classes de base sont les bienvenus.


10
Définissez le «sens», s'il vous plaît! Sérieusement, les débats amers et passionnés sur ce point précis montrent qu'il y a un énorme désaccord à ce sujet. De nombreuses autorités rejettent les opérateurs surchargés car ils peuvent être amenés à faire des choses totalement inattendues. D'autres répondent que les noms de méthode peuvent également être choisis pour être complètement intuitifs, mais ce n'est pas une raison pour rejeter les blocs de code nommés! Vous n'obtiendrez certainement pas d'exemples généralement considérés comme raisonnables. Les exemples qui semblent judicieux de vous - peut - être.
Kilian Foth

Entièrement d'accord avec @KilianFoth. En fin de compte, le programme qui compile a du sens pour le compilateur. Mais en cas de surcharge ==pour faire de la multiplication, cela a du sens pour moi mais peut ne pas avoir de sens pour les autres! S'agit-il de la légitimité de quels langages de programmation d'installation ou parlons-nous de «meilleures pratiques de codage»?
Dipan Mehta

Réponses:


27

Les exemples évidents de surcharge d'opérateur appropriée sont toutes les classes qui se comportent de la même manière que les nombres fonctionnent. Ainsi, les classes BigInt (comme le suggère Jalayn ), les nombres complexes ou les classes matricielles (comme le suggère Superbest ) ont toutes les mêmes opérations que les nombres ordinaires si bien mappées très bien sur les opérateurs mathématiques, tandis que les opérations temporelles (comme suggéré par svick ) mappent bien sur un sous-ensemble de ces opérations.

Un peu plus abstraitement, les opérateurs pourraient être utilisés lors de l'exécution d' opérations de type ensemble , donc operator+pourraient être une union , operator-un complément, etc. Cela commence à étirer le paradigme, surtout si vous utilisez l'opérateur d'addition ou de multiplication pour une opération qui n'est t commutative , comme on pourrait s'y attendre.

C # lui-même a un excellent exemple de surcharge d'opérateur non numérique . Il utilise +=et -=pour ajouter et soustraire des délégués , c'est-à-dire les enregistrer et les désenregistrer. Cela fonctionne bien car les opérateurs +=et -=fonctionnent comme vous vous en doutez, ce qui donne un code beaucoup plus concis.

Pour le puriste, l'un des problèmes avec l' +opérateur de chaîne est qu'il n'est pas commutatif. "a"+"b"n'est pas le même que "b"+"a". Nous comprenons cette exception pour les chaînes car elle est si courante, mais comment savoir si l'utilisation operator+sur d'autres types sera commutative ou non? La plupart des gens penseront que c'est le cas, à moins que l'objet ne soit semblable à une chaîne , mais vous ne savez jamais vraiment ce que les gens supposeront.

Comme pour les cordes, les faiblesses des matrices sont également assez bien connues. Il est évident que Matrix operator* (double, Matrix)c'est une multiplication scalaire, alors que ce Matrix operator* (Matrix, Matrix)serait une multiplication matricielle (c'est-à-dire une matrice de multiplications de produits scalaires) par exemple.

De même, l'utilisation d'opérateurs avec des délégués est tellement loin des mathématiques qu'il est peu probable que vous fassiez ces erreurs.

Soit dit en passant, lors de la conférence ACCU 2011 , Roger Orr et Steve Love ont présenté une session sur Certains objets sont plus égaux que d'autres - un regard sur les nombreuses significations de l'égalité, de la valeur et de l'identité . Leurs diapositives sont téléchargeables , tout comme l' annexe de Richard Harris sur l'égalité en virgule flottante . Résumé: Soyez très prudent avec operator==, voici des dragons!

La surcharge des opérateurs est une technique sémantique très puissante, mais elle est facile à sur-utiliser. Idéalement, vous ne devriez l'utiliser que dans des situations où il est très clair, d'après le contexte, quel est l'effet d'un opérateur surchargé. À bien des égards, a.union(b)est plus clair que a+b, et a*best beaucoup plus obscur que a.cartesianProduct(b), d'autant plus que le résultat d'un produit cartésien serait un SetLike<Tuple<T,T>>plutôt qu'un SetLike<T>.

Les vrais problèmes de surcharge d'opérateur surviennent lorsqu'un programmeur suppose qu'une classe se comportera d'une manière, mais qu'elle se comportera en fait d'une autre. Ce genre de choc sémantique est ce que je suggère qu'il est important d'éviter.


1
Vous dites que les opérateurs sur les matrices sont très bien mappés, mais la multiplication matricielle n'est pas commutative non plus. Les opérateurs sur les délégués sont également encore plus forts. Vous pouvez le faire d1 + d2pour deux délégués du même type.
svick

1
@Mark: Le "produit scalaire" n'est défini que sur les vecteurs; multiplier deux matrices est appelé simplement «multiplication matricielle». La distinction est plus que sémantique: le produit scalaire renvoie un scalaire, tandis que la multiplication matricielle renvoie une matrice (et, soit dit en passant, non commutative) .
BlueRaja - Danny Pflughoeft

26

Je suis surpris que personne n'ait mentionné l'un des cas les plus intéressants de BCL: DateTimeet TimeSpan. Vous pouvez:

  • ajouter ou soustraire deux TimeSpans pour obtenir un autreTimeSpan
  • utiliser unaire moins sur un TimeSpanpour obtenir une négationTimeSpan
  • soustraire deux DateTimes pour obtenir unTimeSpan
  • ajouter ou soustraire TimeSpand'un DateTimepour obtenir un autreDateTime

Une autre série d'opérateurs qui pourrait avoir un sens sur un grand nombre de types sont <, >, <=, >=. Dans la BCL, par exemple, les Versionimplémente.


Exemple très réel plutôt que théories pédantes!
SIslam

7

Le premier exemple qui me vient à l'esprit est l'implémentation de BigInteger , qui vous permet de travailler avec de grands entiers signés. Consultez le lien MSDN pour voir combien d'opérateurs ont été surchargés (c'est-à-dire qu'il y a une grande liste, et je n'ai pas vérifié si tous les opérateurs ont été surchargés, mais cela semble certainement le cas)

De plus, comme je fais aussi Java et Java ne permet pas de surcharger les opérateurs, il est incroyablement plus doux d'écrire

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

Que, en Java:

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

5

Je suis content d'avoir vu cela parce que je plaisante avec Irony et qu'il a une GRANDE utilisation de la surcharge de l'opérateur. Voici un exemple de ce qu'il peut faire.

Irony est donc un "Kit de mise en œuvre du langage .NET" et est un générateur d'analyseur (générant un analyseur LALR). Au lieu d'avoir à apprendre une nouvelle syntaxe / langue comme les générateurs d'analyseurs tels que yacc / lex, vous écrivez la grammaire en C # avec la surcharge de l'opérateur. Voici une simple grammaire 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

C'est donc une simple petite grammaire (veuillez l'excuser s'il y a des incohérences car j'apprends juste le BNF et je construis des grammaires). Voyons maintenant le 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("+") | "-" | "*" | "/" | "**";

Comme vous pouvez le voir, avec la surcharge de l'opérateur, l'écriture de la grammaire en C # équivaut presque exactement à la grammaire en BNF. Pour moi, non seulement cela a du sens, mais c'est une excellente utilisation de la surcharge de l'opérateur.


3

L'exemple clé est operator == / operator! =.

Si vous souhaitez comparer facilement deux objets avec des valeurs de données au lieu de par référence, vous voudrez surcharger .Equals (et.GetHashCode!), Et vous voudrez peut-être également utiliser les opérateurs! = Et == pour des raisons de cohérence.

Je n'ai cependant jamais vu de surcharges sauvages d'autres opérateurs en C # (j'imagine qu'il y a des cas extrêmes où cela pourrait être utile).


1

Cet exemple de MSDN montre comment implémenter des nombres complexes et les faire utiliser l'opérateur normal +.

Un autre exemple montre comment le faire pour l'ajout de matrice, et explique également comment ne pas l'utiliser pour ajouter une voiture à un garage (lire le lien).


0

Une bonne utilisation de la surcharge peut être rare, mais cela arrive.

surcharge opérateur == et opérateur! = montrent deux écoles de pensée: celles qui disent que cela facilite les choses, et celles qui ne disent pas cela empêchent de comparer les adresses (c'est-à-dire que je pointe exactement vers le même endroit en mémoire, pas seulement une copie de la même chose) objet).

Je trouve que les surcharges d'opérateurs de cast sont pratiques dans des situations spécifiques. Par exemple, j'ai dû sérialiser / désérialiser en XML un booléen représenté par 0 ou 1. L'opérateur de cast droit (implicite ou explicite, j'oublie) de booléen en int et en arrière a fait l'affaire.


4
Cela n'empêche pas de comparer les adresses: vous pouvez toujours utiliser object.ReferenceEquals().
dan04

@ dan04 Très très bon à savoir!
MPelletier

Une autre façon de comparer les adresses est de forcer l'utilisation d'objets ==par le cast: (object)foo == (object)barcompare toujours les références. Mais je préférerais ReferenceEquals(), comme @ dan04 le mentionne parce que c'est plus clair ce qu'il fait.
svick

0

Ils ne sont pas dans la catégorie des choses auxquelles les gens pensent généralement lorsqu'ils pensent à la surcharge des opérateurs, mais je pense que l'un des opérateurs les plus importants pour pouvoir surcharger est l' opérateur de conversion .

Les opérateurs de conversion sont particulièrement utiles pour les types de valeur qui peuvent "supprimer le sucre" en un type numérique, ou peuvent agir comme un type numérique dans certains contextes. Par exemple, vous pouvez définir un Idtype spécial qui représente un certain identificateur, et vous pouvez fournir une conversion implicite à intafin que vous puissiez passer un Idà une méthode qui prend un int, mais une conversion explicite de intà Idafin que personne ne puisse passer un intà un méthode qui prend un Idsans le lancer au préalable.

À titre d'exemple en dehors de C #, le langage Python inclut de nombreux comportements spéciaux qui sont implémentés en tant qu'opérateurs surchargeables. Ceux-ci incluent l' inopérateur pour tester l'appartenance, l' ()opérateur pour appeler un objet comme s'il s'agissait d'une fonction et l' lenopérateur pour déterminer la longueur ou la taille d'un objet.

Et puis vous avez des langages comme Haskell, Scala, et de nombreux autres langages fonctionnels, où des noms comme ne +sont que des fonctions ordinaires, et pas du tout des opérateurs (et il y a un support de langage pour utiliser des fonctions en position d'infixe).


0

La structure ponctuelle dans l' espace de noms System.Drawing utilise la surcharge pour comparer deux emplacements différents à l'aide de la surcharge de l'opérateur.

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

Comme vous pouvez le voir, il est beaucoup plus facile de comparer les coordonnées X et Y de deux emplacements en utilisant la surcharge.


0

Si vous connaissez le vecteur mathématique, vous pourriez voir une utilité pour surcharger l' +opérateur. Vous pouvez ajouter un vecteur a=[1,3]avec b=[2,-1]et obtenir c=[3,2].

La surcharge des égaux (==) peut également être utile (même s'il est probablement préférable d'implémenter une equals()méthode). Pour continuer les exemples vectoriels:

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

-2

Imaginez un morceau de code pour dessiner sur un formulaire

{
  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 autre exemple courant est lorsqu'une structure est utilisée pour contenir des informations de position sous la forme d'un vecteur.

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

à utiliser plus tard comme

{
    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
Vous ajoutez des vecteurs, pas des positions: \ Ceci est un bon exemple de quand neoperator+ doit pas être surchargé (vous pouvez implémenter un point en termes de vecteur, mais vous ne devriez pas pouvoir ajouter deux points)
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft: Ajouter des positions pour produire une autre position n'a pas de sens, mais les soustraire (pour produire un vecteur) le fait, tout comme la moyenne . On pourrait calculer la moyenne de p1, p2, p3 et p4 via p1+((p2-p1)+(p3-p1)+(p4-p1))/4, mais cela semble un peu maladroite.
supercat

1
En géométrie affine, vous pouvez faire de l'algèbre avec des points et des lignes, comme l'addition, la mise à l'échelle, etc. L'implémentation nécessite cependant des coordonnées homogènes, qui sont généralement utilisées dans les graphiques 3D de toute façon. L'ajout de deux points se traduit en fait par leur moyenne.
ja72
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.