Студопедия — Перегрузка бинарных и унарных операций
Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

Перегрузка бинарных и унарных операций






Следующим шагом в использовании класса как базового типа данных является переопределение операций языка, в которых один или несколько операндов могут быть объектами класса. Это достигается введением функции-элемента специального вида, обращение к которой компилятор формирует при трансляции такой операции. Естественно, что такая функция должна иметь результат, отличный от void, если предполагается использование этой операции внутри другого выражения.

Переопределение операций осуществляется в рамках стандартного синтаксиса языка Си, то есть обозначение операций, их приоритеты и количество операндов остается неизменным.

Можно описывать функции, определяющие значения следующих операций:

+ - * / % ^ & | ~! = < > += -= *= /= %= ^= &= (тип)|= << >> >>= <<= ==!= <= >=&& || ++ -- [] () new delete

Последние четыре - это индексирование, вызов функции, выделение свободной памяти и освобождение свободной памяти. Изменить приоритеты перечисленных операций невозможно, как невозможно изменить и синтаксис выражений. Нельзя, например, определить унарную операцию % или бинарную!. Невозможно определить новые лексические символы операций, но в тех случаях, когда множество операций недостаточно, вы можете использовать запись вызова функции. Используйте например, не **, а pow(). Эти ограничения могут показаться драконовскими, но более гибкие правила могут очень легко привести к неоднозначностям. Например, на первый взгляд определение операции **, означающей возведение в степень, может показаться очевидной и простой задачей, но подумайте еще раз. Должна ли ** связываться влево (как в Фортране) или вправо (как в Алголе)? Выражение a**p должно интерпретироваться как a*(*p) или как (a)**(p)? Имя функции-операции есть ключевое слово operator (то есть, операция), за которым следует сама операция, например, operator<<. Функция-операция описывается и может вызываться так же, как любая другая функция. Использование операции - это лишь сокращенная запись явного вызова функции операции. Например:

