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

>> x >> y){...}
.

Если мы, например, попробуем считать целое число, но во входных данных следующим является токен

"foobar"
, то преобразование этого значения к типу
int
выполнить нельзя и объект потока входит в ошибочное состояние. Это критически важно только для попытки преобразования, но не для всей программы. Можно совершенно спокойно сбросить ее и попробовать сделать что-то еще. В нашем примере мы попробовали считать список имен после потенциально ошибочной попытки считать два числа. При неудачной попытке считывания числа мы используем функцию
cin.clear()
с целью вернуть
cin
в рабочее состояние. Но после этого его внутренний курсор все еще находится в той позиции, где лежат данные, которые мы ввели вместо чисел. Чтобы отбросить старые входные данные и очистить канал для ввода имен, мы применили очень длинное выражение,
cin.ignore(std::numeric_limits::max(), '\n');
. Необходимо удалять все, что находилось в буфере, поскольку он нужен абсолютно пустым, когда мы запросим у пользователя список имен.

Следующий цикл также может показаться странным на первый взгляд:


for (string s; getline(cin >> ws, s, ',');) { ... }


В условной части цикла мы использовали функцию

getline
. Она принимает объект потока ввода, ссылку на строку в качестве выходного параметра, а также символ-разделитель. По умолчанию таковым является символ перехода на новую строку. Здесь мы указали, что в роли этого символа выступает запятая (
,
), чтобы все имена из списка наподобие
"john, carl, frank"
считывались отдельно.

Пока все идет по плану. Но что означает предоставление функции

cin >> ws
в качестве объекта потока? Это заставляет
cin
отбросить все пробелы, которые стоят перед следующим символом, не являющимся пробелом, а также в конце строки. Если бы мы не применили
ws
, то из строки
"john, carl, frank"
получили бы подстроки
"john"
,
" carl"
и
" frank"
. Заметили лишние пробелы для имен
carl
и
frank
? Они пропадают, поскольку мы использовали
ws

Подсчитываем все слова в файле

Предположим, мы считали текстовый файл и хотим определить количество слов в тексте. Словом, мы называем диапазон символов, расположенный между пробелами. Как же решить эту задачу?

Например, можно подсчитать количество пробелов, поскольку между словами должны быть пробелы. В предложении

"John has a funny little dog."
пять символов пробела, поэтому можно сказать, что оно состоит из шести слов.

Но как быть с предложением, содержащим разные виды пробелов, например:

"John has \t a\nfunny little dog."
? В нем содержится слишком много ненужных пробелов и не только. Из других примеров данной книги вы уже знаете, как удалить эти лишние пробелы. Так что сначала можно было бы предварительно обработать строку, преобразовав ее в обычное предложение, а затем применить стратегию подсчета пробелов. Это выполнимо, но существует гораздо более простой способ. Почему бы не воспользоваться теми возможностями, которые предоставляет STL?

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


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

В этом примере мы напишем короткую функцию, которая считает количество слов из входного буфера и позволяет пользователю выбирать, откуда именно во входном буфере появятся данные.


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

std
:


#include 

#include 

#include 

#include 

#include 


using namespace std;


2. Функция

wordcount
принимает поток ввода, например
cin
. Она создает итератор
std::input_iterator
, который токенизирует строки потока, а затем передает их в
std::distance
. Параметр
distance
принимает в качестве аргументов два итератора и пытается определить, сколько именно операций инкремента нужно выполнить, чтобы переместиться с одной позиции итератора в другую. Для итераторов с произвольным доступом это делается легко, поскольку они реализуют операцию математической разности (
operator-
). Такие итераторы можно вычитать друг из друга, как и другие указатели. Итератор
istream_iterator
, однако, является однонаправленным, и его нужно сдвигать вперед до тех пор, пока он не станет равен итератору
end
. В конечном счете количество шагов будет равно количеству слов.


template 

size_t wordcount(T &is)

{

  return distance(istream_iterator{is}, {});

}


3. В нашей функции

main
мы позволяем пользователю выбрать, откуда придет поток ввода — из
std::cin
или из файла:


int main(int argc, char **argv)

{

  size_t wc;


4. Если пользователь запустит программу в оболочке и укажет имя файла (например,

$ ./count_all_words some_textfile.txt
), то мы сможем получить его из параметра командной строки
argv
и открыть, чтобы передать новый поток ввода в функцию
wordcount
:


  if (argc == 2) {

    ifstream ifs {argv[1]};

    wc = wordcount(ifs);


5. В случае запуска пользователем программы без параметров мы предполагаем, что входные данные появятся из стандартного потока ввода:


  } else {

    wc = wordcount(cin);

  }


6. На этом все, просто выведем количество слов, сохраненное в переменной

wc
:


  cout << "There are " << wc << " words\n";

};


7. Скомпилируем и запустим программу. Сначала передадим данные из стандартного потока ввода. Можно либо выполнить вызов echo, передав туда несколько слов, либо запустить программу и ввести несколько слов с клавиатуры. Во втором случае можно прервать ввод нажатием комбинации клавиш Ctrl+D. Так выглядит вызов

echo
:


$ echo "foo bar baz" | ./count_all_words

There are 3 words


8. Если запустить программу и передать ей в качестве входного файла ее файл с исходным кодом, то она подсчитает количество слов, которые содержатся в нем:


$ ./count_all_words count_all_words.cpp

There are 61 words


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

Здесь особо нечего добавить, большую часть программы мы уже объяснили при реализации, поскольку сама программа очень короткая. Единственный факт, который можно немного пояснить:

std::cin
и
std::ifstream
взаимозаменяемы.
cin
имеет тип
std::istream
, а
std::ifstream
наследует от
std::istream
. Взгляните на диаграмму наследования, приведенную в начале этой главы (см. рис. 7.1). Таким образом, они полностью взаимозаменяемы, даже во время работы программы.


 Поддерживайте модульность кода путем использования абстракций потока. Это позволит отвязать фрагменты исходного кода друг от друга и облегчит тестирование исходного кода, поскольку можно внедрить любой другой соответствующий тип потока.

Форматируем ваши выходные данные с помощью манипуляторов потока ввода-вывода

Во многих случаях недостаточно просто вывести строки и числа. Иногда числа нужно вывести в десятичной системе счисления, иногда — в шестнадцатеричной, а иногда — в восьмеричной. В одних ситуациях перед шестнадцатеричными числами префикс

"0x"
нужен, а в других — нет.

При выводе чисел с плавающей точкой существует множество моментов, на которые можно повлиять. Нужно ли всегда выводить их с одинаковой точностью? Надо ли выводить их вообще? Или, может быть, требуется научное представление?