Урок №133. Перегрузка операторов ввода и вывода

  Юрий  | 

  |

  Обновл. 25 Сен 2021  | 

 126186

 ǀ   25 

На этом уроке мы рассмотрим перегрузку операторов ввода и вывода в языке C++.

Перегрузка оператора вывода <<

Для классов с множеством переменных-членов, выводить в консоль каждую переменную по отдельности может быть несколько утомительно. Например, рассмотрим следующий класс:

Если вы захотите вывести объект этого класса на экран, то вам нужно будет сделать что-то вроде следующего:

Конечно, было бы проще написать отдельную функцию для вывода, которую можно было бы повторно использовать. Например, функцию print():

Теперь уже намного лучше, но здесь также есть свои нюансы. Поскольку метод print() имеет тип void, то его нельзя вызывать в середине стейтмента вывода. Вместо этого стейтмент вывода придется разбить на несколько частей (строк):

А вот если бы мы могли просто написать:

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

Перегрузка оператора вывода << аналогична перегрузке оператора + (оба являются бинарными операторами), за исключением того, что их типы различны.

Рассмотрим выражение std::cout << point. Если оператором является <<, то чем тогда являются операнды? Левым операндом является объект std::cout, а правым — объект нашего класса Point. std::cout фактически является объектом типа std::ostream, поэтому перегрузка оператора << выглядит следующим образом:

Реализация перегрузки оператора << для нашего класса Point довольно-таки проста, так как C++ уже знает, как выводить значения типа double, а все наши переменные-члены имеют тип double, поэтому мы можем просто использовать оператор << для вывода переменных-членов нашего Point. Вот класс Point, приведенный выше, но уже с перегруженным оператором <<:

Всё довольно просто. Обратите внимание, насколько проще стал стейтмент вывода по сравнению с другими стейментами из вышеприведенных примеров. Наиболее заметным отличием является то, что std::cout стал параметром out в нашей функции перегрузки (который затем станет ссылкой на std::cout при вызове этого оператора).

Самое интересное здесь — тип возврата. С перегрузкой арифметических операторов мы вычисляли и возвращали результат по значению. Однако, если вы попытаетесь возвратить std::ostream по значению, то получите ошибку компилятора. Это случится из-за того, что std::ostream запрещает свое копирование.

В этом случае мы возвращаем левый параметр в качестве ссылки. Это не только предотвращает создание копии std::ostream, но также позволяет нам «связать» стейтменты вывода вместе, например, std::cout << point << std::endl;.

Вы могли бы подумать, что, поскольку оператор << не возвращает значение обратно в caller, то мы должны были бы указать тип возврата void. Но подумайте, что произойдет, если наш оператор << будет возвращать void. Когда компилятор обрабатывает std::cout << point << std::endl;, то, учитывая правила приоритета/ассоциативности, он будет обрабатывать это выражение как (std::cout << point) << std::endl;. Тогда std::cout << point приведет к вызову функции перегрузки оператора <<, которая возвратит void, и вторая часть выражения будет обрабатываться как void << std::endl; — в этом нет смысла!

Возвращая параметр out в качестве возвращаемого значения выражения (std::cout << point) мы возвращаем std::cout, и вторая часть нашего выражения обрабатывается как std::cout << std::endl; — вот где сила!

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

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

Point(3, 4.7, 5) Point(9, 10.5, 11)

Перегрузка оператора ввода >>


Также можно перегрузить и оператор ввода. Всё почти так же, как и с оператором вывода, но главное, что нужно помнить — std::cin является объектом типа std::istream. Вот наш класс Point с перегруженным оператором ввода >>:

Вот пример программы с использованием как перегруженного оператора <<, так и оператора >>:

Предположим, что пользователь введет 4.0, 5.5 и 8.37, тогда результат выполнения программы:

You entered: Point(4, 5.5, 8.37)

Заключение

Перегрузка операторов << и >> намного упрощает процесс вывода класса на экран и получение пользовательского ввода с записью в класс.

Тест


Используя класс Fraction, представленный ниже, добавьте перегрузку операторов << и >>.

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

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

Enter fraction 1: 3/4
Enter fraction 2: 4/9
3/4 * 4/9 is 1/3

Вот класс Fraction:

