Приклади перевантаження оператора, які мають сенс [закрито]


12

Під час вивчення C # я виявив, що C # підтримує перевантаження оператора. У мене є проблема з хорошим прикладом, який:

  1. Майте сенс (наприклад, додавання класу з назвою овець і корова)
  2. Чи не є прикладом конкатенації двох рядків

Приклади з бібліотеки базових класів вітаються.


10
Визначте, будь ласка, сенс! Серйозно, гіркі та задушливі дебати саме щодо цього питання свідчать про те, що існує велика незгода щодо саме цього. Багато органів влади відмовляються від перевантажених операторів, оскільки їх можна змусити робити зовсім несподівані речі. Інші відповідають, що імена методів також можуть бути вибрані абсолютно неінтуїтивними, але це не є підставою для відхилення названих блоків коду! Ви майже напевно не збираєтесь отримати жодних прикладів, які, як правило, вважають розумними. Приклади , які здаються розумними вам - можуть бути.
Кіліан Фот

Повністю згоден з @KilianFoth. Зрештою, програма, яка компілює, має сенс для компілятора. Але якщо перевантаження ==робити множення, це має сенс для мене, але може не мати сенсу для інших! Це питання щодо легітимності якихось програмних мов програмування чи ми говоримо про "кодування найкращих практик"?
Діпан Мехта

Відповіді:


27

Очевидними прикладами відповідного перевантаження оператора є будь-які класи, які ведуть себе так, як діють числа. Тож класи BigInt (як пропонує Джалайн ), складні числа або матричні класи (як пропонує Superbest ) усі мають ті ж операції, що звичайні числа так добре відображаються на математичних операторах, тоді як операції з часом (як пропонує Svick ) добре відображають на підмножину цих операцій.

Трохи більш абстрактно, операторів можна використовувати при виконанні операцій, встановлених на зразок , так що це operator+може бути об'єднання , operator-може бути доповненням і т. Д. Це дійсно починає розтягувати парадигму, особливо якщо ви використовуєте оператор додавання або множення для операції, яка не є ' t комутативні , як ви могли їх очікувати.

Сам C # є прекрасним прикладом нечислового перевантаження оператора. Він використовує +=та -=додає та віднімає делегатів , тобто реєструє та скасовує їх. Це добре працює , тому що +=і -=оператори працюють , як можна було б очікувати їх до, і цей результат в набагато більш лаконічному коді.

Для пуристів одна з проблем +оператора рядків полягає в тому, що він не є комутативним. "a"+"b"не те саме, що "b"+"a". Ми розуміємо цей виняток для рядків, оскільки він настільки поширений, але як ми можемо визначити, чи використання operator+інших типів буде комутативним чи ні? Більшість людей вважають, що це так, якщо об'єкт не є стрункоподібним , але ви ніколи не знаєте, що припускатимуть люди.

Як і у випадку з рядками, досить добре відомі також і байки матриць. Очевидно, що Matrix operator* (double, Matrix)це скалярне множення, тоді як, наприклад, Matrix operator* (Matrix, Matrix)було б матричне множення (тобто матриця множення крапкових продуктів).

Аналогічно використання операторів з делегатами настільки далеко від математики, що ви навряд чи помилитесь.

До речі, на конференції ACCU 2011 року Роджер Орр та Стів Лав представили сесію на тему: Деякі об'єкти є більш рівними, ніж інші - погляд на багато значень рівності, цінності та ідентичності . Їх слайди можна завантажити , як і Додаток Річарда Гарріса про рівність з плаваючою комою . Резюме: Будьте дуже обережні з operator==, тут дракони!

Перевантаження оператора - це дуже потужна семантична техніка, але її легко використовувати. В ідеалі ви повинні використовувати його лише в тих ситуаціях, коли з контексту зрозуміло, який вплив перевантажений оператор. Багато в чому a.union(b)це ясніше , ніж a+bта a*bє набагато більш неясним , ніж a.cartesianProduct(b), тим більше , що результат декартова твори буде , SetLike<Tuple<T,T>>а не SetLike<T>.

Справжні проблеми з перевантаженням оператора виникають тоді, коли програміст припускає, що клас буде вести себе в один спосіб, але він насправді веде себе по-іншому. Такого роду смислові зіткнення - це те, що я припускаю, що важливо намагатися уникати.


1
Ви кажете, що оператори на матрицях відображають дійсно добре, але множення матриць теж не є комутативним. Також оператори на делегатах ще сильніші. Ви можете зробити d1 + d2для будь-яких двох делегатів одного типу.
svick

1
@Mark: "Точковий продукт" визначається лише у векторах; множення двох матриць називається просто "матричним множенням". Відмінність є більш ніж просто семантичною: крапковий добуток повертає скалярний, а матричне множення повертає матрицю (і, до речі, некомутативний) .
BlueRaja - Danny Pflughoeft

26

Я здивований, що жоден із цікавіших випадків у БКЛ ніхто не згадав: DateTimeі TimeSpan. Ти можеш:

  • додайте або відніміть два TimeSpans, щоб отримати іншийTimeSpan
  • використовуйте одинарний мінус на а, TimeSpanщоб отримати запереченняTimeSpan
  • відняти два DateTimes, щоб отримати aTimeSpan
  • додати або відняти TimeSpanвід а, DateTimeщоб отримати іншеDateTime

Інший набір операторів , які могли б мати сенс на багатьох типів <, >, <=, >=. У БКЛ, наприклад, Versionреалізує їх.


Дуже реальний приклад, а не педантичні теорії!
SIslam

7

Перший приклад, який мені спадає на думку, - це реалізація BigInteger , яка дозволяє працювати з великими цілими підписами. Перевірте посилання MSDN, щоб побачити, скільки операторів було перевантажено (тобто є великий список, і я не перевіряв, чи перевантажені всі оператори, але це, звичайно, здається)

