Операторы ввода-вывода для пользовательских типов. Реализация операторов вывода

   
На этом шаге мы рассмотрим создание пользовательских операторов вывода.

   
Как упоминалось ранее, главным преимуществом потокового ввода-вывода перед средствами ввода-вывода языка С является возможность
расширения потокового механизма для пользовательских типов. Расширение основано на перегрузке операторов << и >>. Далее
рассматривается пример использования потоков данных для вывода правильных дробей.
Реализация операторов вывода

   
В выражении с оператором вывода << левый операнд определяет поток данных, а правый - объект, записываемый в этот поток:

  поток << объект

   
В соответствии с правилами языка эта конструкция может интерпретироваться двумя способами:

  поток.operator<<(объект) 
  поток.operator<<(поток, объект)

   
Первая интерпретация используется для встроенных типов. Для пользовательских типов должна использоваться вторая интерпретация, поскольку
потоковые классы закрыты для расширения. Все, что требуется, - реализовать глобальный оператор << для пользовательских типов. Задача
решается относительно просто, если при этом не нужен доступ к закрытым членам объекта (но об этом позже).

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

#include <iostream>
inline
std::ostream& operator << (std::ostream& strm, const Fraction& f)
{
  strm << f.numerator() << '/' << f.denominator();
  return strm; 
}

   
Функция выводит числитель и знаменатель, разделенные символом /, в поток данных, передаваемый в аргументе, - файловый, строковый или еще
какой-либо. Для поддержки цепочечных операций вывода, а также для совмещения вывода с проверкой состояния потока данных функция возвращает
ссылку на поток.

   
У этой простой формы есть два основных недостатка.

  • Из-за использования в сигнатуре класса ostream функция применима только к потокам данных с типом символов char. Если
    функция предназначается только для Европы и Америки, проблем не будет. С другой стороны, построить более универсальную версию совсем
    несложно, поэтому следует по крайней мере рассмотреть такую возможность.
  • Другая проблема возникает при задании ширины поля. В данном случае результат окажется не тем, который можно было бы ожидать. Ширина
    поля будет относиться только к ближайшей операции вывода, то есть в данном случае - к выводу числителя. Пример:

    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  "

   
На следующем шаге мы рассмотрим реализацию операторов ввода.



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

Оставить комментарий