На этом уроке мы рассмотрим перегрузку операторов через дружественные функции в языке С++, на следующем — через обычные функции, а затем — через методы класса.
Способы перегрузки операторов
Арифметические операторы плюс (+
), минус (-
), умножение (*
) и деление (/
) являются одними из наиболее используемых операторов в языке C++. Все они являются бинарными, то есть работают только с двумя операндами.
Есть три разных способа перегрузки операторов:
через дружественные функции;
через обычные функции;
через методы класса.
Перегрузка операторов через дружественные функции
Используя следующий класс:
1 2 3 4 5 6 7 8 9 |
class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } int getDollars() const { return m_dollars; } }; |
Перегрузим оператор плюс (+
) для выполнения операции сложения двух объектов класса Dollars:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } // Выполняем Dollars + Dollars через дружественную функцию friend Dollars operator+(const Dollars &d1, const Dollars &d2); int getDollars() const { return m_dollars; } }; // Примечание: Эта функция не является методом класса! Dollars operator+(const Dollars &d1, const Dollars &d2) { // Используем конструктор Dollars и operator+(int, int). // Мы имеем доступ к закрытому члену m_dollars, поскольку эта функция является дружественной классу Dollars return Dollars(d1.m_dollars + d2.m_dollars); } int main() { Dollars dollars1(7); Dollars dollars2(9); Dollars dollarsSum = dollars1 + dollars2; std::cout << "I have " << dollarsSum.getDollars() << " dollars." << std::endl; return 0; } |
Результат выполнения программы:
I have 16 dollars.
Здесь мы:
объявили дружественную функцию operator+();
задали в качестве параметров два операнда, с которыми хотим работать — два объекта класса Dollars;
указали соответствующий тип возврата — Dollars;
записали реализацию операции сложения.
Для выполнения операции сложения двух объектов класса Dollars нам нужно добавить к переменной-члену m_dollars
первого объекта m_dollars
второго объекта. Поскольку наша перегруженная функция operator+() является дружественной классу Dollars, то мы можем напрямую обращаться к закрытому члену m_dollars
. Кроме того, поскольку m_dollars
является целочисленным значением, а C++ знает, как добавлять целочисленные значения, то компилятор будет использовать встроенную версию operator+() для работы с типом int, поэтому мы можем просто указать оператор +
в нашей операции сложения двух объектов класса Dollars.
Перегрузка оператора минус (−
) аналогична:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } // Выполняем Dollars + Dollars через дружественную функцию friend Dollars operator+(const Dollars &d1, const Dollars &d2); // Выполняем Dollars - Dollars через дружественную функцию friend Dollars operator-(const Dollars &d1, const Dollars &d2); int getDollars() const { return m_dollars; } }; // Примечание: Эта функция не является методом класса! Dollars operator+(const Dollars &d1, const Dollars &d2) { // Используем конструктор Dollars и operator+(int, int). // Мы имеем доступ к закрытому члену m_dollars, поскольку эта функция является дружественной классу Dollars return Dollars(d1.m_dollars + d2.m_dollars); } // Примечание: Эта функция не является методом класса! Dollars operator-(const Dollars &d1, const Dollars &d2) { // Используем конструктор Dollars и operator-(int, int). // Мы имеем доступ к закрытому члену m_dollars, поскольку эта функция является дружественной классу Dollars return Dollars(d1.m_dollars - d2.m_dollars); } int main() { Dollars dollars1(5); Dollars dollars2(3); Dollars dollarsSum = dollars1 - dollars2; std::cout << "I have " << dollarsSum.getDollars() << " dollars." << std::endl; return 0; } |
Перегрузки оператора умножения (*
) и оператора деления (/
) аналогичны, только вместо знака минус указываете *
или /
.
Дружественные функции могут быть определены внутри класса
Несмотря на то, что дружественные функции не являются членами класса, они по-прежнему могут быть определены внутри класса, если это необходимо:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } // Выполняем Dollars + Dollars через дружественную функцию. // Эта функция не рассматривается как метод класса, хотя она и определена внутри класса friend Dollars operator+(const Dollars &d1, const Dollars &d2) { // Используем конструктор Dollars и operator+(int, int). // Мы имеем доступ к закрытому члену m_dollars, поскольку эта функция является дружественной классу Dollars return Dollars(d1.m_dollars + d2.m_dollars); } int getDollars() const { return m_dollars; } }; int main() { Dollars dollars1(7); Dollars dollars2(9); Dollars dollarsSum = dollars1 + dollars2; std::cout << "I have " << dollarsSum.getDollars() << " dollars." << std::endl; return 0; } |
Не рекомендуется так делать, поскольку нетривиальные определения функций лучше записывать в отдельном файле .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)
. Следовательно, всякий раз, при перегрузке бинарных операторов для работы с операндами разных типов, нужно писать две функции — по одной на каждый случай. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } // Выполняем Dollars + int через дружественную функцию friend Dollars operator+(const Dollars &d1, int value); // Выполняем int + Dollars через дружественную функцию friend Dollars operator+(int value, const Dollars &d1); int getDollars() { return m_dollars; } }; // Примечание: Эта функция не является методом класса! Dollars operator+(const Dollars &d1, int value) { // Используем конструктор Dollars и operator+(int, int). // Мы имеем доступ к закрытому члену m_dollars, поскольку эта функция является дружественной классу Dollars return Dollars(d1.m_dollars + value); } // Примечание: Эта функция не является методом класса! Dollars operator+(int value, const Dollars &d1) { // Используем конструктор Dollars и operator+(int, int). // Мы имеем доступ к закрытому члену m_dollars, поскольку эта функция является дружественной классу Dollars return Dollars(d1.m_dollars + value); } int main() { Dollars d1 = Dollars(5) + 5; Dollars d2 = 5 + Dollars(5); std::cout << "I have " << d1.getDollars() << " dollars." << std::endl; std::cout << "I have " << d2.getDollars() << " dollars." << std::endl; return 0; } |
Обратите внимание, обе перегруженные функции имеют одну и ту же реализацию — это потому, что они выполняют одно и то же, но просто принимают параметры в разном порядке.
Еще один пример
Рассмотрим другой пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#include <iostream> class Values { private: int m_min; // минимальное значение, которое мы обнаружили до сих пор int m_max; // максимальное значение, которое мы обнаружили до сих пор public: Values(int min, int max) { m_min = min; m_max = max; } int getMin() { return m_min; } int getMax() { return m_max; } friend Values operator+(const Values &v1, const Values &v2); friend Values operator+(const Values &v, int value); friend Values operator+(int value, const Values &v); }; Values operator+(const Values &v1, const Values &v2) { // Определяем минимальное значение между v1 и v2 int min = v1.m_min < v2.m_min ? v1.m_min : v2.m_min; // Определяем максимальное значение между v1 и v2 int max = v1.m_max > v2.m_max ? v1.m_max : v2.m_max; return Values(min, max); } Values operator+(const Values &v, int value) { // Определяем минимальное значение между v и value int min = v.m_min < value ? v.m_min : value; // Определяем максимальное значение между v и value int max = v.m_max > value ? v.m_max : value; return Values(min, max); } Values operator+(int value, const Values &v) { // Вызываем operator+(Values, int) return v + value; } int main() { Values v1(11, 14); Values v2(7, 10); Values v3(4, 13); Values vFinal = v1 + v2 + 6 + 9 + v3 + 17; std::cout << "Result: (" << vFinal.getMin() << ", " << vFinal.getMax() << ")\n"; return 0; } |
Класс 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, который имеет два целочисленных члена: числитель и знаменатель. Реализуйте функцию print(), которая будет выводить дробь.
Следующий фрагмент кода:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { Fraction f1(1, 4); f1.print(); Fraction f2(1, 2); f2.print(); } |
Должен выдавать следующий результат:
1/4
1/2
Ответ а)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <iostream> class Fraction { private: int m_numerator = 0; int m_denominator = 1; public: Fraction(int numerator=0, int denominator=1): m_numerator(numerator), m_denominator(denominator) { } void print() { std::cout << m_numerator << "/" << m_denominator << "\n"; } }; int main() { Fraction f1(1, 4); f1.print(); Fraction f2(1, 2); f2.print(); return 0; } |
b) Добавьте перегрузку оператора умножения (*
) для выполнения операции умножения объекта класса Fraction на целочисленное значение и для перемножения двух объектов класса Fraction. Используйте способ перегрузки оператора через дружественную функцию.
Подсказка: Умножение двух дробей осуществляется умножением двух числителей, а затем отдельно двух знаменателей. Для выполнения операции умножения объекта на целочисленное значение, умножьте только числитель на целочисленное значение (знаменатель не трогайте).
Следующий фрагмент кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> int main() { Fraction f1(3, 4); f1.print(); Fraction f2(2, 7); f2.print(); Fraction f3 = f1 * f2; f3.print(); Fraction f4 = f1 * 3; f4.print(); Fraction f5 = 3 * f2; f5.print(); Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4); f6.print(); } |
Должен выдавать следующий результат:
3/4
2/7
6/28
9/4
6/7
6/24
Ответ b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#include <iostream> class Fraction { private: int m_numerator; int m_denominator; public: Fraction(int numerator=0, int denominator=1): m_numerator(numerator), m_denominator(denominator) { } friend Fraction operator*(const Fraction &f1, const Fraction &f2); friend Fraction operator*(const Fraction &f1, int value); friend Fraction operator*(int value, const Fraction &f1); void print() { std::cout << m_numerator << "/" << m_denominator << "\n"; } }; Fraction operator*(const Fraction &f1, const Fraction &f2) { return Fraction(f1.m_numerator * f2.m_numerator, f1.m_denominator * f2.m_denominator); } Fraction operator*(const Fraction &f1, int value) { return Fraction(f1.m_numerator * value, f1.m_denominator); } Fraction operator*(int value, const Fraction &f1) { return Fraction(f1.m_numerator * value, f1.m_denominator); } int main() { Fraction f1(3, 4); f1.print(); Fraction f2(2, 7); f2.print(); Fraction f3 = f1 * f2; f3.print(); Fraction f4 = f1 * 3; f4.print(); Fraction f5 = 3 * f2; f5.print(); Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4); f6.print(); return 0; } |
Дополнительное задание
c) Дробь 2/4 — это та же дробь, что и 1/2, только 1/2 не делится до минимальных неделимых значений. Мы можем уменьшить любую заданную дробь до наименьших значений, найдя наибольший общий делитель (НОД) для числителя и знаменателя, а затем выполнить деление как числителя, так и знаменателя на НОД.
Ниже приведена функция поиска НОД:
1 2 3 |
int nod(int a, int b) { return (b == 0) ? (a > 0 ? a : -a) : nod(b, a % b); } |
Добавьте эту функцию в ваш класс и реализуйте метод reduce(), который будет уменьшать дробь. Убедитесь, что дробь будет максимально и корректно уменьшена.
Следующий фрагмент кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> int main() { Fraction f1(3, 4); f1.print(); Fraction f2(2, 7); f2.print(); Fraction f3 = f1 * f2; f3.print(); Fraction f4 = f1 * 3; f4.print(); Fraction f5 = 3 * f2; f5.print(); Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4); f6.print(); return 0; } |
Должен выдавать следующий результат:
3/4
2/7
3/14
9/4
6/7
1/4
Ответ c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#include <iostream> class Fraction { private: int m_numerator; int m_denominator; public: Fraction(int numerator=0, int denominator=1): m_numerator(numerator), m_denominator(denominator) { // Мы поместили метод reduce() в конструктор, чтобы убедиться, что все дроби, которые у нас есть, будут уменьшены! // Поскольку выполнение всех перегруженных операторов осуществляется вместе с созданием новых объектов класса Fraction, то мы можем гарантировать, что эта функция вызовется для каждой дроби reduce(); } // Делаем функцию nod() статической, чтобы она могла быть частью класса Fraction и, при этом, для её использования нам не нужно было бы создавать объект класса Fraction static int nod(int a, int b) { return (b == 0) ? (a > 0 ? a : -a) : nod(b, a % b); } void reduce() { int nod = Fraction::nod(m_numerator, m_denominator); m_numerator /= nod; m_denominator /= nod; } friend Fraction operator*(const Fraction &f1, const Fraction &f2); friend Fraction operator*(const Fraction &f1, int value); friend Fraction operator*(int value, const Fraction &f1); void print() { std::cout << m_numerator << "/" << m_denominator << "\n"; } }; Fraction operator*(const Fraction &f1, const Fraction &f2) { return Fraction(f1.m_numerator * f2.m_numerator, f1.m_denominator * f2.m_denominator); } Fraction operator*(const Fraction &f1, int value) { return Fraction(f1.m_numerator * value, f1.m_denominator); } Fraction operator*(int value, const Fraction &f1) { return Fraction(f1.m_numerator * value, f1.m_denominator); } int main() { Fraction f1(3, 4); f1.print(); Fraction f2(2, 7); f2.print(); Fraction f3 = f1 * f2; f3.print(); Fraction f4 = f1 * 3; f4.print(); Fraction f5 = 3 * f2; f5.print(); Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4); f6.print(); return 0; } |
А почему методы nod и reduce здесь public ? Ведь при создании объекта Fraction конструктор обязательно вызовет reduce(), а тот в свою очередь nod(int a, int b). Оба метода должны быть private (проверил — работает)
А почему они должны быть private?
reduce логично чтобы был приватным. А nod скорее публичный.
Первый пример очень неудачный. Ваш класс простая обертка над простым типом.
Код работает И при:
1) Когда функция объявлена как: Dollars operator+(const Dollars d1, const Dollars d2)
2) Когда она возвращает return d1.m_dollars + d2.m_dollars;
Надо бы объяснить почему. Это важные моменты
Вы бы добавили второе поле: центов. Тогда картина была бы другая.
сделал перегрузагрузку таким образом. Насколько это может повлиять на работу программы?
Никак не повлияет.
Мне кажется делать статической ее абсолютно не обязательно, в классе же нет статических полей… Просто когда вы делаете это выражение m_numerator /= nod компилятор не может понять, что вы ему хотите подсунуть, переменную nod или указатель на функцию nod. Для выхода из этой ситуации можно либо использовать указатель this->nod(m_numerator, m_denominator), либо просто изменить имя переменной. Считаю что использование static только запутывает код.
Привет. такой глупый, на первый взгляд, вопрос ) В своем варианте выполнения задания писал все без "const" и не работало. Посмотрел на твой вариант кода добавил "const" и все начало работать. Подскажи , пожалуйста, почему так происходит.
Заранее спасибо.
Тоже самое у меня вышло, хотелось бы узнать ответ
Потому что Fraction(1, 2), Fraction(2, 3), Fraction(3, 4) являются анонимными объектами(урок 127) и, соответственно, r-values, а неконстантные ссылки с r-values не работают.
Нигде нет ни слова о том как сделать перегруженную функцию без использования дополнительного операнда. Причём вообще в инете не нашёл. Т. е.:
А как сделать:
Наоборот пожалуйста:
Все эти operator<< >> реально вообще не тарахтели, только для примерчиков на Ыкранчике )).
Неужели никак нельзя?
Аха-ха. Сам и нашёл. Как я мог это пропустить.
operator str() const { return get() } // Например
Теперь всё работает как надо.
К стати да, перегрузками сильно увлекаться не надо.
Стоит сделать:
Со строками: =/+=/+/+/+/<</operator string().
С числами: =/+=/-=/++/—/++/—/operator int().
Кто в теме 😉
Всё! Больше шаманить и не стоит. Наигрался вдоволь. Если нужно больше возможностей работы с типом, то string(Var), int(Var) всё решает. У меня например класс свой тип переменных который работает с объектами другого класса, в котором есть массив. Так вот везде где работает сеттер нужна перегрузка ну и отдавать без явного приведения по минимуму для простоты неплохо бы. Классно, работаешь с переменными как бы, а они там кучу работы делают невидимо. Главное C++ настолько шустрый, что ты этого даже не можешь заметить, что он массив несколько раз перешерстил пошурику. Особенно если настройки на скорость в компиляторе стоят, а не на минимальный размер.
Кому интересно, стоит такое почитать: https://habr.com/ru/post/164193/
Особенно кому надо работать с UTF и мультибайтом. К этому же:
А вот расшаривать всю std не стоит.
Всем удачи!
Для вашего примера нужна не перегрузка операторов.
Здесь необходимо определение для вашего класса, явного или неявного приведения типов.
Приближенный пример для понимания:
Когда вы делаете
double a(15);
int b(8);
a+b; или a=b;
То срабатывает неявное приведение типов, а именно b(int) конвертируется в b(double). Можно сделать явное приведение типов:
a+static_cast<double>(b); или a=static_cast<double>(b);
Гуглите в этом направлении.
Объясните пожалуйста, что Вы имели ввиду. Я половины не понял. Что значит: "Все эти operator<< >> реально вообще не тарахтели, только для примерчиков на Ыкранчике ))." или "operator str() const { return get() } // Например".
У меня мозг поплыл.
Мой вариант задания
Операцию деления числителя и знаменателя дроби на одно и то же число, корректней называть сокращением дроби, а не уменьшением. По крайнем мере, во всех учебниках по математике она называется именно так.
Ребят,тут такая тема:
этот код прекрасно работает,хоть я и перегрузил только
а в нём так же встречается и
С++ 17,MinGW,может не нужно делать 2 версии перегрузки????
Расскажите,почему,вопреки данному уроку этот код пашет…
Присоединяюсь к данному вопросу, потому что столкнулась с этим же) Только на VS2019. Гугл не хочет помогать, так как не могу сформулировать верно, в чём суть
У Вас всегда будет выполняться:
или
Потому что первый параметр у вас всегда будет типа Number&
1) Number operator+(const Number &num1, const Number &num2)
2) Number operator+(const Number &num1, int value)
Number num = n1+ n2; — работает перегрузка 1
Number num = n1+ 3; — работает перегрузка 2
Number num = 3 + n2; — работает перегрузка 1,
так как компилятор неявно кастит int (в нашем случае 3) к Number. Происходит это потоиму что у Number есть конструктор, который принимает в качестве параметра int. Как я понял ваш вопрос был именно в этом, но, как писал Денис, у вас в коде нет ничего чтобы вызвало эту перегрузку, даже если бы она была:
Не совсем понятно, зачем дополнительно городить reduce(), если можно все сделать в конструкторе?:
А также учитывая множественные замечания в предыдущих уроках:
метод static int nod(int a, int b)
должен быть закрытым, т.е. внутри private:.
Не совсем понятно почему ф-ция nod — static?Кто-то может обьяснить?
Она принадлежит классу, а не объекту, т.к. применяется одинаково для всех объектов и нет смысла ее инициализировать для каждого объекта.
функции и так не определяются для каждого объекта… хоть статик, хоть не статик — все равно в итоговом коде описана единожды
В данном случае скорее всего функция сделана статической, чтобы ей можно было пользоваться без привязки к конкретному объекту… полезная ж функция 🙂
ЗЫ имя функции nod? что за детский сад? Учимся писать нормальный код и пишем названия функций транслитом… почему нельзя погуглить, как на английском это понятие записывается? И выбрать один из вариантов: gcd (лучше) или hcf…
Метод nod статический ,чо бы мы могли вызвать напрямую сам метод,не создавая объекты класса,вот надо нам,например,найти наибольший общий делитель чисел 725 и 125,то мы можем вызвать:
а если бы nod не был статическим,нам бы пришлось создать объект Fraction для вызова nod:
Я тоже не понял зачем ее делать статической если она не работает со статическими полями класса… Но заметил что в данном виде (как в ответе) компилятор ругается, но если сделать имя переменной не int nod, или имя самой функции другое, то все работает нормально. Т.е. компилятор вероятно путает ее с переменной. Вобщем мне кажется замута с присвоением фунции nod() параметра static совершенно ненужная. Эту функцию также можно отправить в private секцию.
Доброго времени суток.У меня вопрос.Не пойму почему без const не компилируется.А с const всё в порядке?Где почитать, подскажите пожалуста.
В данном операторе //Dollars d1 = Dollars(5) + 5;//
Dollars(5) является анонимным объектом и, соответственно, r-значением (см. Анонимные объекты в C++).
Его можно передать только по значению или константной ссылке, так как он имеет область видимости и время жизни выражения и сразу после вычисления вышеупомянутого выражения будет удалён. Передавая функции operator+ данный анонимный объект по не константной ссылке, вы, в конечном счёте, будете ссылаться на несуществующие данные.
Константные ссылки продлевают жизнь r-значений до времени жизни соответствующей ссылки.
У меня появился вопрос к этому:
"Например, Dollars(5) + 5 приведет к вызову operator+(Dollars, int), а 5 + Dollars(5) приведет к вызову operator+(int, Dollars). Следовательно, всякий раз, при перегрузке бинарных операторов для работы с операндами разных типов, нужно писать две функции — по одной на каждый случай. "
Если у нас есть дружественная функция
То и перегружать бинарную операцию не придётся, так как в данном случае будут происходить неявные преобразования типов.
Пример:
Это сработает,пока у тебя один член-значение,в данном случае второй член инициализируется нулём,поэтому при создании дроби из числа,получается такое — же число,если в конструкторе вместо:
написать:
результат перестанет быть правильным,
всегда нужно продумывать все варианты при использовании неявных преобразований…
Можно объяснение почему здесь Values operator+(int value, const Values &v) объект должен быть обязательно константным, иначе — компилятор ругается, что отсутствует оператор "+", который соответствует этим типам?
Values operator+(Values &v1, Values &v2) — здесь и без констант все работает!
Ваше решение, конечно, более правильное — reduce() один раз поместили в конструктор и все.
Я немного по-другому реализовала:
Хороший вариант, тоже к нему пришел.
Странно, что никто не отреагировал.
Отнюдь не хороший вариант. Каждая операция умножения переменных класса Fraction будет генерировать лишний анонимный объект. Мало того, что в процессе умножения создается один новый объект в результате return Fraction(f1.m_numenator*f2.m_numenator, f1.m_denominator*f2.m_denominator), так и еще в довесок вызовом .reduce() сгенерим еще один.
Это легко проверяется отладочной печатью в конструкторе/деструкторе.
"Мало того, что в процессе умножения создается один новый объект в результате return"
Так лишний объект не создастся:
А так создается:
Это оптимизации компилятора.
Как это работает? Минут 15 сидел, так и не понял (про рекурсивную функцию знаю). Я реализовал это немного другим образом:
Здесь используется условный тернарный оператор ?:
Чтобы понять как работает строчка:
предлагаю запустить следующий код:
Если не понятна именно математическая часть, то советую почитать про алгоритм евклида, ровно это и есть он
Ненавижу тернарные операторы, так что раскрутил это в более читаемый вид:
Если пройтись по функции с брейкпоинтами и дебуггером, становится более ли менее понятно, как она работает.