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

         0123456

A hex number with upper case letters: 0X123ABC

A number: 0X64

Oops. now in decimal again: 100

true/false values: 1, 0

true/false values: true, false

doubles: 12.3, 12, 12.0000

scientific double: 1.230000E+11

fixed      double: 123000000000.123001

Very precise double: 0.0000000001

Less precise double: 0.0


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

Все эти stream выражения,

<< foo << bar
, иногда довольно длинные и способны запутать читателя: ему может быть непонятно, что именно делает каждое из них. Поэтому взглянем на таблицу, в которой приведены существующие модификаторы форматов (табл. 7.1). Эти модификаторы нужно помещать в выражение
input_stream >> modifier
или
output_stream << modifier
, в этом случае они будут влиять на входные или выходные данные.

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

При взаимодействии, однако, мы уже могли заметить, что большинство модификаторов являются стойкими. Термин «стойкий» означает следующее: после применения они станут влиять на входные/выходные данные до тех пор, пока не будут сброшены. Единственные нестойкие модификаторы в табл. 7.1 —

setw
и
quoted
. Они влияют только на следующий элемент входных/выходных данных. Это важно знать, поскольку при выводе неких данных с определенным форматированием нужно сбрасывать настройки форматирования объекта потока, так как следующий блок выходных данных, создаваемый несвязанным кодом, может выглядеть странно. Это же верно и для преобразования входных данных, где правильный ход программы может быть нарушен из-за неверных настроек манипулятора ввода/вывода.

Мы не использовали следующие манипуляторы, поскольку они никак не связаны с форматированием, но для полноты картины рассмотрим и их (табл. 7.2).

Среди перечисленных модификаторов стойкими являются только

skipws/noskipws
и
unitbuf/nounitbuf
.

Инициализируем сложные объекты из файла вывода

Считывать отдельные числа и слова довольно просто, поскольку оператор

>>
имеет перегруженные версии для всех этих типов, а потоки ввода удобным образом отбрасывают все промежуточные пробелы.

Но что, если перед нами более сложная структура и нужно прочесть ее из потока ввода или же требуется считать строки, состоящие более чем из одного слова (по умолчанию они будут разбиты на отдельные слова из-за того, что пробелы опускаются)?

Для любого типа можно предоставить еще одну перегруженную версию оператора потока ввода

>>
, и сейчас мы увидим воплощение этого на практике. 


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

В этом примере мы определим пользовательскую структуру данных и предоставим возможности для чтения ее объектов из потоков ввода.


1. Включим некоторые заголовочные файлы и объявим об использовании пространства имен

std
для удобства:


#include 

#include 

#include 

#include 

#include 

#include 


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 l;

  copy(istream_iterator{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
.

Открытым может оставаться вопрос, связанный с проверкой на ошибки. Поэтому снова рассмотрим реализацию оператора

>>
:


istream& operator>>(istream &is, city &c)

{

  is