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

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

Шрифт:

-
+

Интервал:

-
+
1 ... 66 67 68 69 70 71 72 73 74 ... 121
Перейти на страницу:
7.1 — setw и quoted. Они влияют только на следующий элемент входных/выходных данных. Это важно знать, поскольку при выводе неких данных с определенным форматированием нужно сбрасывать настройки форматирования объекта потока, так как следующий блок выходных данных, создаваемый несвязанным кодом, может выглядеть странно. Это же верно и для преобразования входных данных, где правильный ход программы может быть нарушен из-за неверных настроек манипулятора ввода/вывода.

Мы не использовали следующие манипуляторы, поскольку они никак не связаны с форматированием, но для полноты картины рассмотрим и их (табл. 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

В предыдущем примере вы узнали, как

1 ... 66 67 68 69 70 71 72 73 74 ... 121
Перейти на страницу:

Комментарии

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

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