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

C++17 STL Стандартная библиотека шаблонов читать книгу онлайн
С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.
Мы не использовали следующие манипуляторы, поскольку они никак не связаны с форматированием, но для полноты картины рассмотрим и их (табл. 7.2).
Среди перечисленных модификаторов стойкими являются только skipws/noskipws и unitbuf/nounitbuf.
Инициализируем сложные объекты из файла вывода
Считывать отдельные числа и слова довольно просто, поскольку оператор >> имеет перегруженные версии для всех этих типов, а потоки ввода удобным образом отбрасывают все промежуточные пробелы.
Но что, если перед нами более сложная структура и нужно прочесть ее из потока ввода или же требуется считать строки, состоящие более чем из одного слова (по умолчанию они будут разбиты на отдельные слова из-за того, что пробелы опускаются)?
Для любого типа можно предоставить еще одну перегруженную версию оператора потока ввода >>, и сейчас мы увидим воплощение этого на практике.
Как это делается
В этом примере мы определим пользовательскую структуру данных и предоставим возможности для чтения ее объектов из потоков ввода.
1. Включим некоторые заголовочные файлы и объявим об использовании пространства имен std для удобства:
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <iterator>
#include <vector>
using namespace std;
2. В качестве примера сложного объекта определим структуру city. Она будет иметь название, количество населения и географические координаты:
struct city {
string name;
size_t population;
double latitude;
double longitude;
};
3. Чтобы считать объект такой структуры из последовательного потока ввода, следует перегрузить оператор потоковой функции >>. В этом операторе сначала опустим все пробелы, стоящие перед текстом, с помощью ws, поскольку пробелы не должны засорять название города. Затем считаем целую строку текста. Это подразумевает, что входной файл будет включать строку, в которой записано только название города. Затем, после символа новой строки, будет следовать список чисел, разделенный запятой, в котором содержится информация о численности населения, а также географическая широта и долгота:
istream& operator>>(istream &is, city &c)
{
is >> ws; getline(is, c.name);
is >> c.population
>> c.latitude
>> c.longitude; return is;
}
4. В нашей функции main создаем вектор, в котором может содержаться целый диапазон элементов типа city. Заполним его с помощью std::copy. Входными данными для вызова copy является диапазон istream_iterator. Передавая ему тип структуры city в качестве параметра шаблона, мы будем использовать перегруженную функцию >>, реализованную только что:
int main()
{
vector<city> l;
copy(istream_iterator<city>{cin}, {},
back_inserter(l));
5. Чтобы увидеть, прошло ли преобразование правильно, выведем на экран содержимое списка. Форматирование ввода/вывода, left << setw(15) <<, приводит к тому, что название города заполняется пробелами, поэтому выходные данные представлены в приятной и удобочитаемой форме:
for (const auto &[name, pop, lat, lon] : l) {
cout << left << setw(15) << name
<< " population=" << pop
<< " lat=" << lat
<< " lon=" << lon << 'n';
}
}
6. Текстовый файл, из которого наша программа будет считывать данные, выглядит следующим образом. В нем содержится информация о четырех городах: их названия, количество населения и географические координаты:
Braunschweig
250000 52.268874 10.526770
Berlin
4000000 52.520007 13.404954
New York City
8406000 40.712784 -74.005941
Mexico City
8851000 19.432608 -99.133208
7. Компиляция и запуск программы дадут следующий результат, он соответствует нашим ожиданиям. Попробуйте изменить входной файл, добавляя ненужные пробелы перед названием городов, чтобы увидеть, как они будут отфильтрованы:
$ cat cities.txt | ./initialize_complex_objects
Braunschweig population=250000 lat=52.2689 lon=10.5268
Berlin population=4000000 lat=52.52 lon=13.405
New York City population=8406000 lat=40.7128 lon=-74.0059
Mexico City population=8851000 lat=19.4326 lon=-99.1332
Как это работает
Мы снова рассмотрели короткий пример. В нем мы лишь создали новую структуру city, а затем перегрузили оператор >> итератора std::istream для данного типа. Это позволило десериализовать элементы типа city, полученные из стандартного потока ввода, с помощью istream_iterator<city>.
Открытым может оставаться вопрос, связанный с проверкой на ошибки. Поэтому снова рассмотрим реализацию оператора >>:
istream& operator>>(istream &is, city &c)
{
is >> ws;
getline(is, c.name);
is >> c.population >> c.latitude >> c.longitude;
return is;
}
Мы считываем множество разных элементов. Что произойдет, если один из них даст сбой, а следующий за ним — нет? Означает ли это потенциальное считывание всех следующих элементов со «смещением» в потоке токенов? Нет, этого не произойдет. Если хотя бы один элемент потока ввода не сможет быть преобразован, то объект потока ввода входит в ошибочное состояние и отказывается выполнять дальнейшие преобразования. Это значит, что если, например, c.population или c.latitude не могут быть преобразованы, то остальные операнды >> будут просто отброшены и мы покинем область действия функции оператора с наполовину заполненным объектом.
На вызывающей стороне мы получим оповещение об этом при написании конструкции if (input_stream >> city_object). Такое потоковое выражение неявно преобразуется в булево значение, когда используется как условное выражение. Оно возвращает значение false, если объект потока ввода находится в ошибочном состоянии. Зная это, можно сбросить поток и выполнить подходящие операции.
В данном примере мы не писали подобных условий if сами, поскольку позволили выполнить десериализацию итератору std::istream_iterator<city>. Реализация перегруженной версии оператора ++ для этого итератора также выполняет проверку ошибок во время преобразования. При генерации ошибок преобразование будет приостановлено. В этом состоянии проверка будет возвращать значение true при сравнении с конечным итератором, что заставляет алгоритм copy завершить работу. Таким образом, мы в безопасности.
Заполняем контейнеры с применением итераторов std::istream
В предыдущем примере вы узнали, как