Из урока №102 мы уже знаем, что перегрузка функций обеспечивает механизм создания и выполнения вызовов функций с одним и тем же именем, но с разными параметрами. Это позволяет одной функции работать с несколькими разными типами данных (без необходимости придумывать уникальные имена для каждой из функций).
В языке C++ операторы реализованы в виде функций. Используя перегрузку функции оператора, вы можете определить свои собственные версии операторов, которые будут работать с разными типами данных (включая классы). Использование перегрузки функции для перегрузки оператора называется перегрузкой оператора.
Операторы, как функции
Рассмотрим следующий фрагмент:
1 2 3 |
int a = 5; int b = 6; std::cout << a + b << '\n'; |
Здесь компилятор использует встроенную версию оператора плюс (+
) для целочисленных операндов — эта функция сложит два целочисленных значения (a
и b
), и возвратит целочисленный результат. Когда вы видите выражение a + b
, то думайте о нем, как о вызове функции operator+(a, b)
(где operator+
является именем функции).
Теперь рассмотрим следующий фрагмент:
1 2 3 |
double m = 4.0; double p = 5.0; std::cout << m + p << '\n'; |
Компилятор также предоставит встроенную версию оператора плюс (+
) для операндов типа double. Выражение m + p
приведет к вызову функции operator+(m, p)
, а, благодаря перегрузке оператора, вызовется версия double (вместо версии int).
Теперь рассмотрим, что произойдет, если мы попытаемся добавить два объекта класса:
1 2 3 |
Mystring hello = "Hello, "; Mystring world = "World!"; std::cout << hello + world << '\n'; |
Как вы думаете, какой будет результат? Наверное, вывод строки Hello, World!
? Нет, результатом будет ошибка, так как класс Mystring является пользовательским типом данных, а компилятор не имеет встроенной версии operator+() для использования с операндами Mystring. Для того, чтобы сделать то, что мы хотим, нам придется написать свою версию функции operator+() и указать в ней алгоритм работы с операндами типа Mystring. То, как это сделать в коде, мы рассмотрим на следующем уроке.
Вызов перегруженных операторов
При обработке выражения, содержащего оператор, компилятор использует следующие алгоритмы действий:
Если все операнды являются фундаментальных типов данных, то вызывать следует встроенные соответствующие версии операторов (если таковые существуют). Если таковых не существует, то компилятор выдаст ошибку.
Если какой-либо из операндов является пользовательского типа данных (например, объект класса или перечисление), то компилятор будет искать версию оператора, которая работает с таким типом данных. Если компилятор не найдет ничего подходящего, то попытается выполнить конвертацию одного или нескольких операндов пользовательского типа данных в фундаментальные типы данных, чтобы таким образом он мог использовать соответствующий встроенный оператор. Если это не сработает — компилятор выдаст ошибку.
Ограничения в перегрузке операторов
Во-первых, почти любой существующий оператор в языке C++ может быть перегружен. Исключениями являются:
тернарный оператор (?:
);
оператор разрешения области видимости (::
);
операторы выбора члена .
и .*
.
Во-вторых, вы можете перегрузить только существующие операторы. Вы не можете создавать новые или переименовывать существующие. Например, вы не можете создать оператор **
для выполнения операции возведения в степень.
В-третьих, по крайней мере один из операндов перегруженного оператора должен быть пользовательского типа данных. Это означает, что вы не можете перегрузить operator+() для выполнения операции сложения значения типа int со значением типа double. Однако вы можете перегрузить operator+() для выполнения операции сложения значения типа int с объектом класса Mystring.
В-четвертых, изначальное количество операндов, поддерживаемых оператором, изменить невозможно. Т.е. с бинарным оператором используются только два операнда, с унарным — только один, с тернарным — только три.
Наконец, все операторы сохраняют свой приоритет и ассоциативность по умолчанию (независимо от того, для чего они используются), и это не может быть изменено.
Некоторые начинающие программисты пытаются перегрузить побитовый оператор XOR (^
) для выполнения операции возведения в степень. Однако в языке C++ у оператора ^
приоритет ниже, чем у базовых арифметических операторов (+
, -
, *
, /
), и это приведет к некорректной обработке выражений.
В математике операция возведения в степень выполняется до выполнения базовых арифметических операций, поэтому 2 + 5 ^ 2
обрабатывается как 2 + (5 ^ 2) => 2 + 25 => 27
. Однако в языке C++ у базовых арифметических операторов приоритет выше, нежели у оператора ^
, поэтому 2 + 5 ^ 2
выполнится как (2 + 5) ^ 2 => 7 ^ 2 => 49
.
Вам нужно будет явно заключать в скобки часть с возведением в степень (например, 2 + (5 ^ 2)
) каждый раз, когда вы хотите, чтобы она выполнялась первой, что очень легко забыть и, таким образом, наделать ошибок. Поэтому проводить подобные эксперименты не рекомендуется.
Примечание: В языке C++ для возведения в степень используется функция pow() из заголовочного файла cmath. В примере, приведенном выше, с выполнением выражения 2 + 5 ^ 2
в языке C++, имеется в виду, что вы перегрузите побитовый оператор XOR (^
) для выполнения операции возведения в степень.
Правило: При перегрузке операторов старайтесь максимально приближенно сохранять функционал операторов в соответствии с их первоначальными применениями.
Для чего использовать перегрузку операторов? Вы можете перегрузить оператор +
для соединения объектов вашего класса String или для выполнения операции сложения двух объектов вашего класса Fraction. Вы можете перегрузить оператор <<
для вывода вашего класса на экран (или записи в файл). Вы можете перегрузить оператор равенства (==
) для сравнения двух объектов класса и т.д. Подобные применения делают перегрузку операторов одной из самых полезных особенностей языка C++, так как это упрощает процесс работы с классами и открывает новые возможности.
Объясните,как в примере возможно перегрузить оператор ^,если для перегрузки операторов один из параметров должен иметь пользовательский тип,а тут получается,что параметры будут иметь тип int?
Я так понимаю:
Если оба операнда будут целочисленного типа тогда и перегрузки никакой не будет, т.к. компилятор просто использует встроенные версии операторов (то есть XOR). А в примере выше (2 + 5 ^ 2) просто наведен пример, пятерка может быть пользовательского типа, только преобразованная в подходящий int
Очень рад что подписался)