Здавалка
Главная | Обратная связь

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



В С++ оператор [] при перегрузке рассматривается как бинарный оператор. Его следует перегружать с помощью функции-члена. Нельзя использовать дружественную функцию. Общая форма функции-оператора operator[]() имеет следующий вид:

тип имя_класса::оpеrator[](int i)
{
// ...
}

Параметр не обязан иметь тип int, но поскольку функция operator[]() обычно используется для индексации массива, то в таком качестве обычно используется целое значение.
Для заданного объекта О выражение

О [3]

преобразуется в вызов функции operator[]():

operator[](3)

В таком случае значение индекса передается функции operator[]() в качестве явного параметра. Указатель this указывает на объект О, тот самый, который вызывает функцию.

Можно создать функцию-оператор operator[]() таким образом, чтобы оператор [] можно было использовать как с левой, так и с правой стороны оператора присваивания. Для этого достаточ­но в качестве возвращаемой величины для operator[]() задать ссылку.

#include <iostream.h>
class atype {
int a[3];
public:
atype (int i, int j, int k) {
a[0] = i;
a[1] = j;
a[2] = k;
}
int &operator[] (int i) { return a[i]; }
};
int main()
{
atype ob(1, 2, 3);
cout << ob[1]; // выводит 2
cout << " ";
ob[1] = 25; // [] слева от =
cout << ob[1]; // выводит 25
return 0;
}

 

На применение переменных ссылочного типа имеется ряд огра­ничений:

  • Нельзя взять ссылку от переменной ссылочного типа, иными словами, нельзя взять ее адрес.
  • Нельзя создать массив ссылок.
  • Нельзя создать указатель на ссылку.
  • Ссылки на битовые поля не допускаются.
Работает Не работает (не может вернуть this) вызов функции-оператора operator++() осуществляется копией объекта, а не самим объектом op1. Поэтому изменения, которые претерпевает объект внутри operator++(), не влияют на исходный объект
// перегрузка унарного оператора three_d three_d::operator++ () { х++; у++; z++; return *this; } three_d operator++(three_d op1) { op1.x++; op1.y++; op1.z++; return op1; }

/* перегрузка унарного оператора с помощью дружественной функции, необходимо использовать ссылочный параметр */
three_d operator++(three_d &op1)
{
op1.х++;
op1.y++;
op1.z++;
return op1;
}

Перегрузка ввода-вывода:

Для того, чтобы определить оператор вставки для объекта типа three_d, необходимо опреде­лить этот оператор по отношению к классу three_d. Для этого необходимо перегрузить оператор <<. Один из способов показан ниже.

// вывод координат X, Y, Z (оператор вставки для three_d)
ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << ob j.у << ", ";
stream << ob j.z << "\n";
return stream; // возврат потока
}

Многие особенности этой функции являются общими для всех функций вставки. Прежде всего обратим внимание, что в качестве возвращаемого значения этой функции объявлен объект типа ostream. Это необходимо для того, чтобы один оператор мог содержать несколько операторов вставки. Далее, функция имеет два параметра. Первым служит ссылка на поток, который фигури­рует в левой части оператора <<. Вторым параметром служит объект с правой стороны операто­ра <<. В самой функции осуществляется вывод трех величин, содержащихся в объекте типа three_d, после чего возвращается поток stream. Следующая короткая программа служит для демонстра­ции оператора вставки:

#include <iostream.h>
class three_d {
public:
int x, y, z; // трехмерные координаты
three_d(int a, int b, int c) { x=a; y=b; z=c; }
};
// вывод координат X, Y, Z (оператор вставки для three_d)
ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << obj.у << ", ";
stream << obj.z << "\n";
return stream; // возврат потока
}
int main()
{
three_d a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);
cout << a << b << c;
return 0;
}

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

ostream &operator<<(ostream &поток, тип_класса объект)
{
// характерный для типа код
return stream; // возврат потока
}

Остается только определить конкретные действия, выполняемые таким оператором вставки. Об­ратим внимание на необходимость возвращать stream. Также является приемлемым и, более того, является общей практикой использовать в качестве параметра объект ссылку, а не сам объект. Преимуществом передачи ссылки на объект служит то, что если объект является большим, то гораздо быстрее передать его адрес. Во-вторых, это предотвращает вызов деструктора объекта когда функция вставки возвращает результат.

Кажется странным, почему функция вставки объекта типа three_d не реализована в коде напо­добие следующего:

// ограниченная версия - не использовать
ostream &operator<<(ostream &stream, three_d obj)
{
cout << obj.x << ", ";
cout << obj.у << ", ";
cout << obj.z << "\n";
return stream; // возврат потока
}

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