Ответ

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (220 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 25

  1. Павел:

    Такое ощущение, что я пропустил урок про операторы ввода и вывода, попробую разобраться:

    std::cout — это объект из библиотеки std::ostream, который используется для вывода символов на консоль. Но чтобы все заработало ему в пару нужен оператор вывода <<, который здесь действует как вызов функции operator<<.

    Оператор << — бинарный. Он берет объекты слева и справа от себя.

    Когда мы пишем

    левая часть (std::cout) принимается в функцию

    как объект out класса std::ostream.

    И возвращаясь

    опять преобразуется в std::cout.

  2. Capy:

    А как сделать, чтобы вывод был именно таким:

    Enter fraction 1: 3/4

    а не таким:

    Enter fraction 1: 3
    /
    4

    ?

  3. Анатолий:

    Не совсем понятно почему при таких параметрах дружественной функции:

    вторая часть выражения обрабатывается как:

  4. Сергей:

    Не понимаю: если, условно говоря, std::cout<<data; — выводит data на консоль, то почему стейтмент из реализации перегрузки out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")"; не выводит инфу на консоль сразу же?

    1. Gremlins:

      Все просто , это ОДИН И ТОТ ЖЕ ОБЬЕКТ , мы его и по ссылке передаем

  5. Sergiy:

    Не совсем понял почему в перегрузку оператора "<<" передается константный обьект "const Fraction &f1", а в перегрузку ">>" — неконстантный? Понятно что это для предотвращения случайного изменения обьекта Fraction. Но почему при удалении const из параметров функции, получаем ошибку в строке:

    в 5-ом операторе "<<" ?

    1. Mimimishkin:

      Думаю ты уже понял, но всё равно напишу:
      Константная ссылка передаётся, чтобы была возможность использовать в качестве аргумента r-value. Поэтому, если убрать const, то "f1 * f2", не сможет быть переданным в качестве аргумента, так как он не сохранён в переменной (является r-value).
      Если же мы добавим const в перегрузку ">>", то не сможем изменить переменные-члены переданного объекта.

  6. Taras:

    Непонимаю что это может быть за прикол.
    Программа компилируется, но потом вываливается ошибка.
    В предыдущих уроках по перезагрузке тоже такое было.

    Process terminated with status -1073741571 (0 minute(s), 5 second(s))

    Код вроде правильный. Ошибка появляется при использовании cin при использовании только cout (cin закомментировать)все работает.

    1. Юрий:

      Конкретно за ошибку не отвечу, но в перегрузке >> объект Fraction не должен быть константным, иначе как поместить в него значения из in?

  7. Ildar_S:

    Можно ли переопределять операторы ввода вывода когда классы созданы в отдельных файлах (.h и .cpp)?
    Как это делается?

  8. Анатолий:

    Осталось непонятным одно… а если написать так:

    то почему-то не рабоатет… Хотя потоку передаются данные они печатаются внутри оператора… Почему-то требует адреc объектаю Если его передать

    то он и будет напечатан. Разумеется это не то что надо!

    С внешним оператором работает… Но почему не работает с внутренним?!

    1. ArtemF:

      Перегрузка через метод класса, где первым неявно передаётся адрес объекта

      1. Grave18:

        Так как левым операндом перегруженного внутри класса "<<" неявно идет ссылка на объект, нужно писать не:

        а

        Это извращение, но работает)

  9. Олег:

    Перегрузка оператора >> работает как положено и без вызова f1.reduce(). Скажите, почему это так?

  10. Woland:

    Где я просмотрел про двоеточие в конструкторе?

    Оно то понятно, что оно делает, но хотелось бы почитать, а не подсекнуть.

    1. Woland:

      Уже нашёл. Это старый метод инициализации. Тогда было можно только так. Причём я его уже и забыл, когда-то знал, но что-то казалось знакомым.

    2. Steindvart:

      Не знаю, актуально ли это для вас сейчас. Но тут недавно добавили урок по этой теме:

      https://ravesli.com/urok-117-spisok-initsializatsii-chlenov-klassa/

  11. rutrud:

    "вот где сила!" С.
    Просто бомбическое объяснение, я долго не мог понять почему при перегрузке обычных операторов возвращается значение а при перегрузке << — ссылка. теперь все стало на свои места))))

  12. Влад:

    Очень помог данный урок,буду смотреть ваши уроки(Большое Спс).

  13. Oleksiy:

    Что-то я не помню, чтобы мы до этого упоминали std::ostream
    Почему в статье пишется так, как будто мы уже имели с ним дело.

    1. kmish:

      Нормально все пишется. std::ostream из стандартной библиотеки. Что еще нужно знать?

    2. Константин:

      Ага было бы не плохо иметь возможность пощупать подобные вещи "за вымя".

    3. Grave18:

      std::ostream — это тип объекта std::cout. Можно посмотреть навяди курсор мышки на cout в IDE 🙂

  14. паха:

    чета ссылка на ответ не работает

    1. Фото аватара Юрий:

      Спасибо, исправил.

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

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