1 void f(complex a, complex b)2 {3 complex c = a + b; // сокращенная запись4 complex d = operator+(a,b); // явный вызов оператора-друга5 c = a.operator+(b); // явный вызов оператора-члена6 }

Для переопределения операции используется особая форма функции-элемента с заголовком такого вида:

operator операция(список_параметров-операндов)

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

Имеется два способа описания функции, соответствующей переопределяемой операции:

  • если функция задается как обычная функция-элемент класса, то первым операндом операции является объект класса, указатель на который передается неявным параметром this;
  • если первый операнд переопределяемой операции не является объектом некоторого класса, либо требуется передавать в качестве операнда не указатель, а сам объект (значение), то соответствующая функция должна быть определена как дружественная классу с полным списком аргументов. Естественно, что полное имя такой функции не содержит имени класса.

Бинарная операция может быть определена или как функция-член, получающая один параметр, или как функция-друг, получающая два параметра. Таким образом, для любой бинарной операции @ aa@bb может интерпретироваться или как aa.operator@(bb), или как operator@(aa,bb). Если определены обе, то aa@bb является ошибкой. Унарная операция, префиксная или постфиксная, может быть определена или как функция-член, не получающая параметров, или как функция-друг, получающая один параметр. Таким образом, для любой унарной операции @ aa@ или @aa может интерпретироваться или как aa.operator@(), или как operator@(aa). Если определена и то, и другое, то и aa@ и @aa являются ошибками. Рассмотрим следующие примеры:

01 class X {02 // друзья03 friend X operator-(X); // унарный минус04 friend X operator-(X,X); // бинарный минус05 friend X operator-(); // ошибка: нет операндов06 friend X operator-(X,X,X); // ошибка: тернарная07 // члены (с неявным первым параметром: this)08 X* operator&(); // унарное & (взятие адреса)09 X operator&(X); // бинарное & (операция И)10 X operator&(X,X); // ошибка: тернарное11 };

Необходимо отметить также и тот факт, что для каждой комбинации типов операндов в переопределяемой операции необходимо ввести отдельную функцию, то есть транслятор не может производить перестановку операндов местами, даже если базовая операция допускает это. Например, при переопределении операции сложения объекта класса dat с целым необходимо две функции dat+int и int+dat.

В качестве примера рассмотрим доопределение стандартных операций над датами:

01 /*********************************************************02 * dat.h *03 *********************************************************/04 class dat 05 {06 int day,month,year;07 public:08 void next(); // Элемент-функция вычисления следующего дня09 dat operator++(); // Операция ++10 dat operator+(int);// Операция "дата + целое" с передачей11 // первого операнда через this12 friend dat operator+(int,dat);// Операция с явной передачей всех 13 // аргументов по значению14 dat(int=0,int=0,int=0);15 dat(char *); //16 ~dat(); //17 }; //01 /*********************************************************02 * main.cpp *03 *********************************************************/04 #include "dat.h"05 void main()06 {07 int i;08 dat a, b(17,12,1990), c(12,7), d(3), e;09 dat *p = new dat[10];10 e = ++a; // следующий день11 d = b+14; // через 2 недели12 for (i=0; i<10; i++) p[i] = i + с; // календарь на декаду13 delete[] p;14 }01 /*********************************************************02 * dat.cpp *03 *********************************************************/04 #include "dat.h"05 static int days[]={ 0,31,28,31,30,31,30,31,31,30,31,30,31};06 //------ Функция вычисления следующего дня -----------------07 // Используется ссылка на текущий объект this,08 // который изменяется в процессе операции09 void dat::next()10 {11 day++;12 if (day > days[month])13 {14 if ((month==2) && (day==29) && (year%4==0)) return;15 day=1; month++;16 if (month==13)17 {18 month=1; year++;19 }20 }21 }22 //------ Операция инкремента даты -------------------------23 // 1. Первый операнд по указателю this24 // 2. Возвращает копию входного объекта (операнда)25 // до увеличения26 // 3. Соответствует операции dat++ (увеличение после27 // использования)28 // 4. Замечание: для унарных операций типа -- или ++29 // использование их до или после операнда не имеет30 // значения (вызывается одна и та же функция).31 dat dat::operator++()32 { // Создается временный объект33 dat x = *this; // В него копируется текущий объект34 next(); // Увеличивается значение текущего объекта35 return(x); // Возвращается временный объект по36 } // значению37 //------ Операция "дата + целое" --------------------------38 // 1. Первый операнд по указателю this39 // 2. Входной объект не меняется, результат возвращается40 // в виде значения автоматического объекта x41 dat dat::operator+(int n)42 {43 dat x;44 x = *this; // Копирование текущего объекта в x45 while (n--!=0) x.next();// Вызов функции next для объекта x46 return(x); // Возврат объекта x по значению47 }48 //------ Операция "целое + дата" -------------------------49 // 1. Дружественная функция с полным списком операндов50 // 2. Второй операнд класса dat - передается по значению,51 // поэтому может модифицироваться без изменения исходного52 // объекта53 dat operator+(int n, dat p)54 {55 while (n--!=0) p.next(); // Вызов функции next для p56 return(p); // Возврат копии объекта p57 }

Для многих переопределяемых операций тип результата совпадает с типом одного из операндов. Это позволяет выполнить подряд несколько операций в одном выражении. Возможны различные варианты реализации в соответствии со способами передачи параметров и результата: по значению или по ссылке. Отметим наиболее важные из них:

01 //------ Операция "дата + целое" --------------------------02 // 1. Функция с неявным первым операндом по указателю this03 // 2. Меняется значение текущего объекта04 // 3. Результат - ссылка на текущий объект05 dat& dat::operator+(int n)06 {07 while (n--!=0) next(); // Вызов next с текущим объектом08 return(*this); // Возврат ссылки на объект this09 }10 //------ Операция "дата + целое" -------------------------11 // 1. Дружественная функция с полным списком аргументов12 // 2. Первый операнд класса dat - ссылка, меняется при13 // выполнении операции14 // 3. Результат - ссылка на операнд15 dat& operator+(dat& p,int n)16 {17 while (n--!=0) p.next(); // Вызов next для объекта p,18 // заданного ссылкой19 return(p); // Возврат ссылки на p20 }21 //----- Операция "целое + дата" --------------------------22 // 1. Дружественная функция с полным списком аргументов23 // 2. Второй операнд класса dat - ссылка, меняется при24 // выполнении операции25 // 3. Результат - ссылка на операнд26 dat& operator+(int n, dat& p)27 {28 while (n--!=0) p.next(); // Вызов next для объекта p,29 // заданного ссылкой30 return(p); // Возврат ссылки на p31 }32 //--------------------------------------------------------33 void main()34 {35 dat a, b; // "Арифметические" эквиваленты36 a + 2 + 3; // a = a + 2; a = a + 3;37 5 + b + 4; // b = 5 + b; b = b + 4;38 }

Во всех трех случаях ссылка на операнд - объект класса возвращается в качестве результата. Все действия, выполняемые операцией, реализуются в том же объекте, который "накапливает" результат.

Естественный "арифметический" вид переопределяемых операций получается, когда результат возвращается по значению не в виде ссылки, а в виде объекта:

01 //------ Операция "дата + целое" --------------------------02 // 1. Функция с неявным первым операндом по указателю this03 // 2. Изменяется автоматический объект - копия операнда04 // 3. Результат - значение автоматического объекта05 dat dat::operator+(int n)06 {07 dat tmp = *this; // Объект - копия операнда08 while (n--!=0) tmp.next();// Вызов next с объектом tmp09 return(tmp); // Возврат значения объекта tmp10 }11 //------ Операция "дата + целое" -------------------------12 // 1. Дружественная функция с полным списком аргументов13 // 2. Первый параметр класса dat передается по значению,14 // является копией первого операнда и меняется при15 // выполнении операции16 // 3. Результат - значение формального параметра17 dat operator+(dat p,int n)18 {19 while (n--!=0) p.next(); // Вызов next для объекта p,20 // копии операнда21 return(p); // Возврат значения22 } // формального параметра

Во втором случае, когда формальный параметр - операнд передается по значению, он является отдельным объектом, в который копируется объект - фактический параметр. Поэтому его изменение не затрагивает входного операнда. Кроме того, в обоих случаях при возвращении объекта в качестве результата транслятор создает в вызывающей функции временный объект, в который копируется содержимое объекта-результата в операторе return. Дополнительная проблема для таких объектов заключается в их корректном конструировании.

При отсутствии переопределения операции присваивания производится побайтное копирование объектов. Такая интерпретация операции присваивания некорректна, если объект имеет указатели на динамические переменные или массивы, идентификаторы связанных ресурсов и т.д. При копировании таких объектов необходимо сначала уничтожить связанные динамические переменные и ресурсы левого операнда, а затем заново резервировать, но уже с параметрами, необходимыми для интерпретации операции присваивания:

00 #include <string.h>01 class string02 {03 char *Str;04 int size;05 public:06 string& operator=(string&);07 };08 string& string::operator=(string& right)09 {10 if(Str!=NULL) delete Str;// Освободить динамическую память11 size = right.size; // Резервировать память12 Str = new char[size];13 strncpy(Str,right->Str,size); // Копировать строки14 return *this;15 }






Дата добавления: 2015-10-01; просмотров: 392. Нарушение авторских прав; Мы поможем в написании вашей работы!



Функция спроса населения на данный товар Функция спроса населения на данный товар: Qd=7-Р. Функция предложения: Qs= -5+2Р,где...

Аальтернативная стоимость. Кривая производственных возможностей В экономике Буридании есть 100 ед. труда с производительностью 4 м ткани или 2 кг мяса...

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

Расчетные и графические задания Равновесный объем - это объем, определяемый равенством спроса и предложения...

В эволюции растений и животных. Цель: выявить ароморфозы и идиоадаптации у растений Цель: выявить ароморфозы и идиоадаптации у растений. Оборудование: гербарные растения, чучела хордовых (рыб, земноводных, птиц, пресмыкающихся, млекопитающих), коллекции насекомых, влажные препараты паразитических червей, мох, хвощ, папоротник...

Типовые примеры и методы их решения. Пример 2.5.1. На вклад начисляются сложные проценты: а) ежегодно; б) ежеквартально; в) ежемесячно Пример 2.5.1. На вклад начисляются сложные проценты: а) ежегодно; б) ежеквартально; в) ежемесячно. Какова должна быть годовая номинальная процентная ставка...

Выработка навыка зеркального письма (динамический стереотип) Цель работы: Проследить особенности образования любого навыка (динамического стереотипа) на примере выработки навыка зеркального письма...

Влияние первой русской революции 1905-1907 гг. на Казахстан. Революция в России (1905-1907 гг.), дала первый толчок политическому пробуждению трудящихся Казахстана, развитию национально-освободительного рабочего движения против гнета. В Казахстане, находившемся далеко от политических центров Российской империи...

Виды сухожильных швов После выделения культи сухожилия и эвакуации гематомы приступают к восстановлению целостности сухожилия...

КОНСТРУКЦИЯ КОЛЕСНОЙ ПАРЫ ВАГОНА Тип колёсной пары определяется типом оси и диаметром колес. Согласно ГОСТ 4835-2006* устанавливаются типы колесных пар для грузовых вагонов с осями РУ1Ш и РВ2Ш и колесами диаметром по кругу катания 957 мм. Номинальный диаметр колеса – 950 мм...

Studopedia.info - Студопедия - 2014-2024 год . (0.012 сек.) русская версия | украинская версия