Шаг 483.
Библиотека STL. Ввод-вывод с использованием потоковых классов. Стандартные функции ввода-вывода. Принципы работы манипуляторов

    На этом шаге мы рассмотрим общие принципы работы манипуляторов.

    Реализация манипуляторов основана на очень простом приеме, который не только упрощает управление потоками данных, но и наглядно демонстрирует мощь механизма перегрузки функций. Манипуляторы представляют собой обычные функции, передаваемые операторам ввода-вывода в аргументах. Оператор вызывает переданную функцию. Например, оператор вывода класса ostream перегружается примерно так:

  ostream& ostream::operator << (ostream (*op)(ostream&))
  {
    // Вызов функцию, передаваемой в параметре, с аргументом-потоком 
    return (*op)(*this); 
  }

    Аргумент op представляет собой указатель на функцию. Точнее говоря, это функция, которая получает и возвращает поток данных ostream (предполагается, что возвращается тот же объект ostream, который был получен при вызове). Если второй операнд оператора << является такой функцией, то при ее вызове в аргументе передается первый операнд оператора <<.

    На первый взгляд описание кажется очень сложным, но в действительности все относительно просто. Следующий пример поможет лучше разобраться в происходящем. Манипулятор (то есть функция) endl() для объекта ostream реализуется примерно так:

  std::ostream& std::endl (std::ostream& strm)
  {
    // Запись признака конца строки 
    strm.put('\n');
    // Принудительный вывод выходного буфера 
    strm.flush();
    // Возвращение strm для организации цепочечных вызовов 
    return strm; 
  }

    Манипулятор используется в выражениях вида

  std::cout << std::endl;

    Для потока данных cout вызывается оператор <<, которому во втором операнде передается функция endl(). Реализация оператора << преобразует этот вызов в вызов переданной функции, которой в качестве аргумента передается объект потока данных:

  std::endl (std::cout)

    Чтобы добиться эффекта "вывода" манипулятора, можно просто использовать это выражение. Более того, у функциональной записи есть свои преимущества - она не требует указания пространства имен:

  endl(std::cout)

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

    Поскольку потоковые классы оформлены в виде шаблонов, параметризованных по типу символов, настоящая реализация endl() выглядит примерно так:

  tempiate<class charT, class traits>
  std::basic_ostream<charT,traits>&
  std::endl (std::basic_ostream<charT,traits>& strm)
  {
    strm.put(strm.widen('\n'));
    strm.flush();
    return strm; 
  }

    Функция widen() преобразует символ новой строки к кодировке, используемой в потоке данных.

    В стандартную библиотеку C++ также включены параметризованные манипуляторы (то есть манипуляторы, которым при вызове передаются аргументы). Принципы работы этих манипуляторов зависят от реализации; не существует стандартных правил определения пользовательских параметризованных манипуляторов.

    Стандартные параметризованные манипуляторы определяются в заголовоч-ном файле <iomanip>. Если вы собираетесь использовать их, в программу необходимо включить соответствующий файл:

  #include <iomanip>

    Все стандартные параметризованные манипуляторы связаны с форматированием данных, поэтому они будут рассматриваться далее при описании средств форматирования.

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




Предыдущий шаг Содержание Следующий шаг