В программе вставки класса three_d перегруженная функция вставки не является членом класса three_d. Действительно, ни функция вставки, ни функция извлечения не могут быть членами клас­са. Причина заключается в том, что если функция-оператор является членом класса, то левым операндом, неявно передаваемым с использованием указателя this, служит объект того класса, который осуществляет вызов функции-оператора. И нет способа изменить такой порядок. Вместе с тем при перегрузке оператора вставки левым аргументом является поток, а правым аргументом — объект класса. Поэтому перегруженные операторы вставки не могут быть функциями-членами. Тот факт, что функции вставки не могут быть членами класса для действия, на котором они определены, вызывает серьезные вопросы: как перегруженный оператор вставки может получить доступ к частным членам класса? В предыдущей программе переменные х, у и z были публичны­ми, так что оператор вставки имел к ним доступ. Однако защита данных является одной из важ­нейших особенностей объектно-ориентированного программирования, и требовать, чтобы все данные были публичными, значит противоречить самому духу объектно-ориентированного про­граммирования. Тем не менее данная проблема имеет решение: оператор вставки может быть другом класса. В качестве друга класса, для которого он определен, он имеет доступ к частным данным. Иллюстрация этого имеется в следующем примере:

#include <iostream.h>
class three_d {
int x, у, z; // трехмерные координаты - теперь частные
public:
three_d(int a, int b, int c) { x=a; y=b; z=c; }
friend ostream &operator<<(ostream &stream, three_d obj);
};
// вывод координат X, Y, Z (оператор вставки для three_d)
ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << obj.у << ", ";
stream << obj.z << "\n";
return stream; // возврат потока
}
int main() {
three_d a(1, 2, 3), b(3, 4, 5), с (5, 6, 7);
cout << a << b << c;
return 0;
}

Обратим внимание, что переменные х, у и z являются в данном случае частными членами класса three_d, но тем не менее продолжают быть доступными непосредственно с помощью функции вставки. Объявление операторов вставки и извлечение друзьями классов, для которых они опре­делены, позволяет сохранить неприкосновенным принцип инкапсуляции объектно-ориентированного программирования.

Для того, чтобы перегрузить оператор извлечения — экстрактор, используется тот же самый об­щий подход, что и для перегрузки операторов вставки — инсертеров. Например, следующий эк­страктор осуществляет ввод трехмерных координат. Обратим внимание, что он также осуществ­ляет подсказку пользователю.

// получение трехмерных значений - экстрактор
istream &operator>>(istream &stream, three_d &obj)
{
cout << "Enter X Y Z values, separating each with a space: " ;
stream >> obj.x >> obj.у >> obj.z;
return stream;
}

Экстрактор должен возвращать ссылку на объект типа istream. Первым параметром должна быть ссылка на объект типа istream. Вторым параметром служит ссылка на объект, принимаю­щий ввод. Поскольку это именно ссылка, то второй аргумент может быть модифицирован при вводе информации.

Общая форма экстрактора имеет вид:

istream &operator>> (istream &поток, тип_класса &объект)
// код экстрактора
return stream;
}

 

ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << obj.у << ", ";
stream << obj.z << "\n";
return stream; // возврат потока
}

 

Подобно функциям вставки, функции извлечения не могут быть членами класса, на элементах которого они оперируют. Как показано в примере, они могут быть друзьями или просто незави­симыми функциями. За исключением того, что экстрактор должен возвращать ссылку на объект типа istream, внутри экстрактора можно делать все, что угодно. Однако для лучшей структуриза­ции программы и ее ясности лучше всего ограничить эти действия только операциями ввода.

Оператор Рекомендуемая форма
Все унарные операторы Член класса
= () [] -> ->* Обязательно член класса
+= -= /= *= ^= &= |= %= >>= <<= Член класса
Остальные бинарные операторы Не член класса

 


Задачи

 

Базовый уровень

Перегрузить операции ввода, вывода, преобразование в строку toString

1. Создать класс Point для работы с точками на плоскости. Координаты точки – декартовы. Обязательно должны быть реализованы: перемещение точки по оси Х, перемещение по оси Y, определение расстояния до начала координат, расстояния между двумя точками, сравнение на совпадение и несовпадение

2. Создать класс «банкомат» для работы с выдачей купюр в соответствии с суммой. Класс представлен номиналами и количеством купюр соответствующего достоинства. Реализовать метод определения суммы, а также формирования множества купюр по введенной сумме.

3. Реализовать класс Bill, представляющий собой разовый платеж за телефонный разговор. Класс должен включать поля: фамилия, номер телефона, тариф за минуту разговора, скидка (в процентах), время начала разговора, время окончания, сумма к оплате. Для представления времени использовать класс Time. Реализовать методы извлечения и изменения полей. Время разговора, подлежащее оплате, вычисляется в минутах. Неполная минута считается за полную. Реализовать метод toString для вывода суммы оплаты

 

Усложненные

4. Создать класс ModelWindow для работы с моделями экранных окон. В качестве полей задаются: заголовок окна, координаты левого верхнего угла, размер по горизонтали, размер по вертикали, цвет окна, состояние «видимое/невидимое», состояние «с рамкой/без рамки». Координаты и размеры указываются в целых числах. Реализовать операции: перемещения окна по горизонтали, по вертикали, изменение высоты и/или ширины окна, изменение цвета, изменение состояния, опрос состояния. Операции передвижения и изменения размера должны осуществлять проверку на пересечение границ экрана. Функция вывода на экран должна индуцировать состояние полей объекта.

5. Реализовать работу с набором (массивом) счетов из задачи 3.







©2015 arhivinfo.ru Все права принадлежат авторам размещенных материалов.