зачем нужна перегрузка операторов c
Перегрузка операторов
operator Ключевое слово объявляет функцию, указывающую, какой оператор-Symbol означает при применении к экземплярам класса. Это дает оператору более одного значения — «перегружает» его. Компилятор различает разные значения оператора, проверяя типы его операндов.
Синтаксис
тип operator operator-символ ( parameter-list )
Remarks
Функцию большинства встроенных операторов можно переопределить глобально или для отдельных классов. Перегруженные операторы реализуются в виде функции.
Имя перегруженного оператора — operator x, где x — это оператор, как показано в следующей таблице. Например, для перегрузки оператора сложения необходимо определить функцию с именем operator +. Аналогично, чтобы перегрузить оператор сложения и присваивания, += Определите функцию с именем operator + =.
Переопределяемые операторы
| Оператор | Имя | Тип |
|---|---|---|
| , | Запятая | Двоичные данные |
| ! | Логическое НЕ | Унарный |
| != | Неравенство | Двоичные данные |
| % | Modulus | Двоичные данные |
| %= | Назначение модуля | Двоичные данные |
| & | Побитовое И | Двоичные данные |
| & | Взятие адреса | Унарный |
| && | Логическое И | Двоичные данные |
| &= | Назначение побитового И | Двоичные данные |
| ( ) | Вызов функции | — |
| ( ) | Оператор приведения | Унарный |
| * | Умножение | Двоичные данные |
| * | Разыменование указателя | Унарный |
| *= | Присваивание умножения | Двоичные данные |
| + | Сложение | Двоичные данные |
| + | Унарный плюс | Унарный |
| ++ | Шаг 1 | Унарный |
| += | Присваивание сложения | Двоичные данные |
| — | Вычитание | Двоичные данные |
| — | Унарное отрицание | Унарный |
| — | Уменьшить 1 | Унарный |
| -= | Присваивание вычитания | Двоичные данные |
| -> | Выбор члена | Двоичные данные |
| — >* | Выбор указателя на член | Двоичные данные |
| / | Отдел | Двоичные данные |
| /= | Присваивание деления | Двоичные данные |
| Больше | Двоичные данные | |
| >= | Больше или равно | Двоичные данные |
| >> | Сдвиг вправо | Двоичные данные |
| >>= | Сдвиг вправо и присваивание | Двоичные данные |
| [ ] | Индекс массива | — |
| ^ | Исключающее ИЛИ | Двоичные данные |
| ^= | Исключающее ИЛИ/присваивание | Двоичные данные |
| | | Побитовое ИЛИ | Двоичные данные |
| |= | Назначение побитового включающего ИЛИ | Двоичные данные |
| || | Логическое ИЛИ | Двоичные данные |
| Дополнение до единицы | Унарный | |
| delete | Удаление | — |
| new | Создать | — |
| операторы преобразования | операторы преобразования | Унарный |
Существует 1 две версии унарных операторов инкремента и декремента: добавочное и инкрементное.
Урок №130. Введение в перегрузку операторов
Обновл. 13 Сен 2021 |
Из урока №102 мы уже знаем, что перегрузка функций обеспечивает механизм создания и выполнения вызовов функций с одним и тем же именем, но с разными параметрами. Это позволяет одной функции работать с несколькими разными типами данных (без необходимости придумывать уникальные имена для каждой из функций).
В языке C++ операторы реализованы в виде функций. Используя перегрузку функции оператора, вы можете определить свои собственные версии операторов, которые будут работать с разными типами данных (включая классы). Использование перегрузки функции для перегрузки оператора называется перегрузкой оператора.
Операторы, как функции
Рассмотрим следующий фрагмент:
Теперь рассмотрим следующий фрагмент:
Теперь рассмотрим, что произойдет, если мы попытаемся добавить два объекта класса:
Вызов перегруженных операторов
При обработке выражения, содержащего оператор, компилятор использует следующие алгоритмы действий:
Если все операнды являются фундаментальных типов данных, то вызывать следует встроенные соответствующие версии операторов (если таковые существуют). Если таковых не существует, то компилятор выдаст ошибку.
Если какой-либо из операндов является пользовательского типа данных (например, объект класса или перечисление), то компилятор будет искать версию оператора, которая работает с таким типом данных. Если компилятор не найдет ничего подходящего, то попытается выполнить конвертацию одного или нескольких операндов пользовательского типа данных в фундаментальные типы данных, чтобы таким образом он мог использовать соответствующий встроенный оператор. Если это не сработает — компилятор выдаст ошибку.
Ограничения в перегрузке операторов
Во-первых, почти любой существующий оператор в языке C++ может быть перегружен. Исключениями являются:
оператор разрешения области видимости ( :: );
Во-вторых, вы можете перегрузить только существующие операторы. Вы не можете создавать новые или переименовывать существующие. Например, вы не можете создать оператор ** для выполнения операции возведения в степень.
В-третьих, по крайней мере один из операндов перегруженного оператора должен быть пользовательского типа данных. Это означает, что вы не можете перегрузить operator+() для выполнения операции сложения значения типа int со значением типа double. Однако вы можете перегрузить operator+() для выполнения операции сложения значения типа int с объектом класса Mystring.
В-четвертых, изначальное количество операндов, поддерживаемых оператором, изменить невозможно. Т.е. с бинарным оператором используются только два операнда, с унарным — только один, с тернарным — только три.
Наконец, все операторы сохраняют свой приоритет и ассоциативность по умолчанию (независимо от того, для чего они используются), и это не может быть изменено.
Вам нужно будет явно заключать в скобки часть с возведением в степень (например, 2 + (5 ^ 2) ) каждый раз, когда вы хотите, чтобы она выполнялась первой, что очень легко забыть и, таким образом, наделать ошибок. Поэтому проводить подобные эксперименты не рекомендуется.
Примечание: В языке C++ для возведения в степень используется функция pow() из заголовочного файла cmath. В примере, приведенном выше, с выполнением выражения 2 + 5 ^ 2 в языке C++, имеется в виду, что вы перегрузите побитовый оператор XOR ( ^ ) для выполнения операции возведения в степень.
Правило: При перегрузке операторов старайтесь максимально приближенно сохранять функционал операторов в соответствии с их первоначальными применениями.
Для чего использовать перегрузку операторов? Вы можете перегрузить оператор + для соединения объектов вашего класса String или для выполнения операции сложения двух объектов вашего класса Fraction. Вы можете перегрузить оператор для вывода вашего класса на экран (или записи в файл). Вы можете перегрузить оператор равенства ( == ) для сравнения двух объектов класса и т.д. Подобные применения делают перегрузку операторов одной из самых полезных особенностей языка C++, так как это упрощает процесс работы с классами и открывает новые возможности.
Урок №139. Перегрузка оператора ()
Обновл. 25 Сен 2021 |
На этом уроке мы рассмотрим перегрузку оператора () в языке С++.
Перегрузка оператора ()
Но следует помнить о двух вещах:
Во-первых, перегрузка круглых скобок должна осуществляться через метод класса.
Во-вторых, в не объектно-ориентированном C++ оператор () является оператором вызова функции. В случае с классами перегрузка круглых скобок выполняется в методе operator()()<> (в объявлении функции перегрузки находятся две пары круглых скобок).
Матрицы являются ключевой концепцией в линейной алгебре и часто используются в геометрическом моделировании и в 3D-графике. Всё, что вам нужно знать сейчас — это то, что класс Matrix является двумерным массивом (5×5 типа double).
На уроке о перегрузке оператора индексации мы использовали оператор [] для прямого доступа к элементам закрытого одномерного массива. Здесь же нам нужен доступ к элементам двумерного массива. Поскольку оператор [] ограничен лишь одним параметром, то его функциональности недостаточно для доступа к двумерному массиву.
Однако, поскольку оператор () может принимать разное количество параметров, мы можем объявить версию operator(), которая будет принимать два целочисленных параметра (два индекса), и использовать эти индексы для доступа к элементам нашего двумерного массива. Например:
Результат выполнения программы:
Выполним перегрузку оператора () еще раз, но уже без использования каких-либо параметров:
Результат выполнения программы:
Функторы в C++
Перегрузка оператора () используется в реализации функторов (или «функциональных объектов») — классы, которые работают как функции. Преимущество функтора над обычной функцией заключается в том, что функторы могут хранить данные в переменных-членах (поскольку они сами являются классами). Вот пример использования простого функтора:
Обратите внимание, использование класса Accumulator выглядит так же, как и вызов обычной функции, но наш объект класса Accumulator может хранить значение, которое увеличивается.
Вы можете спросить: «Зачем использовать класс, если всё можно реализовать и через обычную функцию со статической локальной переменной?». Можно сделать и через static, но, поскольку функции представлены только одним глобальным экземпляром (т.е. нельзя создать несколько объектов функции), использовать эту функцию мы можем только для выполнения чего-то одного за раз. С помощью функторов мы можем создать любое количество отдельных функциональных объектов, которые нам нужны, и использовать их одновременно.
Заключение
Перегрузка оператора () также часто используется при создании функторов. Хотя функторы, которые мы использовали выше, являются довольно простыми и понятными, но обычно они используются в более продвинутых/сложных темах программирования и заслуживают отдельного урока.
Напишите класс, переменной-членом которого является строка. Перегрузите оператор () для возврата подстроки, которая начинается с индекса, указанного в значении первого параметра. Второй параметр должен указывать требуемую длину подстроки.
Подсказки:
Вы можете использовать индекс массива [] для доступа к отдельным символам строки.
Вы можете использовать оператор += для добавления чего-либо к строке.
Перегрузка всех 49-и операторов в C++
Когда я только начал свой путь по изучению C++, у меня возникало много вопросов, на которые, порой, не удавалось быстро найти ответов. Не стала исключением и такая тема как перегрузка операторов. Теперь, когда я разобрался в этой теме, я хочу помочь другим расставить все точки над i.
В этой публикации я расскажу: о различных тонкостях перегрузки операторов, зачем вообще нужна эта перегрузка, о типах операторов (унарные/бинарные), о перегрузке оператора с friend (дружественная функция), а так же о типах принимаемых и возвращаемых перегрузками значений.
Для чего нужна перегрузка?
Предположим, что вы создаете свой класс или структуру, пусть он будет описывать вектор в 3-х мерном пространстве:
Теперь, Вы создаете 3 объекта этой структуры:
И хотите прировнять объект v2 объекту v1, пишете:
Все работает, но пример с вектором очень сильно упрощен, может быть у вас такая структура, в которой необходимо не слепо копировать все значения из одного объекта в другой (как это происходит по умолчанию), а производить с ними некие манипуляции. К примеру, не копировать последнюю переменную z. Откуда программа об этом узнает? Ей нужны четкие команды, которые она будет выполнять.
Поэтому нам необходимо перегрузить оператор присваивания (=).
Общие сведения о перегрузке операторов
Для этого добавим в нашу структуру перегрузку:
Теперь, в коде выше мы указали, что при присваивании необходимо скопировать переменные x и y, а z обнулить.
Но такая перегрузка далека от совершенства, давайте представим, что наша структура содержит в себе не 3 переменные типа int, а множество объектов других классов, в таком случае этот вариант перегрузки будет работать довольно медленно.
Разумеется, вряд ли кто-то в здравом уме станет производить такие не очевидные манипуляции. Но все же, не помешает исключить даже вероятность такого изменения.
Для этого нам всего-лишь нужно добавить const перед принимаемым аргументом, таким образом мы укажем, что изнутри метода нельзя изменить этот объект.
Но при возврате ссылки, могут появиться определенные проблемы.
Мы уже не напишем такое выражение: v1 = (v2 + v3);
Небольшое отступление о return:
Когда я изучал перегрузки, то не понимал:
Дело в том, что все операции мы должны самостоятельно и явно указать в теле метода. Что значит, написать: this->x = v.x и т.д.
Но для чего тогда return, что мы возвращаем? На самом деле return в этом примере играет достаточно формальную роль, мы вполне можем обойтись и без него:
И такой код вполне себе работает. Т.к. все, что нужно сделать, мы указываем в теле метода.
Но в таком случае у нас не получится сделать такую запись:
Т.к. ничего не возвращается, нельзя выполнить и присваивание.
Либо же в случае со ссылкой, что получается аналогично void, возвращается ссылка на временный объект, который уже не будет существовать в момент его использования (сотрется после выполнения метода).
Получается, что лучше возвращать объект а не ссылку? Не все так однозначно, и выбирать тип возвращаемого значения (объект или ссылка) необходимо в каждом конкретном случае. Но для большинства небольших объектов — лучше возвращать сам объект, чтобы мы имели возможность дальнейшей работы с результатом.
Отступление 2 (как делать не нужно):
Теперь, зная о разнице операции return и непосредственного выполнения операции, мы можем написать такой код:
Для того, что бы реализовать этот ужас мы определим перегрузку таким образом:
Отличия унарных и бинарных операторов
Унарные операторы — это такие операторы, где задействуется только один объект, к которому и применяются все изменения
Бинарные операторы — работают с 2-я объектами
Перегрузка в теле и за телом класса
Мы можем объявить и реализовать перегрузку непосредственно в самом теле класса или структуры. Думаю, что как это сделать уже понятно. Давайте рассмотрим вариант, в котором объявление перегрузки происходит в теле класса, а ее реализация уже за пределами класса.
Зачем в перегрузке операторов дружественные функции (friend)?
Дружественные функции — это такие функции которые имеют доступ к приватным методам класса или структуры.
Предположим, что в нашей структуре Vector3, такие члены как x,y,z — являются приватными, тогда мы не сможем обратиться к ним за пределами тела структуры. Здесь то и помогают дружественные функции.
Единственное изменение, которое нам необходимо внести, — это добавить ключевое слово fried перед объявлением перегрузки:
Когда не обойтись без дружественных функций в перегрузке операторов?
Ошибка произойдет по следующей причине, в структуре Vector2 мы перегрузили оператор +, который в качестве значения справа принимает тип Vector3, поэтому первый вариант работает. Но во втором случае, необходимо писать перегрузку уже для структуры Vector3, а не 2. Чтобы не лезть в реализацию класса Vector3, мы можем написать такую дружественную функцию:
Примеры перегрузок различных операторов с некоторыми пояснениями
Пример перегрузки для постфиксных форм инкремента и декремента (var++, var—)
Перегрузка арифметических операций с объектами других классов
Перегрузка унарного плюса (+)
Перегрузка унарного минуса (-)
Пример перегрузки побитного составного присваивания &=, |=, ^=, >=
Как перегружать new и delete? Примеры:
Перегрузка new и delete отдельная и достаточно большая тема, которую я не стану затрагивать в этой публикации.
BestProg
Данная тема отображает возможности языка C++ по реализации «перегрузки» операторов. Не все современные языки программирования поддерживают перегрузку операторов. Хорошое понимание процесса программирования перегруженных операторов есть показателем профессиональности и высокого мастерства современного программиста.
Перед рассмотрением данной темы рекомендуется ознакомиться со следующей темой:
Содержание
Поиск на других ресурсах:
1. Что такое унарные и бинарные операторы?
Унарные операторы – это операторы, которые для вычислений требуют одного операнда, который может размещаться справа или слева от самого оператора.
Примеры унарных операторов:
Бинарные операторы – это операторы, которые для вычисления требуют двух операндов.
2. В чем состоит суть перегрузки операторов? Что такое операторная функция?
Язык C++ имеет широкие возможности для перегрузки большинства операторов. Перегрузка оператора означает использование оператора для оперирования объектами классов. Перегрузка оператора – способ объявления и реализации оператора таким образом, что он обрабатывает объекты конкретных классов или выполняет некоторые другие действия. При перегрузке оператора в классе вызывается соответствующая операторная функция (operator function), которая выполняет действия, которые касаются данного класса.
Если оператор «перегружен», то его можно использовать в других методах в обычном для него виде. Например, команды поэлементного суммирования двух массивов a1 и a2
лучше вызвать более естественном способом:
В данном примере оператор ‘+’ считается перегруженным.
3. Какими способами можно реализовать операторную функцию для заданного класса? Какие существуют разновидности операторных функций?
Для заданного класса операторную функцию в классе можно реализовать:
4. Общая форма операторной функции, которая реализована в классе. Ключевое слово operator
Общая форма операторной функции, реализованной в классе, имеет следующий вид:
5. Пример перегрузки унарных и бинарных операторов для класса, который содержит одиночные данные. Операторная функция реализована внутри класса
В вышеприведенном коде, в операции суммирования ‘+’ объект P1 вызывает операторную функцию. То есть, фрагмент строки
Реализовать операторную функцию operator+() в классе можно и по другому
В вышеприведенной функции в операторе return создается временный объект путем вызова конструктора с двумя параметрами, реализованного в классе. Если (в данном случае) из тела класса убрать конструктор с двумя параметрами
то вышеприведенный вариант функции operator+() работать не будет, так как для создания объекта типа Point эта функция использует конструктор с двумя параметрами. В этом случае компилятор выдаст сообщение
что значит: нет метода (конструктора) Point::Point() принимающего 2 аргумента.
Использование класса ArrayFloat в другом методе
7. Пример суммирования двух массивов. Операторная функция operator+() размещается внутри класса
Ниже продемонстрировано использование класса ArrayFloat и операторной функции operator+() этого класса.
8. Какие ограничения накладываются на перегруженные операторы?
На использование перегруженных операторов накладываются следующие ограничения:
9. Какие операторы нельзя перегружать?
Нельзя перегружать следующие операторы:
10. Объекты каких типов может возвращать операторная функция? Примеры операторных функций, которые возвращают объекты разных типов
Операторная функция может возвращать объекты любых типов. Наиболее часто операторная функция возвращает объект типа класса, в котором она реализованная или с которыми она работает.
Текст класса следующий:
Далее демонстрируется использование класса Complex и перегруженных операторных функций в некотором другому методе
11. Можно ли изменять значения операндов в операторной функции?
Да, можно. Однако такие действия не являются полезными с точки зрения здравого смысла. Так, например, операция умножения
не изменяет значения своих операндов 6 и 9. Результат равен 54. Если операторная функция operator*() будет изменять значения своих операндов, то это может привести к невидимым ошибкам в программах, поскольку программист по привычке, будет считать, что значения операндов есть неизменными.
12. Можно ли реализовать операторные функции в классе, которые перегружают одинаковый оператор, получают одинаковые параметры но возвращают разные значения?
Нет, нельзя. Операторная функция не может иметь несколько реализаций в классе с одинаковой сигнатурой параметров (когда типы и количество параметров совпадают). В случае нарушения этого правила компилятор выдает ошибку:
Например. Нельзя в классе перегружать оператор ‘+’ так как показано ниже
Это правило касается любых функций класса.