Урок 131. Перегрузка операторов через дружественные функции

   ⁄ 

 Обновлено 5 Апр 2018  ⁄ 

⁄   1281

Арифметические операторы плюс (+), минус (-), умножения (*) и деления (/) являются одними из наиболее используемых операторов в языке C++. Все они являются бинарными, то есть работают только с двумя операндами, по одному с каждой стороны.

Есть три разных способа перегрузки операторов:

  через дружественные функции;

  через обычные функции;

  через методы класса.

В этом уроке мы рассмотрим перегрузку операторов через дружественные функции, в следующем — через обычные функции, и уже потом – через методы классов. Естественно, рассмотрим что, где и когда следует использовать.



Перегрузка операторов через дружественные функции

Используя следующий класс:

Перегрузим оператор плюс (+) для выполнения операции сложения двух объектов класса Values:

Результат выполнения программы выше:

I have 16 dollars.

Здесь мы:

  объявили дружественную функцию operator+;

  задали в качестве параметров два операнда, которые хотим добавить – два объекта класса Dollars;

  указали соответствующий тип возврата – Dollars;

  записали реализацию операции сложения.

Для выполнения операции сложения двух объектов класса Dollars, нам нужно добавить переменную-член m_dollars первого объекта к m_dollars второго объекта. Поскольку наша перегруженная функция operator+() является дружественной классу Dollars, то мы можем напрямую обращаться к закрытому члену m_dollars. Кроме того, поскольку m_dollars является целочисленным значением, а C++ знает, как добавлять целочисленные значения, то компилятор будет использовать встроенную версию operator+ для работы с типом int, поэтому мы можем просто указать оператор + в нашей операции сложения двух объектов класса Dollars.

Перегрузка оператора минус (-) аналогична:

Перегрузка оператора умножения (*) и оператора деления (/) аналогичны, только вместо знака минус указываете * или /.

Дружественные функции могут быть определены внутри класса

Несмотря на то, что дружественные функции не являются членами класса, они по-прежнему могут быть определены внутри класса, если это необходимо:

Мы не рекомендуем так делать, поскольку нетривиальные определения функций лучше записывать в отдельном .cpp файле вне класса (детальнее об этом в уроке 122).

Перегрузка операторов с операндами разных типов

Один оператор может работать с операндами разных типов. Например, мы можем добавить Dollars(5) к числу 5 для получения результата Dollars(10).

Когда C++ обрабатывает выражение a + b, то a становится первым параметром, а b становится вторым параметром. Когда a и b одного и того же типа, то не имеет значения, пишите ли вы a + b или b + a — в любом случае, вызывается одна и та же версия operator+. Однако, если операнды разных типов, то a + b уже не то же самое, что b + a.

Например, Dollars(5) + 5 приведет к вызову operator+(Dollars, int), а 5 + Dollars(5) приведет к вызову operator+(int, Dollars). Следовательно, всякий раз, при перегрузке бинарных операторов для работы с операндами разных типов, нужно писать две функции — по одной на каждый случай. Например:

Обратите внимание, обе перегруженные функции имеют одну и ту же реализацию — это потому, что они выполняют одно и то же, просто принимают параметры в разном порядке.

Еще пример

Рассмотрим другой пример:

Класс Values отслеживает минимальное и максимальное значения. Мы перегрузили оператор плюс (+) 3 раза для выполнения операции сложения двух объектов класса Values и операций сложения целочисленного значения и одного объекта Values.

Результат выполнения программы выше:

Result: (4, 17)

Мы получили минимальное и максимальное значения из всех, которые указали в vFinal. Рассмотрим детальнее, как обрабатывается строчка Values vFinal = v1 + v2 + 6 + 9 + v3 + 17;:



  приоритет оператора + выше приоритета оператора =, и ассоциативность оператора + слева направо, поэтому сначала вычисляется v1 + v2. Это приводит к вызову operator+(v1, v2), которое возвращает Values(7, 14);

  затем следующей выполняется операция Values(7, 14) + 6. Это приводит к вызову operator+(Values(7, 14), 6), которое возвращает Values(6, 14);

  тогда выполняется Values(6, 14) + 9, которое возвращает Values(6, 14);

  затем Values(6, 14) + v3 возвращает Values(4, 14);

  и, наконец, Values(4, 14) + 17 возвращает Values(4, 17). Это и является нашим конечным результатом, который присваивается vFinal.

Другими словами, выражение выше обрабатывается как Values vFinal = (((((v1 + v2) + 6) + 9) + v3) + 17), причем каждая последующая операция сложения возвращает объект Values, который становится левым операндом для следующего оператора +.

Примечание: Мы определили operator+(int, Values) вызовом operator+(Values, int) (смотрите код). Это может быть менее эффективным, чем полная отдельная реализация (за счет дополнительного вызова функции), но, таким образом, наш код короче и проще в поддержке + мы уменьшили дублирование кода. Когда это возможно, то определяйте перегруженный оператор вызовом другого перегруженного оператора (как в нашем примере выше)!

Тест

a) Напишите класс Fraction, который имеет два целочисленных члена: числитель (numerator) и знаменатель (denominator). Реализуйте функцию print(), которая будет выводить дробь.

Следующий фрагмент кода:

должен производить следующий результат:

1/4
1/2

Ответ а)

b) Добавьте перегрузку оператора умножения (*) для выполнения операции умножения объекта класса Fraction и целочисленного значения, и для умножения двух объектов класса Fraction. Используйте способ перегрузки оператора через дружественную функцию.

Подсказка: Умножение двух дробей осуществляется умножением двух числителей, а затем, отдельно, двух знаменателей. Для выполнения операции умножения объекта и целочисленного значения — умножьте только числитель на целочисленное значение (знаменатель не трогайте).

Следующий фрагмент кода:

должен производить следующий результат:

3/4
2/7
6/28
9/4
6/7
6/24

Ответ b)

c) Дополнительное задание: Дробь 2/4 – это та же дробь 1/2, только 2/4 не делится до минимальных неделимых значений. Мы можем уменьшить любую заданную дробь до наименьших значений, найдя наибольший общий делитель (НОД) для числителя и знаменателя, а затем выполнив деление как числителя, так и знаменателя на НОД.

Ниже приведена функция поиска НОД:

Добавьте эту функцию в ваш класс и реализуйте метод reduce(), который будет уменьшать дробь. Убедитесь, что дробь будет максимально и корректно уменьшена.

Следующий фрагмент кода:

должен производить следующий результат:

3/4
2/7
3/14
9/4
6/7
1/4

Ответ c)

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (12 оценок, среднее: 4,67 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО