□ удобный и красивый динамический вывод чисел на экран в зависимости от контекста;
□ перехват читабельных исключений для ошибок потока
std::iostream
. Введение
Данная глава посвящена обработке строк, анализу и выводу на экран произвольных данных. Для этих задач STL предоставляет потоковую библиотеку для работы с вводом-выводом. Библиотека состоит из следующих классов, показанных в серых прямоугольниках (рис. 7.1).
Стрелки показывают схему наследования классов. Рисунок на первый взгляд может показаться непонятным, но в рамках главы мы рассмотрим все классы и познакомимся с ними по очереди. Если мы попробуем обратиться к документации С++ в STL, то не найдем упомянутые классы по конкретно этим именам. Причина такова: названия, приведенные на рис. 7.1, мы видим только как программисты приложений. Но они, по сути, являются просто ключевыми словами для классов с префиксом имени класса
basic_
(например, в документации STL проще найти класс basic_istream
, в отличие от istream
). Классы для работы с потоками, которые начинаются с префикса basic_*
, являются шаблонными, они могут быть специализированы для разных типов символов. Классы, показанные на рис. 7.1, специализированы для значений типа char
. В книге мы будем применять именно эти специализации. Если мы воспользуемся префиксом w
для упомянутых имен класса, то получим названия wistream
, wostream
и т.д. — они являются специализациями для типа wchar_t
вместо char
.В верхней части рис. 7.1 мы видим класс
std::ios_base
. Мы практически никогда не будем использовать непосредственно его; он приведен для полноты картины, поскольку другие классы наследуют от него. Следующая специализация — это std::ios
, она воплощает идею объекта, сопровождающего поток данных, который может находиться в исправном состоянии, в состоянии, когда закончились данные (empty of data, EOF) или в другом ошибочном состоянии.Первыми специализациями, которые мы применим на самом деле, являются
std::istream
и std::ostream
. Префиксы "i"
и "o"
расшифровываются как input
и output
(«ввод» и «вывод»). Мы уже видели такие префиксы на раннем этапе программирования на С++ в простейших примерах в объектах std::cout
и std::cin
(а также std::cerr
). Экземпляры этих классов всегда доступны глобально. Мы выполняем вывод данных с помощью ostream
, а ввод — с использованием input
.Класс, который наследует от классов
istream
и ostream
, называется iostream
. С его помощью можно выполнять как ввод, так и вывод данных. Зная, как использовать все классы из трио istream
, ostream
и iostream
, вы сможете незамедлительно применить следующие классы:□ классы
ifstream
, ofstream
и fstream
наследуют от классов istream
, ostream
и iostream
соответственно, но задействуют их возможности, чтобы перенаправить ввод/вывод в файлы или из них из файловой системы компьютера;□ классы
istringstream
, ostringstream
и iostringstream
работают по схожему принципу. Они помогают создавать строки в памяти, а затем помещают туда данные или считывают их. Создание, конкатенация и преобразование строк
Даже те, кто довольно давно пользовался языком C++ , знают о классе
std::string
. Хотя в языке C обработка строк довольно утомительна, особенно при анализе, конкатенации и копировании и т.д., класс std::string
— это реальный шаг вперед к простоте и безопасности.Благодаря выходу C++11 теперь даже не нужно копировать строки, когда мы хотим передать право собственности какой-то другой функции или структуре данных, поскольку можем перемещать их. Таким образом, в подобных случаях не возникает больших издержек.
По мере выхода новых стандартов класс
std::string
получил несколько новых свойств. Совершенно новой особенностью С++17 является std::string_view
. Мы немного поработаем с ней (но впереди будет и другой пример, в котором более подробно рассматривается std::string_view
), чтобы понять, как взаимодействовать с подобным инструментарием в эпоху С++17.
Как это делается
В этом примере мы создадим строки и строковые представления, а затем выполним простые операции конкатенации и преобразования.
1. Как и обычно, сначала включим заголовочные файлы и объявим об использовании пространства имен
std
:
#include
#include
#include
#include
#include
using namespace std;
2. Сначала создадим объекты строк. Самым очевидным способом является инстанцирование объекта класса
string
. Мы контролируем его содержимое, передавая конструктору строку в стиле C (которая после компиляции будет встроена в бинарный файл как статический массив, содержащий символы). Конструктор скопирует ее и сделает содержимым объекта строки a
. Помимо этого, вместо инициализации строки с помощью строки в стиле C можно применить оператор строкового литерала ""s
. Он создает объект строк динамически. Мы используем его для создания объекта b
, что позволит применять автоматическое выведение типа.
int main()
{
string a { "a" };
auto b ( "b"s );
3. Строки, которые мы только что создали, копируют их входные данные из аргумента конструктора в собственный буфер. Чтобы не копировать строку, а просто сослаться на нее, можно воспользоваться объектами типа
string_ view
. Этот класс также имеет оператор литерала, он вызывается с помощью конструкции ""sv
:
string_view c { "c" };
auto d ( "d"sv );
4. Теперь поработаем с нашими строками и строковыми представлениями. Для обоих типов существует перегруженная версия оператора
<<
для класса std::ostream
, поэтому их удобно выводить на экран:
cout << a << ", " << b << '\n';
cout << c << ", " << d << '\n';
5. В классе
string
имеется перегруженная версия оператора +
, поэтому можно сложить две строки и получить в качестве результата их конкатенацию. Таким образом, выражение "a" + "b"
даст результат "ab"
. Конкатенация строк a
и b
с помощью данного способа выполняется довольно легко. При необходимости сложить a
и c
могут возникнуть некоторые трудности, поскольку c
— не строка, а экземпляр класса string_view
. Сначала нужно получить строку из c
, это делается путем создания новой строки из c
и сложения ее с a
. Кто-то может задаться вопросом: «Погодите, зачем копировать с в промежуточную строку только для того, чтобы сложить ее с а
? Можно ли этого избежать, использовав конструкцию c.data()
?» Идея хороша, но имеет недостаток: экземпляры класса string_view
не обязаны содержать строки, завершающиеся нулем. Данная проблема может привести к переполнению буфера.
cout << a + b << '\n';
cout << a + string{c} << '\n';
6. Создадим новую строку, содержащую все введенные нами строки и строковые представления. С помощью
std::ostringstream
можно поместить любую переменную в объект потока, который ведет себя точно так же, как и