📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 69 70 71 72 73 74 75 76 77 ... 121
Перейти на страницу:
можем указать отбросить пробел с запятой после последнего числа, поскольку итератор не знает, что достиг конца строки, до тех пор, пока это не случится.

  ostream_iterator<int> oit_comma {cout, ", "};

  for (int i : v) { *oit_comma = i; }

  cout << 'n';

10. Присвоение элементов итератору потока вывода для вывода их на экран — один из корректных способов использования итератора, но его создавали не для этого. Идея заключается в том, чтобы применить их вместе с алгоритмами. Самым простым алгоритмом является std::copy. Можно предоставить начальный и конечный итераторы вектора в качестве входного диапазона данных, а также итератор вывода потока в качестве итератора вывода. Алгоритм выведет все числа, содержащиеся в векторе. Сделаем это для обоих итераторов вывода, а затем сравним полученный результат с результатом работы циклов, написанных нами ранее:

  copy(begin(v), end(v), oit);

  cout << 'n';

  copy(begin(v), end(v), oit_comma);

  cout << 'n';

11. Помните функцию word_num, которая соотносит числа и строки — 1 с "one ", 2 с "two" и т.д.? Можно использовать для вывода данных и ее. Нужен только оператор вывода потока, имеющий шаблон, специализированный для типа string, поскольку мы больше не выводим целые числа. Вместо алгоритма std::copy станем использовать алгоритм std::transform, поскольку он позволяет применять функцию преобразования для каждого элемента входного диапазона до того, как эти элементы будут скопированы в выходной диапазон.

  transform(begin(v), end(v),

            ostream_iterator<string>{cout, " "},

            word_num);

  cout << 'n';

12. В последней строке выходных данных наконец используем структуру bork. Мы могли бы передать алгоритму std::transform функцию преобразования, но не стали. Вместо этого можно просто создать итератор потока вывода, который специализирован для типа bork в вызове std::copy. В результате экземпляры типа bork будут неявно создаваться на основе целых чисел, находящихся в диапазоне данных. Это даст интересные выходные данные:

  copy(begin(v), end(v),

       ostream_iterator<bork>{cout, "n"});

}

13. Компиляция и запуск программы дадут следующий результат. Первые две строки полностью идентичны следующим двум строкам, как мы и ожидали.

Далее мы получили «аккуратные» строки, содержащие текстовое написание чисел, разделенное пробелами, а после этого — множество строк bork!, для них был использован разделитель "n" вместо пробелов.

$ ./ostream_printing 12345

1, 2, 3, 4, 5,

12345

1, 2, 3, 4, 5,

one two three four five bork!

bork! bork!

bork! bork! bork!

bork! bork! bork! bork!

bork! bork! bork! bork! bork!

Как это работает

Мы увидели, что итератор std::ostream_iterator — всего лишь оболочка для функции вывода данных на экран, имеющая форму и синтаксис итератора. Инкрементирование этого итератора ни к чему не приводит. Его разыменование возвращает лишь прокси-объект, чей оператор присваивания перенаправляет аргумент в поток вывода.

Итераторы потока вывода, специализированные для типа T (например, ostream_ iterator<T>), работают для всех типов, для которых предоставлена реализация ostream& operator<<(ostream&, const T&).

Итератор ostream_iterator всегда пытается вызвать оператор << того типа, для которого он был специализирован, с помощью своего параметра шаблона. Он попробует неявно преобразовать типы, если это разрешено. Когда мы итерируем по диапазону элементов типа A, но копируем данные элементы в экземпляры типа output_iterator<B>, то код будет работать при условии, что тип A можно неявно преобразовать к типу B. Мы сделали именно это для структуры bork: экземпляр типа bork можно неявно получить из целочисленного значения. Как следствие, можно легко сгенерировать множество строк "bork! " на консоли пользователя.

Если неявное преобразование выполнить нельзя, можно провести его самостоятельно с помощью алгоритма std::transform, что мы и сделали в комбинации с функцией word_num.

 

 Обратите внимание: как правило, разрешение неявного преобразования для пользовательских типов — плохой стиль программирования, поскольку такие преобразования являются распространенным источником ошибок, которые очень трудно найти. В нашем примере неявный конструктор скорее полезен, чем опасен, поскольку класс применяется только для вывода данных на экран.

Перенаправляем выходные данные в файл для конкретных разделов кода

Поток std::cout предоставляет удобный способ вывести на экран все, что мы хотим и когда хотим, поскольку его действительно просто использовать, легко расширять и получать к нему доступ глобально. Даже если мы желаем выводить особые сообщения, например сообщения об ошибках, которые нужно изолировать от обычных сообщений, то можем просто задействовать поток std::cerr. Он похож на cout, но выводит данные в стандартный канал ошибок, а не в стандартный канал для выходных данных.

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

Перенаправить выходные данные объектов потока можно. Далее мы рассмотрим, как это сделать очень простым и элегантным способом.

Как это делается

В этом примере мы реализуем вспомогательный класс, который решает задачу перенаправления потока и отмены такого перенаправления средствами конструкторов/деструкторов. А затем увидим, как это можно использовать.

1. В этот раз нужны только заголовочные файлы для потоков ввода/вывода и файлового потока. Кроме того, мы объявим об использовании пространства имен std по умолчанию:

#include <iostream>

#include <fstream>

using namespace std;

2. Реализуем класс, содержащий объект файлового потока и указатель на буфер потока. cout, представленный как объект потока, имеет внутренний буфер, который можно подменить. В процессе выполнения подобной подмены можно сохранить его исходное состояние, чтобы в будущем иметь возможность отменить это изменение. Мы могли бы найти его тип в справочном материале по С++, но также можем использовать decltype, чтобы узнать, какой тип возвращает конструкция cout.rdbuf(). Данный прием подходит не для всех ситуаций, но в нашем случае мы получим тип указателя:

class redirect_cout_region

{

  using buftype = decltype(cout.rdbuf());

  ofstream ofs;

  buftype buf_backup;

3. Конструктор нашего класса принимает в

1 ... 69 70 71 72 73 74 75 76 77 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?