C++17 STL Стандартная библиотека шаблонов — страница 62 из 119

□ удобный и красивый динамический вывод чисел на экран в зависимости от контекста;

□ перехват читабельных исключений для ошибок потока

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
можно поместить любую переменную в объект потока, который ведет себя точно так же, как и