В программировании перегрузка операторов , иногда называемая полиморфизмом операторов ad hoc , является частным случаем полиморфизма , где разные операторы имеют разные реализации в зависимости от их аргументов. Перегрузка операторов обычно определяется языком программирования , программистом или обоими.
Перегрузка операторов — это синтаксический сахар , и используется, поскольку позволяет программировать с использованием нотации, более близкой к целевой области [1], и предоставляет определяемым пользователем типам аналогичный уровень синтаксической поддержки, как и встроенным в язык типам. Это распространено, например, в научных вычислениях, где позволяет манипулировать вычислительными представлениями математических объектов с тем же синтаксисом, что и на бумаге.
Перегрузка операторов не изменяет выразительную силу языка (с функциями), поскольку ее можно эмулировать с помощью вызовов функций. Например, рассмотрим переменные a
, b
и c
некоторого определенного пользователем типа, например матрицы :
a + b * c
В языке, поддерживающем перегрузку операторов, и с обычным предположением, что оператор «*» имеет более высокий приоритет , чем оператор «+», это будет кратким способом записи:
Add(a, Multiply(b, c))
Однако предыдущий синтаксис отражает общепринятое математическое использование.
В этом случае оператор сложения перегружается, чтобы разрешить сложение с пользовательским типом Time
в C++ :
Оператор времени + ( const Time & lhs , const Time & rhs ) { Время temp = lhs ; temp . seconds += rhs . seconds ; temp . minutes += temp . seconds / 60 ; temp . seconds %= 60 ; temp . minutes += rhs . minutes ; temp . hours += temp . minutes / 60 ; temp . minutes %= 60 ; temp . hours += rhs . hours ; return temp ; }
Сложение — это бинарная операция , то есть у нее два операнда . В C++ передаваемые аргументы — это операнды, а temp
объект — это возвращаемое значение.
Операцию также можно определить как метод класса, заменив lhs
его скрытым this
аргументом; Однако это заставит левый операнд иметь тип Time
:
// "const" прямо перед открывающейся фигурной скобкой означает, что |this| не изменяется. Time Time :: Operator + ( const Time & rhs ) const { Time temp = * this ; // |this| не должен изменяться, поэтому создаем копию. temp . seconds += rhs . seconds ; temp . minutes += temp . seconds / 60 ; temp . seconds %= 60 ; temp . minutes += rhs . minutes ; temp . hours += temp . minutes / 60 ; temp . minutes %= 60 ; temp . hours += rhs . hours ; return temp ; }
Обратите внимание, что унарный оператор, определенный как метод класса, не будет получать видимых аргументов (он работает только из this
):
bool Время :: оператор ! () const { return часы == 0 && минуты == 0 && секунды == 0 ; }
Оператор «меньше» (<) часто перегружается для сортировки структуры или класса:
класс Пара { public : bool оператор < ( const Пара & p ) const { if ( x_ == p . x_ ) { return y_ < p . y_ ; } return x_ < p . x_ ; } частный : int x_ ; int y_ ; };
Как и в предыдущих примерах, в последнем примере перегрузка операторов выполняется внутри класса. В C++ после перегрузки оператора «меньше» (<) для сортировки некоторых классов можно использовать стандартные функции сортировки .
Перегрузка операторов часто подвергалась критике [2] , поскольку она позволяет программистам переназначать семантику операторов в зависимости от типов их операндов. Например, использование оператора <<
в C++ сдвигает биты в переменной слева на биты, если и имеют целочисленный тип, но если является выходным потоком, то приведенный выше код попытается записать в поток. Поскольку перегрузка операторов позволяет исходному программисту изменять обычную семантику оператора и заставать врасплох последующих программистов, считается хорошей практикой использовать перегрузку операторов с осторожностью (создатели Java решили не использовать эту возможность, [3] хотя и не обязательно по этой причине).a << b
a
b
a
b
a
b
Другая, более тонкая проблема с операторами заключается в том, что некоторые правила из математики могут быть ошибочно ожидаемы или непреднамеренно приняты. Например, коммутативность + (т. е. что a + b == b + a
) не всегда применима; пример этого возникает, когда операнды являются строками, так как + обычно перегружается для выполнения конкатенации строк (т. е. "bird" + "song"
дает "birdsong"
, в то время "song" + "bird"
как дает "songbird"
). Типичный контраргумент [ требуется цитата ] к этому аргументу исходит непосредственно из математики: в то время как + является коммутативным для целых чисел (и, в более общем смысле, для любых комплексных чисел), он не является коммутативным для других «типов» переменных. На практике + даже не всегда ассоциативен , например, для значений с плавающей точкой из-за ошибок округления. Другой пример: в математике умножение является коммутативным для действительных и комплексных чисел, но не коммутативным при умножении матриц .
Проведена классификация некоторых распространенных языков программирования в зависимости от того, могут ли их операторы перегружаться программистом и ограничены ли операторы предопределенным набором.
Спецификация ALGOL 68 допускала перегрузку операторов. [44]
Выдержка из спецификации языка АЛГОЛ 68 (стр. 177), где определены перегруженные операторы ¬, =, ≠ и abs :
10.2.2 Операции над булевыми операндамиа) оп ∨ = ( логическое значение a, b) логическое значение :( a | истина | b );б) оп ∧ = ( bool a, b) bool : ( a | b | false );в) оп ¬ = ( bool a) bool : ( a | false | true );г) оп = = ( булев а, б) булев :( а∧б ) ∨ ( ¬б∧¬а );д) оп ≠ = ( логическое a, b) логическое : ¬(a=b);е) оп abs = ( bool a) int : ( a | 1 | 0 );
Обратите внимание, что для перегрузки оператора не требуется специального объявления , и программист может свободно создавать новые операторы. Для диадических операторов можно задать их приоритет по сравнению с другими операторами:
прио макс = 9; op max = ( int a, b) int : ( a>b | a | b ); op ++ = ( ref int a ) int : ( a +:= 1 );
Ada поддерживает перегрузку операторов с самого начала, с публикацией стандарта языка Ada 83. Однако разработчики языка решили исключить определение новых операторов. Только существующие операторы в языке могут быть перегружены путем определения новых функций с идентификаторами, такими как "+", "*", "&" и т. д. Последующие версии языка (в 1995 и 2005 годах) сохраняют ограничение на перегрузку существующих операторов.
В C++ перегрузка операторов более совершенна, чем в ALGOL 68. [ 45]
Разработчики языка Java в Sun Microsystems решили отказаться от перегрузки. [46] [47] [48]
Python допускает перегрузку операторов посредством реализации методов со специальными именами. [49] Например, оператор сложения (+) можно перегрузить, реализовав метод obj.__add__(self, other)
.
Ruby допускает перегрузку операторов в качестве синтаксического сахара для простых вызовов методов.
Lua допускает перегрузку операторов в качестве синтаксического сахара для вызовов методов с дополнительной функцией: если первый операнд не определяет этот оператор, будет использоваться метод для второго операнда.
Microsoft добавила перегрузку операторов в C# в 2001 году и в Visual Basic .NET в 2003 году.
Scala рассматривает все операторы как методы и, таким образом, допускает перегрузку операторов через прокси.
В Raku определение всех операторов делегировано лексическим функциям, и поэтому, используя определения функций, операторы могут быть перегружены или добавлены новые операторы. Например, функция, определенная в исходном коде Rakudo для увеличения объекта Date с помощью "+", выглядит так:
мульти инфикс:<+> ( Дата:D $d , Целое:D $x ) { Дата . новый-от-дня ( $d . день + $x )}
Поскольку было использовано "multi", функция добавляется в список кандидатов multidispatch , а "+" перегружается только в случае, когда выполняются ограничения типа в сигнатуре функции. Хотя возможности перегрузки включают + , * , >= , постфикс и термин i и т. д., она также позволяет перегружать различные операторы фигурных скобок: " [ x, y ] ", "x [ y ] ", "x { y } " и "x ( y ) ".
Kotlin поддерживает перегрузку операторов с момента своего создания.
{{cite web}}
: Проверить |url=
значение ( помощь )Одной из самых приятных особенностей ООП на языке C++ является возможность перегружать операторы для обработки объектов ваших классов (в некоторых других языках, ориентированных на ООП, например Java, этого сделать нельзя).