Глава 13: Перегрузка операторов



 

В C++ операторы можно перегружать для "классовых" типов, определяемых программистом. Принципиальный выигрыш от перегрузки операторов состоит в том, что она позволяет органично интегрировать новые типы данных в среду программирования.

Перегружая оператор, можно определить его значение для конкретного класса. Например, класс, который определяет связный список, может использовать оператор "+" для добавления объекта к списку. Класс, которые реализует стек, может использовать оператор "+" для записи объекта в стек. В каком‑нибудь другом классе тот же оператор "+" мог бы служить для совершенно иной цели. При перегрузке оператора ни одно из оригинальных его значений не теряется. Перегруженный оператор (в своем новом качестве) работает как совершенно новый оператор. Поэтому перегрузка оператора "+" для обработки, например, связного списка не приведет к изменению его функции (т.е. операции сложения) по отношению к целочисленным значениям.

Перегрузка операторов тесно связана с перегрузкой функций. Чтобы перегрузить оператор, необходимо определить значение новой операции для класса, к которому она будет применяться. Для этого создается функция operator (операторная функция), которая определяет действие этого оператора. Общий формат функции operator таков.

 

 

Операторы перегружаются с помощью функции operator.

Здесь перегружаемый оператор обозначается символом "#" , а элемент тип представляет собой тип значения, возвращаемого заданной операцией. И хотя он в принципе может быть любым, тип значения, возвращаемого функцией operator , часто совпадает с именем класса, для которого перегружается данный оператор. Такая корреляция облегчает использование перегруженного оператора в составных выражениях. Как будет показано ниже, конкретное значение элемента список_аргументов определяется несколькими факторами.

Операторная функция может быть членом класса или не быть им. Операторные функции, не являющиеся членами класса, часто определяются как его "друзья". Операторные функции‑члены и функции‑не члены класса различаются по форме перегрузке. Каждый из вариантов мы рассмотрим в отдельности.

 

Перегрузка операторов с использованием функций‑членов

 

Начнем с простого примера. В следующей программе создается класс three_d , который поддерживает координаты объекта в трехмерном пространстве. Для класса three_d перегружаются операторы "+" и "=" . Итак, рассмотрим внимательно код этой программы.

 

 

При выполнении эта программа генерирует такие результаты.

 

 

Исследуя код этой программы, вы, вероятно, удавились, увидев, что обе операторные функции имеют только по одному параметру, несмотря на то, что они перегружают бинарные операции. Это, на первый взгляд, "вопиющее" противоречие можно легко объяснить. Дело в том, что при перегрузке бинарного оператора с использованием функции‑члена ей передается явным образом только один аргумент. Второй же неявно передается через указатель this . Таким образом, в строке

 

 

под членом х подразумевается член this‑>x , т.е. член х связывается с объектом, который вызывает данную операторную функцию. Во всех случаях неявно передается объект, указываемый слева от символа операции, который стал причиной вызова операторной функции. Объект, располагаемый с правой стороны от символа операции, передается этой функции в качестве аргумента. В общем случае при использовании функции‑члена для перегрузки унарного оператора параметры не используются вообще, а для перегрузки бинарного – только один параметр. (Тернарный оператор "?" перегружать нельзя.) В любом случае объект, который вызывает операторную функцию, неявно передается через указатель this .

Чтобы понять, как работает механизм перегрузки операторов, рассмотрим внимательно предыдущую программу, начиная с перегруженного оператора "+" . При обработке двух объектов типа three_d оператором "+" выполняется сложение значений соответствующих координат, как показано в функции operator+() . Но заметьте, что эта функция не модифицирует значение ни одного операнда. В качестве результата операции эта функция возвращает объект типа three_d , который содержит результаты попарного сложения координат двух объектов. Чтобы понять, почему операция "+" не изменяет содержимое ни одного из объектов‑участников, рассмотрим стандартную арифметическую операцию сложения, примененную, например, к числам 10 и 12 . Результат операции 10+12 равен 22 , но при его получении ни 10 , ни 12 не были изменены. Хотя не существует правила, которое бы не позволяло перегруженному оператору изменять значение одного из его операндов, все же лучше, чтобы он не противоречил общепринятым нормам и оставался в согласии со своим оригинальным назначением.

