// поэтому может модифицироваться без изменения исходного объекта
//-------------------------------------------------------dat operator+(dat p,int n)
{
while (n-- !=0) p.next(p); // Вызов функции next для объекта p
return(p); // Возврат копии объекта 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;
dat b(17,12,1990);
dat c(12,7);
dat d(3);
dat e;
dat *p = new dat[10];
clrscr();
e = a++;
d=b+15;
for (i=0; i<10; i++)
p[i] = p[i] + i;
delete[10] p;
}
Многие из переопределяемых операций представляют собой аналог некоторого конвейера, по которому продвигается объект в процессе обработки. Такие операции в качестве одного из операндов и в качестве результата имеют объект одного класса. Вышеприведенные операции с датами как раз являются таковыми. Но преобразование объекта может производиться в таких операциях двумя разными способами. При этом возникают некоторые особенности в вызове конструкторов и деструкторов, на которых необходимо остановиться.
В рассмотренном выше примере одним из параметров элементафункции - объект класса dat передается по значению, аналогично передается и результат операции. Эти два объекта определены неявно и создаются путем копирования из других объектов. Поэтому конструкторы для них не вызываются. Однако при уничтожении этих объектов производится вызов деструкторов. Таким образом, при проектировании класса и передаче его объектов по значению необходимо учитывать эти "лишние" объекты и деструкторы. Перечислим возможные случаи возникновения и уничтожения объектов в функции:
- объект -формальный параметр - создается в стеке, его значение представляет собой копию соответствующего фактического параметра, соответственно, конструктор для него не вызывается;
- автоматический объект x в теле функции создается в стеке перед вызовом функции. Если он инициализируется копией другого объекта (например, текущего), то конструктор для него не вызывается:
dat x = *this;
В противном случае вызывается конструктор с соответствующим набором параметров:
dat x;
dat x("13-JUL-1990");
- в любом случае перед выходом из функции вызывается деструктор для автоматического объекта;
- если функция имеет формальный параметр - объект класса, то
для него вызывается деструктор после выхода из функции (если это
переопределяемый оператор, то после вычисления выражения);
- если функция возврашает объект по значению (а не по явной
или неявной ссылке), то функция получает дополнительный неявный
параметр - ссылку на результат (см. "Неявные ссылки"), а по оператору return производится копирование возвращаемого объекта в
объект-результат по заданной ссылке. Однако при вызове такой
функции транслятор может по-разному формировать эту ссылку:
- если производится прямое присваивание результата функции,
то передается ссылка на объект в левой части операции присваивания;
- в остальных случаях на каждый вызов функции (по синтаксису
программы) вызывающей функцией формируется дополнительный автоматический объект. Конструктор для него не вызывается, так как он
формируется копированием результата функции, а деструктор вызывается при выходе из вызывающей функции, как для обычного автоматического объекта.
Наиболее эффективным способом работы с объектом является передача его на вход (формальный параметр) и выдача на выход в качестве ссылки (преимущественно неявной). Однако в этом способе
все изменения, производимые при выполнении операции, происходят в
исходном объекте. Это не согласуется с интерпретацией большинства
бинарных операций, где результат является отдельным элементом
данных (объектов), а операнды не меняются. В качестве примера
рассмотрим вариант операции сложения "дата + целое", в котором
исходная дата увеличивается на заданное значение и одновременно является результатом операции.
class dat
{
int day,month,year;
public:
void next(); // Элемент-функция вычисления
// следующего для
dat& operator+(int); // Операция "дата + целое"
// с неявным операндом через this
//------ Операция "дата + целое" ------------------------------//1. Дружественная элемент-функция с полным списком аргументов
//2. Альтернативный вариант предыдущей функции
//3. Первый операнд класса dat - неявная ссылка на фактический
// параметр, значение меняется при выполнении операции
//4. Тело функции непосредственно в определении класса.
//-------------------------------------------------------friend dat& operator+(dat& p,int n)
{
while (n-- !=0) p.next();// Вызов функции next для объекта p
// по неявной ссылке на него.
return(p); // Возврат неявной ссылки неа p
}
// Операция "целое + дата" ------------------------------------//1. Дружественная элемент-функция с полным списком аргументов
//2. Второй операнд класса dat - неявная ссылка на фактический
// параметр, значение меняется при выполнении операции.
//3. Тело функции непосредственно в определении класса.
//-------------------------------------------------------
friend dat& operator+(int n, dat& p)
{
while (n-- !=0) p.next(); // Вызов функции next для объекта p
// по неявной ссылке на него.
return(p); // Возврат неявной ссылки на p
}
//-------------------------------------------------------
dat(); // Конструкторы
dat(int,int,int); // (см. предыдущие примеры)
dat(char *); //
~dat(); // Деструктор
}; // (см. предыдущие примеры)
//------ Операция "дата + целое" -------------------------------//1. Элемент-функция с неявным первым аргументом по ссылке this
//2. Меняется значение текущего объекта
//3. Результат - неявная ссылка на текущий объект
//--------------------------------------------------------dat& dat::operator+(int n)
{
while (n-- !=0)
next(); // Вызов функции next с текущим объектом this
return(*this); // Возврат неявной ссылки на объект (this)
}
//---------------------------------------------------------
void main()
{
int i;
dat a;
dat b(17,12,1990);
dat c(12,7);
dat d(3);
dat e;
dat *p = new dat[10];
e = a++;
d=b+15;
for (i=0; i<10; i++)
p[i] + i;
delete[10] p;
}
Лекция 5. Особенности переопределения различных операций
-------------------------------------------------------
Одной из важных возможностей использования переменных базовых типов в операциях является их преобразование к другим типам,
которое может производиться неявно (в бинарных арифметических
операциях и при присваивании), либо с использованием операции явного преобразования типа. Если преследовать целью достижение при
добавлении новых классов всей полноты свойств базовых типов данных, то аналогичные преобразования необходимо ввести и для этих
классов. Далее рассмотрим два возможных способа такого преобразования - стандартный, преобразование объекта класса к переменной
базового типа данных, и нестандартный - преобразование переменной
базового типа данных или объекта класса к объекту другого класса.
5.1 Преобразование к базовому типу данных
----------------------------------------
В качестве примера рассмотрим неявное преобразование объекта класса dat к базовым типам данных int и long. Сущность его заключается в вычислении полного количества дней в дате, заданной входным объектом (long) и количества дней в текущем году в этой же дате (int). Для задания этих операции необходимо переопределить в классе dat одноименные операции int и long. Переопределяемые операции задаются соответствующими элементами-функциями без параметров, ссылка на текущий объект входного класса передается через неявный параметр this. Тип результата совпадает с базовым типом, к которому осуществляется приведение и поэтому не указывается.
static int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
class dat
{
int day,month,year;
public:
operator int(); // Преобразование dat в int
operator long(); // Преобразование dat в long
long operator -(dat &p); // Операция dat-dat вычисляет
// разность дат в днях
dat(); // Конструкторы
dat(int,int,int); //
dat(char *); //
~dat(); // Деструктор
};
//------ Преобразование dat в int -----------------------------// Используется ссылка на текущий объект this
//------------------------------------------------------- dat::operator int()
{
int r; // Текущий результат
int i; // Счетчик месяцев
for (r=0, i=1; i<month; i++) // Число дней в прошедших
r += days[month]; // месяцах
if ((month>2) && (year%4==0)) r++; // Високосный год
r += day; // Дней в текущем месяце
return(r);
}
//------ Преобразование dat в long ---------------------------// Используется ссылка на текущий объект this
//------------------------------------------------------
dat::opertor long()
{
long r; // Текущий результат
r = 365 * (year-1) // Дней в предыдущих полных годах
r += year / 4; // Високосные года
r += (int)(*this); // Дней в текущем году - предыдущая
// операция (явное преобразование
return(r); // dat в int
}
//-------- Операция вычисления разницы двух дат ---------------// Первый операнд по ссылке на текущий объект this
// Второй операнд по неявной ссылке p
//-------------------------------------------------------long dat::operator-(dat& p)
{
return((long)(*this) - (long)p); // Преобразовать оба объекта
// к типу long и вычисл. разность
}
void main()
{
dat a("12-05-1990"); // Дата, заданная текстовой строкой
dat b; // Текущая дата
int c;
long d;
// Явное преобразование к long
printf("С 12-05-1990 прошло %4ld дней\n",(long)b-(long)a);
// Явное преобразование к int
printf("В этом году прошло %3d дней\n",(int)b);
// Неявное преобразование при присваивании
c = b;
d = b - a; // Операция dat-dat
printf("С 12-05-1990 прошло %4ld дней\n",d);
printf("В этом году прошло %3d дней\n",c);
}
5.2 Преобразование переменной к объекту класса
---------------------------------------------
Данный способ не является стандартным и требует проверки работоспособности в используемом компиляторе. Он основан на том
факте, что при компиляции явного или неявного преобразования объекта класса к базовому типу данных "xxx" вызывается переопределяемая операция "operator xxx()". Соответственно, при явном или неявном преобразовании к классу "zzz" должна вызываться переопределяемая операция "operator zzz". Логично, что такая операция должна быть определена в классе "zzz". Но тогда имя соответствующей элемента-функции будет "zzz::zzz", что соответствует конструктору. Таким образом, если необходимо определить явное или неявное преобразование от базового типа или класса "xxx" к классу "zzz",