Г. Шилтд - Самоучитель C++ (DJVU) (1114955), страница 29
Текст из файла (страница 29)
Но нельзя для функции — члена класса располагать значение встроенного типа слева от оператора. Например, пусть перегружается оператор-функция— член класса, тогда первая показанная здесь инструкция правильна, а вторая нет: оЬ1 = оь2 + 10; /1 правильно оЬ1 =- 10 ~ оЬ2; // неправильно Несмотря на то, что допустимо строить выражения так, как показано в первом примере, необходимость постоянно думать о том, чтобы объект находился слева от оператора, а значение встроенного типа — справа, может быть обременительной. Решение проблемы состоит в том, чтобы сделать перегруженную оператор- функцию дружественной и задать обе возможные ситуации.
Как вы знаете, дружественной оператор-функции передаются явно оба операнда. Таким образом, можно задать перегружаемую дружественную функцию так, чтобы левый операнд был объектом, а правый — операндом другого типа. Затем можно снова перегрузить оператор, чтобы левый операнд был значением встроенного типа, а правый — объектом. Следующая программа иллюстрирует такой подход: Дружественные оператор-функпии придают гибкость программе ()1пс1ибе <1ояСгеап» паупер патяеярасе яюс(; с1аяя соотг ( х, у; // значения координат рпЬ11с: соогс)() ( х = Ор у= 0; ) соогс((1пЬ 1, 1гс б) ( х = 1' у = 3' ) то б дес ху(1пс ь1, зпг аз) ( 1 = хг з = уг ) 1ггепб соогб орегасог+ (соогс( оЫ, гпп 1); тггепб соогс) орегасог+(1пс 1, соогс оЫ); 192 Самоучитель Сч+ I/ ПеРегрузка оператора ч для операции оЬ ~ соогс( орегаког+(соого оЬ1, 1пс ( соогд сетлр; сезар.х — оЬт.х ь 1; Ьеглр.у = оЬ1.
у а ге цгп епр; перегрузка оператора г для операции 1пл + оЬ соотг( орегасог+(1пк 1, соотг) оЬ1) соотг( сетпр; 'сеп~р.х =- оЬ1.х Ьегпр.у = обт.у + гесцгп 'сепр; ) гп! ваьп ( ) соей о1 (10, ! О); )пг х, у; о1 = о! + 10; О объект + целое о1.чей ху(х, у); соц! « "!о! -+ 10) Х: " « х « ", Ъ: " « у « "~п"; о1 = 99 г о1; !1 целое ь объект о1, дел ху (х, у); соцб « "(99 а о1) Х; " «х « ", У: « у « 1п гебигп 0; В результате перегрузки дружественных оператор-функций становятся правильными обе инструкции: о1 = о1 е 10; о!=99+о1; 3. При использовании дружественной оператор-функции для перегрузки унарного оператора ++ или — необходимо передавать операнд в функцию в качестве параметра-ссылки, поскольку дружественной функции не передается указатель !1(1в.
Запомните, что в операторах инкремента и декремента подразумевается, что операнд будет изменен. Однако при перегрузке этих операторов посредством дружественных функций операнд передается по значению. Таким образом, любое изменение параметра внутри дружественной оператор-функции не влияет на объект, являющийся источником вызова. Поскольку при ис- Глава б. Введение в перегрузку операторов пользовании дружественной функции отсутствует явно передаваемый указатель на объект (т. е.
указатель й(а), инкремент и декремент не влияют на операнд. Однако при передаче операнда дружественной функции в качестве параметра-ссылки, изменения, которые имеют место внутри дружественной функции, влияют на объект, являющийся источником вызова. Например, в следующей программе посредством дружественной функции перегружается оператор ++.
Перегрузка оператора ++ с использованием дружественной функции ()1пс1цс(е <ьоясгеагп> цяьпо патпеярасе ягг(; с1аяя соога )пс х, у; !! значения координат рпЬ11с: соогс(() ( х = О; у= О; соогс((ьпг 1, тпг 3) ( х = 1~ у 3' гоьо дес ху(ьпс 61, ьпс Й3) ( 1 = х' 3 = у( йгьепс( соого орегагог++ (соогс(аоЬ); Перегрузка оператора ++ с испОдьвОВаниЕм дРУжественной функции соогс( орегасог++ (соогст ЯоЬ) // использование ссылки в качестве параметра ( оЪ.х++; оЬ.у++; геьзгп оЬ( /! возврацение о(л екта, ставпего источником вызова ьпг та1п () соогс( о1 (10 10) г ьпсх, у; +жо1; // ооьект о1 передается по ссылке о1.дев ху(х, у); соцг « '" (++о1) ~С: " «х « ", 'У: " «у << "1п"; те(птп 0; Если вы используете современный компилятор, то с помощью дружественной оператор-функции можно определить разницу между префиксной и постфиксной формами операторов инкремента и декремента точно так же, как это делалось с помощью функций-членов.
Просто добавьте целый параметр при задании постфиксной версии. Например, здесь приводятся пре- Самоучитель С++ 104 фиксная и постфиксная версии оператора инкремента относительно класса соого: соото орекасог+е (соото ЬоЬ1, уу префикснея версия соска ореквсог+е(соокб Яоо, Ыс посинее); 11 постфиксная версия Если оператор ++ находится перед операндом, то вызывается функция соогд орега1ог++(соогд йоЬ). Однако, если оператор ++ находится после операнда, вызывается функция соогй орегагог++(соогй йоЬ, 1п( по(пвео). В этом случае переменной по(овей будет передано значение О. 1.
Перегрузите операторы — и / для класса соогй посредством дружественных функций. 2. Перепишите класс сопи так, чтобы можно было использовать объекты типа соого для умножения каждой из координат на целое. Должны быть корректными обе следующие инструкции: оЬ * 1п1 и 1пг * оЬ. 3. Объясните, почему решение упражнения 2 требует использования дружественных оператор-Функций. Покажите, как с помощью дружественной оператор-функции перегрузить оператор — относительно класса соогй. Определите как префиксную, так и постфиксную формы. 6.6.
Особенности использования оператора присваивания Как уже отмечалось, относительно класса можно перегрузить оператор присваивания. По умолчанию, если оператор присваивания применяется к объекту, то происходит поразрядное копирование объекта, стоящего справа от оператора, в объект, стоящий слева от оператора. Если это то, что вам нужно, нет смысла создавать собственную функцию пролог=О. Однако бывают случаи, когда точное поразрядное копирование нежелательно. В главе 3 при выделении памяти объекту вам было представлено несколько примеров подобного рода. В таких случаях требуется особая операция присваивания.
Примеры ! 1. Здесь приведена новая версия класса вГг1уре, различные формы которого изучались в предыдущих главах. В этой версии оператор = перегружается так, что указатель р при присваивании не перезаписывается. Глава 6. введение в перетрузхуоператоров 795 ((1пс1пс)е <1ояггеатп> Ф?пс1ибе <сяггупд> ((1пс1ис(е <сяяб1' Ь> пя?пд пап~еярасе ясб; с1аяя ясггуре ( сЬаг *р; 1пс 1еп; рпЬ1?с: яГгсуре(сЬаг+я); -ягггуре () ( соШ « "Освобождение памяти по адресу " « (ыпя?апас)) р « '1п'; де[его [) р; с)тат "дег() ( гегигп р; ) вскоре ЯорегаГог= (ясгсуре йоЬ) г ); ягггуре;: ясгсуре (сЬаг*я) ( ?пг 1; 1 = ясг1еп(в) ъ 1; р = пеи сват [1) 7 1Г((р) ( сопя « "с~иибка выделения памяти1п'"7 ехяс (1); 1еп = 1; я?геру (р, я) // Присваинание.объекта вгггуре 7(вгггуре::орегасог= (ясгсуре аоЬ) выяснение необходимости дополнительной памяти 1~(1еп < оЬ.1еп) ( // требуется выделение дополнительной памяти с(е1есе [ ) р = пеы сЬаг [оЬ.1еп); Ж?р) ( сопя « "Олибка выделения памяти1п"; ехэ'.Г(1) р ) 1еп =- оЬ.1еп; яггсру(р, оЬ.р); гевпгп *ГЬ?я; Самоучитель нп тазп О вСт1уре а( Ч)ривет"), Ь("Здесь" ) сост «а.дев() « сеанс «Ь.дес() « 'М'; а = Ь; // теперь указатель р не переэаписываетсл соцт «а.дес() « 'Хв'; соцт « Ь.дес() « '~п'; гегцгп О; Как видите, перегрузка оператора присваивания предотвращает перезапись указателя р.
При первом контроле выясняется, достаточно ли в объекте слева от оператора присваивания выделено памяти для хранения присваиваемой ему строки. Если это не так, то память освобождается и выделяется новый фрагмент. Затем строка копируется в эту память, а длина строки копируется в переменную 1еи. Отметьте два важных свойства функции орега1ог=(). Во-первых, в ней используется параметр-ссылка, Это необходимо для предотвращения создания копии объекта, стоящего справа от оператора присваивания. Как известно по предыдущим главам, при передаче в функцию объекта создается его копия, и эта копия удаляется при завершении работы функции. В этом случае для удаления копии должен вызываться деструктор, который освобождает память, обозначенную указателем р. Однако память по адресу р все еще необходима объекту, который является аргументом.
Параметр-ссылки помогает решить проблему. Вторым важным свойством функции еуега(аг=О является то, что она возвращает не объект, а ссылку на него. Смысл этого тот же, что и при обычном использовании параметра-ссылки. Функция возвращает временный объект, который удаляется после полного завершения ее работы. Это означает, что для временного объекта будет вызван деструктор, который вызовет освобождение памяти по адресу р, но указатель р (и память на которую он ссылается) все еще необходимы для присваивания значения объекту.
Поэтому, чтобы избежать создания временного объекта, в качестве возвращаемого значения используется ссылка. Как вы узнали из главы 5, создание конструктора копий — это другой путь решения проблем, описанных в двух предыдущих разделах. Но конструктор копий может оказаться не столь эффективным решением, как ссылка в качестве параметра и ссылка в качестве возвращаемого значения функции. Это происходит потому, что использование ссылки исключает затраты ресурсов, 197 Глава б. Введение в перегрузку операторов связанные с копированием объекта в каждом из двух указанных случаев. Как видите, в С++ часто имеется несколько способов достижения одной и той же цели. Понимание их преимуществ и недостатков — это часть процесса вашего становления как профессионального программиста С+»-.
(. Пусть дано следующее объявление класса, добавьте все необходимое для создания типа динамический массив. То есть выделите память для массива и сохраните указатель на эту память по адресу р. Размер массива в байтах сохраните в переменной яие. Создайте функцию рш(), возвращающую ссылку на заданный элемент массива и функцию яе(О, возвращающую значение заданного элемента. Обеспечьте контроль границ массива. Кроме этого перегрузите оператор присваивания так, чтобы выделенная каждому массиву такого типа память не была случайно повреждена при присваивании одного массива другому. (В следующем разделе будет показан более совершенный способ решения этого упражнения.) с1авз пупаггау ( 1п' *р; 1пс вузе; рШ>11с: с)упаттау(тпс в); // передача размера массива в переменной з тп ьрвв(1пс 1); // возвражение осыки на элемент 1пс наес(1гК 1); // возьращение значения переменной 1 создайте функцию орегасот=() 6.7.
Перегрузка оператора индекса массива Ц Последним оператором, который мы научимся перегружать, будет о(тератор индекса массива []. В С++ при перегрузке оператор Ц рассматривается как бинарный. Оператор [] можно перегружать только как функцию-член. Ниже представлена основная форма оператор-функции — члена класса орега$огЦ (): тпип имя класса:: орегаФог (1 (!п1 мвдежс) ( !! 1 С технической точки зрения тип параметра не обязательно должен быть целым, но поскольку оператор-функция орега(от[]Ц, как правило, используется для получения индекса массива, то ее параметр обычно имеет тип )и(.