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