На этом шаге мы рассмотрим создание пользовательских операторов вывода.
Как упоминалось ранее, главным преимуществом потокового ввода-вывода перед средствами ввода-вывода языка С является возможность расширения потокового механизма для пользовательских типов. Расширение основано на перегрузке операторов << и >>. Далее рассматривается пример использования потоков данных для вывода правильных дробей.
В выражении с оператором вывода << левый операнд определяет поток данных, а правый - объект, записываемый в этот поток:
поток << объект
В соответствии с правилами языка эта конструкция может интерпретироваться двумя способами:
поток.operator<<(объект) поток.operator<<(поток, объект)
Первая интерпретация используется для встроенных типов. Для пользовательских типов должна использоваться вторая интерпретация, поскольку потоковые классы закрыты для расширения. Все, что требуется, - реализовать глобальный оператор << для пользовательских типов. Задача решается относительно просто, если при этом не нужен доступ к закрытым членам объекта (но об этом позже).
Например, для вывода объекта класса Fraction в формате числитель/знаменатель можно воспользоваться следующей функцией:
#include <iostream> inline std::ostream& operator << (std::ostream& strm, const Fraction& f) { strm << f.numerator() << '/' << f.denominator(); return strm; }
Функция выводит числитель и знаменатель, разделенные символом /, в поток данных, передаваемый в аргументе, - файловый, строковый или еще какой-либо. Для поддержки цепочечных операций вывода, а также для совмещения вывода с проверкой состояния потока данных функция возвращает ссылку на поток.
У этой простой формы есть два основных недостатка.
Fraction vat(16,100); // В Германии действует единая ставка НДС=16%
std::cout << "VAT: \" " << std::left << std::setw(8)
<< vat << "\"" << std::endl;
Эта программа выведет следующий результат:
VAT: "16 /100"
В следующей версии решены обе проблемы:
#include <iostream> #include <sstream> template <class charT, class traits> inline std::basic_ostream<charT,traits>& operator << (std::basic_ostream<charT,traits>& strm, const Fraction& f) { // Строковый поток // - с тем же форматом // - без специальной ширины поля std::basic_ostringstream<charT,traits> s; s.copyfmt(strm); s.width(0); // Заполнение строкового потока s << f.numerator() << '/' << f.denominator(); // print string stream strm << s.str(); return strm; }
Оператор превратился в шаблон функции, параметризованный для всех разновидностей потоков данных. Проблема с шириной поля решается записью в строковый поток данных без указания конкретной ширины. Сконструированная строка затем передается в поток данных, переданный в аргументе. В результате символьное представление дроби выводится одной операцией записи, к которой применяется ширина поля. Например, рассмотрим такой фрагмент:
Fraction vat(16,100); // В Германии действует единая ставка НДС=16%
std::cout << "VAT: \"" << std::left << std::setw(8)
<< VAT << "\"" << std::endl;
Этот фрагмент выведет следующий результат:
VAT: "16/100 "
На следующем шаге мы рассмотрим реализацию операторов ввода.