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. Можно ли реализовать операторные функции в классе, которые перегружают одинаковый оператор, получают одинаковые параметры но возвращают разные значения?
Нет, нельзя. Операторная функция не может иметь несколько реализаций в классе с одинаковой сигнатурой параметров (когда типы и количество параметров совпадают). В случае нарушения этого правила компилятор выдает ошибку:
Например. Нельзя в классе перегружать оператор ‘+’ так как показано ниже
Это правило касается любых функций класса.
13. Можно ли реализовать две и более операторных функции в классе, которые перегружают одинаковый оператор и получают разные (отличные между собой) параметры?
Перегрузка операторов
operator Ключевое слово объявляет функцию, указывающую, какой оператор-Symbol означает при применении к экземплярам класса. Это дает оператору более одного значения — «перегружает» его. Компилятор различает разные значения оператора, проверяя типы его операндов.
Синтаксис
тип operator operator-символ ( parameter-list )
Remarks
Функцию большинства встроенных операторов можно переопределить глобально или для отдельных классов. Перегруженные операторы реализуются в виде функции.
Имя перегруженного оператора — operator x, где x — это оператор, как показано в следующей таблице. Например, для перегрузки оператора сложения необходимо определить функцию с именем operator +. Аналогично, чтобы перегрузить оператор сложения и присваивания, += Определите функцию с именем operator + =.
Переопределяемые операторы
| Оператор | Имя | Тип |
|---|---|---|
| , | Запятая | Двоичные данные |
| ! | Логическое НЕ | Унарный |
| != | Неравенство | Двоичные данные |
| % | Modulus | Двоичные данные |
| %= | Назначение модуля | Двоичные данные |
| & | Побитовое И | Двоичные данные |
| & | Взятие адреса | Унарный |
| && | Логическое И | Двоичные данные |
| &= | Назначение побитового И | Двоичные данные |
| ( ) | Вызов функции | — |
| ( ) | Оператор приведения | Унарный |
| * | Умножение | Двоичные данные |
| * | Разыменование указателя | Унарный |
| *= | Присваивание умножения | Двоичные данные |
| + | Сложение | Двоичные данные |
| + | Унарный плюс | Унарный |
| ++ | Шаг 1 | Унарный |
| += | Присваивание сложения | Двоичные данные |
| — | Вычитание | Двоичные данные |
| — | Унарное отрицание | Унарный |
| — | Уменьшить 1 | Унарный |
| -= | Присваивание вычитания | Двоичные данные |
| -> | Выбор члена | Двоичные данные |
| — >* | Выбор указателя на член | Двоичные данные |
| / | Отдел | Двоичные данные |
| /= | Присваивание деления | Двоичные данные |
| Больше | Двоичные данные | |
| >= | Больше или равно | Двоичные данные |
| >> | Сдвиг вправо | Двоичные данные |
| >>= | Сдвиг вправо и присваивание | Двоичные данные |
| [ ] | Индекс массива | — |
| ^ | Исключающее ИЛИ | Двоичные данные |
| ^= | Исключающее ИЛИ/присваивание | Двоичные данные |
| | | Побитовое ИЛИ | Двоичные данные |
| |= | Назначение побитового включающего ИЛИ | Двоичные данные |
| || | Логическое ИЛИ | Двоичные данные |
| Дополнение до единицы | Унарный | |
| delete | Удаление | — |
| new | Создать | — |
| операторы преобразования | операторы преобразования | Унарный |
Существует 1 две версии унарных операторов инкремента и декремента: добавочное и инкрементное.
Перегрузка операторов в C++. Основы
Авторизуйтесь
Перегрузка операторов в C++. Основы
В C++ этого ограничения нет — мы можем перегрузить практически любой известный оператор. Возможностей не счесть: можно выбрать любую комбинацию типов операндов, единственным ограничением является необходимость того, чтобы присутствовал как минимум один операнд пользовательского типа. То есть определить новый оператор над встроенными типами или переписать существующий нельзя.
Когда стоит перегружать операторы?
Приведём хороший и плохой примеры перегрузки операторов. Вышеупомянутое сложение матриц — наглядный случай. Здесь перегрузка оператора сложения интуитивно понятна и, при корректной реализации, не требует пояснений:
Примером плохой перегрузки оператора сложения будет сложение двух объектов типа «игрок» в игре. Что имел в виду создатель класса? Каким будет результат? Мы не знаем, что делает операция, и поэтому пользоваться этим оператором опасно.
Как перегружать операторы?
Большую часть операторов можно перегрузить как методами класса, так и простыми функциями, но есть несколько исключений. Когда перегруженный оператор является методом класса, тип первого операнда должен быть этим классом (всегда *this ), а второй должен быть объявлен в списке параметров. Кроме того, операторы-методы не статичны, за исключением операторов управления памятью.
При перегрузке оператора в методе класса он получает доступ к приватным полям класса, но скрытая конверсия первого аргумента недоступна. Поэтому бинарные функции обычно перегружают в виде свободных функций. Пример:
Когда унарные операторы перегружаются в виде свободных функций, им доступна скрытая конверсия аргумента, но этим обычно не пользуются. С другой стороны, это свойство необходимо бинарным операторам. Поэтому основным советом будет следующее:
Реализуйте унарные операторы и бинарные операторы типа “X=” в виде методов класса, а прочие бинарные операторы — в виде свободных функций.
Какие операторы можно перегружать?
Мы можем перегрузить почти любой оператор C++, учитывая следующие исключения и ограничения:
В следующей части вашему вниманию будут представлены перегружаемые операторы C++, в группах и по отдельности. Для каждого раздела характерна семантика, т.е. ожидаемое поведение. Кроме того, будут показаны типичные способы объявления и реализации операторов.
Хинт для программистов: если зарегистрируетесь на соревнования Huawei Cup, то бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.
Перейти к регистрации
Какие операции нельзя перегружать c
К сожалению, лишь ограниченное число типов непосредственно поддерживается любым языком программирования. Например, С и С++ не позволяют выполнять операции с комплексными числами, матрицами, строками, множествами. Однако, все эти операции можно выполнить через классы в языке С++.
Пусть заданы множества А и В:
А = < а1, а2, а3 >;
В = < a3, a4, a5 >,
и мы хотим выполнить операции объединения ( + ) и пересечения ( * ) множеств.
При необходимости может добавляться и прототип:
Если принять, что конструкция operator знак_операции есть имя некоторой функции, то прототип и определение операции-функции подобны прототипу и определению обычной функции языка С++. Определенная таким образом операция называется перегруженной (overload).
Перегрузка унарных операций
Унарные операции, перегружаемые в рамках определенного класса, могут перегружаться только через нестатическую компонентную функцию без параметров. Вызываемый объект класса автоматически воспринимается как операнд.
Унарные операции, перегружаемые вне области класса (как глобальные функции), должны иметь один параметр типа класса. Передаваемый через этот параметр объект воспринимается как операнд.
а) в первом случае (описание в области класса):
б) во втором случае (описание вне области класса):
Перегрузка бинарных операций
Операции, перегружаемые внутри класса, могут перегружаться только нестатическими компонентными функциями с параметрами. Вызываемый объект класса автоматически воспринимается в качестве первого операнда.
Операции, перегружаемые вне области класса, должны иметь два операнда, один из которых должен иметь тип класса.
Указание параметра int для постфиксной формы не специфицирует второй операнд, а используется только для отличия от префиксной формы.
Перегрузка операции вызова функции
Это операция ‘ () ‘. Она является бинарной операцией. Первым операндом обычно является объект класса, вторым – список параметров.
Перегрузка операции присваивания
Операция отличается тремя особенностями:
Хотя в данном примере эта ошибка и не опасна, в реальных программах с динамическим распределением памяти она может вызвать крах программы.
В этом примере выясняется, не происходит ли самоприсваивание (типа ob = ob ). Если имеет место самоприсваивание, то просто возвращается ссылка на объект.
Затем проверяется, достаточно ли памяти в объекте, стоящем слева от знака присваивания, для объекта, стоящего справа от знака присваивания. Если не достаточно, то память освобождается и выделяется новая, требуемого размера. Затем строка копируется в эту память.
Синтаксис
Перегруженную операцию new можно определить в следующих формах, соответственно для не массивов и для массивов:
Например, пусть задана следующая функция:
и она вызывается следующим образом:
Здесь t = double, n = 5.
Эти функции используют генерацию исключений ( throw ) и собственный распределитель памяти ( allocator ).
Правила использования операции new
Обработка ошибок операции new происходит в два этапа:
В реализации ВС++ включена специальная глобальная переменная _new_handler, значением которой является указатель на new_handler функцию, которая выполняется при неудачном завершении new. По умолчанию, если операция new не может выделить требуемое количество памяти, формируется исключение bad_alloc. Изначально это исключение называлось xalloc и определялось в файле except.h. Исключение xalloc продолжает использоваться во многих компиляторах. Тем не менее, оно вытесняется определенным в стандарте С++ именем bad_alloc.
Рассмотрим несколько примеров.
Пример 2. Поскольку в предыдущем примере при работе в нормальных условиях ошибка выделения памяти маловероятна, в этом примере ошибка выделения памяти достигается принудительно. Процесс выделения памяти длится до тех пор, пока не произойдет ошибка.
Пример 4. Демонстрируются различные формы перегрузки операции new.
Операция-функция delete бывает двух видов:
Особенностью перегрузки операции delete является то, что глобальные операции delete не могут быть перегружены. Их можно перегрузить только по отношению к классу.
Перегрузка операторов в C++
Доброго времени суток!
Желание написать данную статью появилось после прочтения поста Перегрузка C++ операторов, потому что в нём не были раскрыты многие важные темы.
Самое главное, что необходимо помнить — перегрузка операторов, это всего лишь более удобный способ вызова функций, поэтому не стоит увлекаться перегрузкой операторов. Использовать её следует только тогда, когда это упростит написание кода. Но, не настолько, чтобы это затрудняло чтение. Ведь, как известно, код читается намного чаще, чем пишется. И не забывайте, что вам никогда не дадут перегрузить операторы в тандеме со встроенными типами, возможность перегрузки есть только для пользовательских типов/классов.
Синтаксис перегрузки
В данном случае, оператор оформлен как член класса, аргумент определяет значение, находящееся в правой части оператора. Вообще, существует два основных способа перегрузки операторов: глобальные функции, дружественные для класса, или подставляемые функции самого класса. Какой способ, для какого оператора лучше, рассмотрим в конце топика.
В большинстве случаев, операторы (кроме условных) возвращают объект, или ссылку на тип, к которому относятся его аргументы (если типы разные, то вы сами решаете как интерпретировать результат вычисления оператора).
Перегрузка унарных операторов
Рассмотрим примеры перегрузки унарных операторов для определенного выше класса Integer. Заодно определим их в виде дружественных функций и рассмотрим операторы декремента и инкремента:
Теперь вы знаете, как компилятор различает префиксные и постфиксные версии декремента и инкремента. В случае, когда он видит выражение ++i, то вызывается функция operator++(a). Если же он видит i++, то вызывается operator++(a, int). То есть вызывается перегруженная функция operator++, и именно для этого используется фиктивный параметр int в постфиксной версии.
Бинарные операторы
Рассмотрим синтаксис перегрузки бинарных операторов. Перегрузим один оператор, который возвращает l-значение, один условный оператор и один оператор, создающий новое значение (определим их глобально):
Во всех этих примерах операторы перегружаются для одного типа, однако, это необязательно. Можно, к примеру, перегрузить сложение нашего типа Integer и определенного по его подобию Float.
Аргументы и возвращаемые значения
Оптимизация возвращаемого значения
При создании новых объектов и возвращении их из функции следует использовать запись как для вышеописанного примера оператора бинарного плюса.
Честно говоря, не знаю, какая ситуация актуальна для C++11, все рассуждения далее справедливы для C++98.
На первый взгляд, это похоже на синтаксис создания временного объекта, то есть как будто бы нет разницы между кодом выше и этим:
Но на самом деле, в этом случае произойдет вызов конструктора в первой строке, далее вызов конструктора копирования, который скопирует объект, а далее, при раскрутке стека вызовется деструктор. При использовании первой записи компилятор изначально создаёт объект в памяти, в которую нужно его скопировать, таким образом экономится вызов конструктора копирования и деструктора.
Особые операторы
В C++ есть операторы, обладающие специфическим синтаксисом и способом перегрузки. Например оператор индексирования []. Он всегда определяется как член класса и, так как подразумевается поведение индексируемого объекта как массива, то ему следует возвращать ссылку.
Оператор запятая
В число «особых» операторов входит также оператор запятая. Он вызывается для объектов, рядом с которыми поставлена запятая (но он не вызывается в списках аргументов функций). Придумать осмысленный пример использования этого оператора не так-то просто. Хабраюзер AxisPod в комментариях к предыдущей статье о перегрузке рассказал об одном.
Оператор разыменования указателя
Перегрузка этих операторов может быть оправдана для классов умных указателей. Этот оператор обязательно определяется как функция класса, причём на него накладываются некоторые ограничения: он должен возвращать либо объект (или ссылку), либо указатель, позволяющий обратиться к объекту.
Оператор присваивания
Оператор присваивания обязательно определяется в виде функции класса, потому что он неразрывно связан с объектом, находящимся слева от «=». Определение оператора присваивания в глобальном виде сделало бы возможным переопределение стандартного поведения оператора «=». Пример:
Как можно заметить, в начале функции производится проверка на самоприсваивание. Вообще, в данном случае самоприсваивание безвредно, но ситуация не всегда такая простая. Например, если объект большой, можно потратить много времени на ненужное копирование, или при работе с указателями.
Неперегружаемые операторы
Некоторые операторы в C++ не перегружаются в принципе. По всей видимости, это сделано из соображений безопасности.




