Информатика и технология программирования

         

Переопределение операторов


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

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

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

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


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

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

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



-если функция задается как обычная функция-элемент класса, то первым операндом операции является объект класса, указатель на который передается неявным параметром this ;



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


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



//------------------------------------------------------bk73-03.cpp

//------Переопределение операций в классе "дата"

static int days[]={ 0,31,28,31,30,31,30,31,31,30,31,30,31};
class dat
{
int day,month,year;
public:
void next(); // Элемент-функция вычисления

// следующего для

dat operator++(); // Операция ++

dat operator+(int); // Операция "дата + целое" с передачей

// первого операнда через this

// Операция с явной передачей

friend dat operator+(int,dat); // всех аргументов по значению

dat(int=0,int=0,int=0);
dat(char *); //

~dat(); //

}; //

//------ Функция вычисления следующего дня -----------------

// Используется ссылка на текущий объект this,

// который изменяется в процессе операции

//----------------------------------------------------------

void dat::next()
{
day++;
if (day &#62 days[month])
{
if ((month==2) &#38&#38 (day==29) &#38&#38 (year%4==0)) return;
day=1; month++;
if (month==13)
{
month=1; year++;
}
}
}
//------ Операция инкремента даты -------------------------

// 1. Первый операнд по указателю this

// 2. Возвращает копию входного объекта (операнда)

// до увеличения

// 3. Соответствует операции dat++ (увеличение после

// использования)

// 4. Замечание: для унарных операций типа -- или ++

// использование их до или после операнда не имеет

// значения (вызывается одна и та же функция).

//--------------------------------------------------------

dat dat::operator++()
{ // Создается временный объект

dat x = *this; // В него копируется текущий объект

dat::next(); // Увеличивается значение текущего объекта

return(x); // Возвращается временный объект по

} // значению

//------ Операция "дата + целое" --------------------------

// 1. Первый операнд по указателю this

// 2. Входной объект не меняется, результат возвращается

// в виде значения автоматического объекта x

//---------------------------------------------------------




dat dat::operator+(int n)
{
dat x;
x = *this; // Копирование текущего объекта в x

while (n-- !=0) x.next(); // Вызов функции next для объекта x

return(x); // Возврат объекта x по значению

}
//------ Операция "целое + дата" -------------------------

// 1. Дружественная функция с полным списком операндов

// 2. Второй операнд класса dat - передается по значению,

// поэтому может модифицироваться без изменения исходного

// объекта

//--------------------------------------------------------

dat operator+(int n, dat p)
{
while (n-- !=0) p.next(); // Вызов функции next для p

return(p); // Возврат копии объекта p

}
//--------------------------------------------------------

void main()
{
int i;
dat a, b(17,12,1990), c(12,7), d(3), e;
dat *p = new dat[10];
e = a++;
d = b+15;
for (i=0; i&#60 10; i++) p[i] = p[i] + i;
delete[10] p; }



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



//------------------------------------------------------bk73-04.cpp

//------ Операция "дата + целое"

// 1. Функция с неявным первым операндом по указателю this

// 2. Меняется значение текущего объекта

// 3. Результат - ссылка на текущий объект

//--------------------------------------------------------

dat&#38 dat::operator+(int n)
{
while (n-- !=0) next(); // Вызов next с текущим объектом

return(*this); // Возврат ссылки на объект this

}
//------ Операция "дата + целое" -------------------------

// 1. Дружественная функция с полным списком аргументов

// 2. Первый операнд класса dat - ссылка, меняется при

// выполнении операции

// 3. Результат - ссылка на операнд

//--------------------------------------------------------

dat&#38 operator+(dat&#38 p,int n)
{
while (n-- !=0) p.next(); // Вызов next для объекта p, заданного ссылкой




return(p); // Возврат ссылки на p

}
//----- Операция "целое + дата" --------------------------

// 1. Дружественная функция с полным списком аргументов

// 2. Второй операнд класса dat - ссылка, меняется при

// выполнении операции

// 3. Результат - ссылка на операнд

//--------------------------------------------------------

dat&#38 operator+(int n, dat&#38 p)
{
while (n-- !=0) p.next(); // Вызов next для объекта p, заданного ссылкой

return(p); // Возврат ссылки на p

}

void main()
{
dat a,b; // "Арифметические" эквиваленты

a + 2 + 3; 5 + b + 4; // a = a + 2; a = a + 3; b = 5 + b; b = b + 4;

}



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

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





//------------------------------------------------------bk73-05.cpp

//------ Операция "дата + целое"

// 1. Функция с неявным первым операндом по указателю this

// 2. Изменяется автоматический объект - копия операнда

// 3. Результат - значение автоматического объекта

//--------------------------------------------------------

dat dat::operator+(int n)
{
dat tmp = *this; // Объект - копия операнда

while (n-- !=0) tmp.next(); // Вызов next с объектом tmp

return(tmp); // Возврат значения объекта tmp

}
//------ Операция "дата + целое" -------------------------

// 1. Дружественная функция с полным списком аргументов

// 2. Первый параметр класса dat передается по значению,

// является копией первого операнда и меняется при

// выполнении операции

// 3. Результат - значение формального параметра

//--------------------------------------------------------

dat operator+(dat p,int n)
{
while (n-- !=0) p.next(); // Вызов next для объекта p, копии операнда

return(p); // Возврат значения - формального параметра

}

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


Содержание раздела