C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Шрифт:
Интервал:
1. На сей раз нам понадобится только iostream. Так что включим этот единственный заголовочный файл и объявим об использовании пространства имен std по умолчанию:
#include <iostream>
using namespace std;
2. Пригласим пользователя ввести два числа. Поместим их в переменные типов int и double. Пользователь может разделить их пробелами. Например, 1 2.3 — это корректные входные данные.
int main()
{
cout << "Please Enter two numbers:n> ";
int x;
double y;
3. Анализ и проверка ошибок выполняются одновременно в условной части блока if. Выведем числа на экран только в том случае, если их можно проанализировать и они имеют смысл:
if (cin >> x >> y) {
cout << "You entered: " << x
<< " and " << y << 'n';
4. Если по какой-то причине анализ завершился ошибкой, то мы скажем пользователю об этом. Объект потока cin теперь находится в состоянии ошибки и не будет давать другие данные до тех пор, пока мы не избавимся от этого состояния. Чтобы иметь возможность проанализировать новые входные данные после ошибки, вызываем метод cin.clear() и отбрасываем все входные данные, полученные ранее. Отбрасывание выполняется с помощью cin.ignore, где мы указываем, что нужно отбросить максимальное количество символов до тех пор, пока не встретим символ новой строки (который тоже будет отброшен). После этого символа снова начинаются интересные входные данные:
} else {
cout << "Oh no, that did not go well!n";
cin.clear();
cin.ignore(
std::numeric_limits<std::streamsize>::max(),
'n');
}
5. Теперь запросим еще какие-нибудь входные данные. Мы позволим пользователю вводить имена. Они могут состоять из нескольких слов, разделенных пробелами, поэтому символ пробела не подходит в качестве разделителя. Так что воспользуемся std::getline, принимающим объектом потока наподобие cin, ссылкой на строку, в которую будут скопированы входные данные, и символом-разделителем (таковым выступит запятая ','). Задействуя вместо cin конструкцию cin >> ws в качестве параметра потока для getline, можно дать cin команду отбросить пробелы, стоящие перед именами. На каждом шаге цикла выводим текущее имя, но если оно пустое, то завершим цикл:
cout << "now please enter some "
"comma-separated names:n> ";
for (string s; getline(cin >> ws, s, ',');) {
if (s.empty()) { break; }
cout << "name: "" << s << ""n";
}
}
6. Компиляция и запуск программы дадут следующий результат (для него предполагается введение только корректных значений). Мы ввели числа "1 2", они были преобразованы корректно, а затем ввели некоторые имена, также корректно выведенные на экран. Пустое имя, полученное в виде двух запятых, расположенных рядом друг с другом, завершает цикл:
$ ./strings_from_user_input Please Enter two numbers:
> 1 2
You entered: 1 and 2
now please enter some comma-separated names:
> john doe, ellen ripley, alice, chuck norris,,
name: "john doe"
name: "ellen ripley"
name: "alice"
name: "chuck norris"
7. При повторном запуске программы и вводе некорректных цифр в самом начале мы увидим, что программа корректно выбирает другую ветвь, отбрасывает некорректные данные и продолжает работу с помощью слушателя имен. Попробуйте использовать строки cin.clear() и cin.ignore(...), чтобы увидеть, как это пересекается с кодом чтения имен:
$ ./strings_from_user_input
Please Enter two numbers:
> a b
Oh no, that did not go well!
now please enter some comma-separated names:
> bud spencer, terence hill,,
name: "bud spencer"
name: "terence hill"
Как это работает
В этом разделе мы выполнили сложную операцию получения входных данных. Первое, на что следует обратить внимание: получение данных и проверка на ошибки всегда происходили одновременно.
Результатом выполнения выражения cin >> x является ссылка на cin. Таким образом можно создавать конструкции наподобие cin >> x >> y >> z >> .... В то же время можно преобразовать данные к булевым значениям, используя их в булевых контекстах, таких как условие if. Булево значение говорит нам, была ли корректной последняя операция чтения. Вот почему можно создавать конструкции, аналогичные выражению if (cin >> x >> y){...}.
Если мы, например, попробуем считать целое число, но во входных данных следующим является токен "foobar", то преобразование этого значения к типу int выполнить нельзя и объект потока входит в ошибочное состояние. Это критически важно только для попытки преобразования, но не для всей программы. Можно совершенно спокойно сбросить ее и попробовать сделать что-то еще. В нашем примере мы попробовали считать список имен после потенциально ошибочной попытки считать два числа. При неудачной попытке считывания числа мы используем функцию cin.clear() с целью вернуть cin в рабочее состояние. Но после этого его внутренний курсор все еще находится в той позиции, где лежат данные, которые мы ввели вместо чисел. Чтобы отбросить старые входные данные и очистить канал для ввода имен, мы применили очень длинное выражение, cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');. Необходимо удалять все, что находилось в буфере, поскольку он нужен абсолютно пустым, когда мы запросим у пользователя список имен.
Следующий цикл также может показаться странным на первый взгляд:
for (string s; getline(cin >> ws, s, ',');) { ... }
В условной части цикла мы использовали функцию getline. Она принимает объект потока ввода, ссылку на строку в качестве выходного параметра, а также символ-разделитель. По умолчанию таковым является символ перехода на новую строку. Здесь мы указали, что в роли этого символа выступает запятая (,), чтобы все имена из списка наподобие "john, carl, frank" считывались отдельно.
Пока все идет по плану. Но что означает предоставление функции cin >> ws в качестве объекта потока? Это заставляет cin отбросить все пробелы, которые стоят перед следующим символом, не являющимся пробелом, а также в конце строки. Если бы мы не применили ws, то из строки "john, carl, frank" получили бы подстроки "john", " carl" и " frank". Заметили лишние пробелы для имен
Поделиться книгой в соц сетях:
Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!