Крім того, оскільки я також роблю Java і Java не дозволяє перевантажувати операторів, писати неймовірно солодше

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

Тоді, як на Java:

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

5

Я радий, що це я бачив, тому що я обдурив Іронію, і вона ВЕЛИКО використовує перевантаження операторів. Ось зразок того, що він може зробити.

Отже, Irony - це "набір для реалізації мови .NET" і є генератором аналізаторів (генеруючий аналізатор LALR). Замість того, щоб вивчати новий синтаксис / мову, як генератори парсера, такі як yacc / lex, ви пишете граматику в C # з перевантаженням оператора. Ось проста граматика 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

Отже, це проста маленька граматика (вибачте, будь ласка, якщо є невідповідності, оскільки я лише вивчаю BNF і будую граматики). Тепер давайте подивимось на 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("+") | "-" | "*" | "/" | "**";

Як бачите, з перевантаженням оператора, граматика в C # майже точно пише граматику в BNF. Для мене це не тільки має сенс, але і чудово використовує перевантаження оператора.


3

Ключовий приклад - оператор == / оператор! =.

Якщо ви хочете легко порівняти два об'єкти із значеннями даних замість посилання, ви хочете перевантажити .Equals (і.GetHashCode!), А також, можливо, хочете зробити так само оператори! = І == для послідовності.

Я ніколи не бачив жодних диких перевантажень інших операторів у C #, хоча (я думаю, є випадки, коли це може бути корисним).


1

Цей приклад з MSDN показує, як реалізувати складні числа та змусити їх використовувати оператор normal +.

Інший приклад показує, як це зробити для доповнення матриці, а також пояснює, як не використовувати його для додавання автомобіля до гаража (читайте посилання).


0

Гарне використання перевантажень може бути рідко, але це трапляється.

оператор перевантаження == і оператор! = показують дві школи думки: ті, хто говорить, що полегшує справи, а ті, хто говорить, не дозволяють порівнювати адреси (тобто я вказую саме на те саме місце в пам'яті, а не лише на копію одного і того ж об’єкт).

Я вважаю, що перевантаження передавача операторів зручно використовувати в конкретних ситуаціях. Наприклад, мені довелося серіалізувати / десериалізувати в XML булеве значення, представлене як 0 або 1. Право (неявне або явне, я забуваю) оператор кидання з boolean в int і назад зробив трюк.


4
Це не заважає порівнювати адреси: Ви все одно можете використовувати object.ReferenceEquals().
dan04

@ dan04 Дуже добре знати!
MPelletier

Інший спосіб порівняння адрес - змусити використовувати об'єкт ==шляхом кастингу: (object)foo == (object)barзавжди порівнює посилання. Але я вважаю за краще ReferenceEquals(), як згадує @ dan04, оскільки зрозуміліше, що це робить.
svick

0

Вони не належать до тієї категорії речей, про яку люди зазвичай думають, коли вони перевантажуються оператором, але я думаю, що одним з найважливіших операторів, який може перевантажуватись, є оператор перетворення .

Оператори перетворення особливо корисні для типів значень, які можуть "знецінювати" чисельний тип або можуть діяти як числовий тип у деяких контекстах. Наприклад, ви можете визначити спеціальний Idтип, який представляє певний ідентифікатор, і ви можете надати неявну конверсію, щоб intви могли передати Idметод, який займає int, але експліцитне перетворення intдо того, Idщоб ніхто не міг перейти intв метод, який приймає, Idне вводячи його спочатку.

Як приклад поза C #, мова Python включає безліч спеціальних способів поведінки, реалізованих як оператори, що завантажуються. До них відносяться inоператор для тестування членства, ()оператор для виклику об'єкта як би функцією та lenоператор для визначення довжини чи розміру об'єкта.

І тоді у вас є такі мови, як Haskell, Scala та багато інших функціональних мов, де такі імена +є просто звичайними функціями, а не операторами (а є підтримка мови для використання функцій у infix положенні).


0

Структура точки в просторі імен System.Drawing використовує перевантаження для порівняння двох різних місць за допомогою перевантаження оператора.

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

Як бачимо, набагато простіше порівняти координати X і Y двох локацій за допомогою перевантаження.


0

Якщо ви знайомі з математичним вектором, ви можете побачити використання в перевантаженні +оператора. Ви можете додати вектор за a=[1,3]допомогою b=[2,-1]і отримати c=[3,2].

Перевантаження рівних (==) також може бути корисною (хоча, можливо, краще реалізувати equals()метод). Щоб продовжити векторні приклади:

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

-2

Уявіть фрагмент коду для малювання на формі

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

Ще один поширений приклад - коли структура використовується для утримання інформації про положення у вигляді вектора.

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

використовуватись лише пізніше

{
    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
Ви додаєте вектори, а не позиція: \ Це хороший приклад того , коли operator+слід НЕ перевантажувати (ви можете реалізувати точку в термінах вектора, але ви не повинні бути в змозі додати дві точки)
BlueRaja - Денні Pflughoeft

@ BlueRaja-DannyPflughoeft: Додавання позицій для отримання іншої позиції не має сенсу, але віднімання їх (для отримання вектора) робить, як і усереднення їх. Можна було вирахувати середнє значення p1, p2, p3 та p4 через p1+((p2-p1)+(p3-p1)+(p4-p1))/4, але це здається дещо незручним.
supercat

1
У афінній геометрії ви можете робити алгебру з точками і лініями, наприклад додавання, масштабування і т. Д. Для реалізації потрібні однорідні координати, які, як правило, використовуються в 3D-графіці. Додавання двох балів фактично призводить до їх середнього значення.
ja72
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.