Обратите внимание на то, что функция operator+() возвращает объект типа three_d . Несмотря на то что она могла бы возвращать значение любого допустимого в C++ типа, тот факт, что она возвращает объект типа three_d , позволяет использовать оператор "+" в таких составных выражениях, как a+b+с . Часть этого выражения, а+Ь , генерирует результат типа three_d , который затем суммируется с объектом с . И если бы эта часть выражения генерировала значение иного типа (а не типа three_d ), такое составное выражение попросту не работало бы.

В отличие от оператора "+" , оператор присваивания приводит к модификации одного из своих аргументов. (Прежде всего, это составляет саму суть присваивания.) Поскольку функция operator=() вызывается объектом, который расположен слева от символа присваивания (= ), именно этот объект и модифицируется в результате операции присваивания. После выполнения этой операции значение, возвращаемое перегруженным оператором, содержит объект, указанный слева от символа присваивания. (Такое положение вещей вполне согласуется с традиционным действием оператора "=" .) Например, чтобы можно было выполнять инструкции, подобные следующей

 

 

необходимо, чтобы операторная функция operator=() возвращала объект, адресуемый указателем this , и чтобы этот объект располагался слева от оператора "=" . Это позволит выполнить любую цепочку присваиваний. Операция присваивания – это одно из самых важных применений указателя this .

Узелок на память. Если для перегрузки бинарного оператора используется функция‑член, объект, стоящий слева от оператора, вызывает операторную функцию и передается ей неявно через указатель this. Объект, расположенный справа от оператора, передается операторной функции как параметр.

 

Использование функций‑членов для перегрузки унарных операторов

 

Можно также перегружать такие унарные операторы, как "++" , "‑‑" , или унарные "‑" и "+" . Как упоминалось выше, при перегрузке унарного оператора с помощью функции‑члена операторной функции ни один объект не передается явным образом. Операция же выполняется над объектом, который генерирует вызов этой функции через неявно переданный указатель this . Например, рассмотрим расширенную версию предыдущего примера программы. В этом варианте для объектов типа three_d определяется операция инкремента.

 

 

Эта версия программы генерирует такие результаты.

 

 

Как видно по последней строке результатов программы, операторная функция operator++() инкрементирует каждую координату объекта и возвращает модифицированный объект, что вполне согласуется с традиционным действием оператора "++" .

Как вы знаете, операторы "++" и "‑‑" имеют префиксную и постфиксную формы. Например, оператор инкремента можно использовать в форме

 

 

и в форме

 

 

Как отмечено в комментариях к предыдущей программе, функция operator++() определяет префиксную форму оператора "++" для класса three_d . Но нам ничего не мешает перегрузить и постфиксную форму. Прототип постфиксной формы оператора "++" для класса three_d имеет следующий вид.

 

 

Операторы инкремента и декремента имеют как префиксную, так и постфиксную формы.

Параметр notused не используется самой функцией. Он служит индикатором для компилятора, позволяющим отличить префиксную форму оператора инкремента от постфиксной. (Этот параметр также используется в качестве признака постфиксной формы и для оператора декремента.) Ниже приводится один из возможных способов реализации постфиксной версии оператора "++" для класса three_d.

 

 

Обратите внимание на то, что эта функция сохраняет текущее значение операнда путем выполнения такой инструкции.

 

 

Сохраненное значение операнда (в объекте temp ) возвращается с помощью инструкции return . Следует иметь в виду, что традиционный постфиксный оператор инкремента сначала получает значение операнда, а затем его инкрементирует. Следовательно, прежде чем инкрементировать текущее значение операнда, его нужно сохранить, а затем и возвратить (не забывайте, что постфиксный оператор инкремента не должен возвращать модифицированное значение своего операнда).

В следующей версии исходной программы реализованы обе формы оператора "++" .

 

 

Вот как выглядят результаты выполнения этой версии программы.

 

 

Как подтверждают последние четыре строки результатов программы, при префиксном инкрементировании значение объекта c увеличивается до выполнения присваивания объекту a , при постфиксном инкрементировании – после присваивания.

Помните, что если символ "++" стоит перед операндом, вызывается операторная функция operator++() , а если после операнда – операторная функция operator++(int notused) .Тот же подход используется и для перегрузки префиксной и постфиксной форм оператора декремента для любого класса. В качестве упражнения определите оператор декремента для класса three_d.

Важно! Ранние версии языка C++ не содержали различий между префиксной и постфиксной формами операторов инкремента и декремента. Тогда в обоих случаях вызывалась префиксная форма операторной функции. Это следует иметь в виду, если вам придется работать со старыми С++‑программами.

 


Дата добавления: 2018-09-22; просмотров: 319; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!