Почему std :: getline () пропускает ввод после форматированного извлечения?
У меня есть следующий код, который подскажет пользователю имя и состояние:
#include #include int main() { std::string name; std::string state; if (std::cin >> name && std::getline(std::cin, state)) { std::cout << "Your name is " << name << " and you live in " << state; } }
Я нахожу, что имя было успешно извлечено, но не состояние. Вот вход и результат:
Input: "John" "New Hampshire" Output: "Your name is John and you live in "
данныеInput: "John" "New Hampshire" Output: "Your name is John and you live in "
Почему имя состояния было исключено из вывода? Я дал правильный ввод, но код каким-то образом игнорирует его. Почему это происходит?
- Почему я получаю InputMismatchException?
- Тестирование JUnit с использованием имитируемого пользовательского ввода
- Обнаруживать, есть ли в тексте ввод текста с помощью CSS - на странице, которую я посещаю и не контролирую?
- Автозаполнение jQuery для динамически создаваемых входов
- Чтение целого числа из пользовательского ввода
Почему это происходит?
Это не имеет никакого отношения к введенным вами данным, а скорее к поведению std::getline()
по умолчанию. Когда вы указали свой ввод имени ( std::cin >> name
), вы не только отправили следующие символы, но также добавили неявную строку новой строки в stream:
"John\n"
Новая строка всегда добавляется к вашему вводу, когда вы выбираете Enter или Return при отправке с терминала. Он также используется в файлах для перехода к следующей строке. Новая строка остается в буфере после извлечения в name
до следующей операции ввода-вывода, где она либо отбрасывается, либо потребляется. Когда stream управления достигнет std::getline()
, новая строка будет отброшена, но вход немедленно прекратится. Причина этого в том, что функциональность по умолчанию этой функции диктует ее (она пытается прочитать строку и останавливается, когда находит новую строку).
Поскольку эта ведущая новая строка препятствует ожидаемой функциональности вашей программы, следует, что ее нужно как-то пропустить. Одним из вариантов является вызов std::cin.ignore()
после первого извлечения. Он будет отбрасывать следующий ansible символ, чтобы новая строка больше не была навязчивой.
In-Depth Explanation:
Это перегрузка std::getline()
которую вы вызывали:
template
std::basic_istream & getline( std::basic_istream & input, std::basic_string & str )
Другая перегрузка этой функции принимает разделитель типа charT
. Символ разделителя – это символ, который представляет границу между последовательностями ввода. Эта конкретная перегрузка задает разделитель символу newline input.widen('\n')
по умолчанию, поскольку он не был указан.
Теперь это несколько из условий, при которых std::getline()
завершает ввод:
- Если stream выделил максимальное количество символов, может быть
std::basic_string
- Если символ конца файла (EOF) найден
- Если разделитель найден
Третье условие – это то, с чем мы имеем дело. Таким образом, ваш вход в state
представлен следующим образом:
"John\nNew Hampshire" ^ | next_pointer
где next_pointer
– следующий символ, подлежащий анализу. Поскольку символ, сохраненный в следующей позиции во входной последовательности, является разделителем, std::getline()
будет спокойно отбрасывать этот символ, увеличивать next_pointer
до следующего доступного символа и останавливать ввод. Это означает, что остальные символы, которые вы предоставили, все еще остаются в буфере для следующей операции ввода-вывода. Вы заметите, что если вы выполните другое чтение из строки в state
, ваше извлечение даст правильный результат, поскольку последний вызов std::getline()
отменил разделитель.
Возможно, вы заметили, что вы обычно не сталкиваетесь с этой проблемой при извлечении с помощью форматированного оператора ввода ( operator>>()
). Это связано с тем, что входные streamи используют пробелы как разделители для ввода, и по умолчанию установлен стандартный манипулятор std::skipws
1 . Потоки будут отбрасывать ведущие пробелы из streamа при начале выполнения форматированного ввода. 2
В отличие от форматированных входных операторов, std::getline()
является неформатированной входной функцией. И все неформатированные входные функции имеют следующий код несколько общего:
typename std::basic_istream::sentry ok(istream_object, true);
Вышеупомянутый объект является часовым, который создается во всех форматированных / неформатированных функциях ввода / вывода в стандартной реализации на C ++. Объекты Sentry используются для подготовки streamа для ввода-вывода и определения того, находится ли он в состоянии сбоя. Вы обнаружите, что в неформатированных входных функциях второй аргумент конструктора часовых равен true
. Этот аргумент означает, что ведущие пробелы не будут отбрасываться с начала входной последовательности. Вот соответствующая цитата из Стандарта [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream
& is, bool noskipws = false); […] Если
noskipws
равен нулю иis.flags() & ios_base::skipws
отличен от нуля, функция извлекает и отбрасывает каждый символ, если следующий ansible входной символc
является символом пробела. […]
Поскольку указанное выше условие ложно, объект-сторож не будет отбрасывать пробелы. Причина, по которой noskipws
установлена в true
этой функцией, заключается в том, что точка std::getline()
заключается в чтении необработанных, неформатированных символов в объект std::basic_string
.
Решение:
std::getline()
остановить это поведение std::getline()
. Вам нужно будет отбросить новую строку до std::getline()
но сделать это после форматированного извлечения). Это можно сделать, используя ignore()
чтобы отбросить остальную часть ввода до тех пор, пока мы не достигнем новой новой строки:
if (std::cin >> name && std::cin.ignore(std::numeric_limits::max(), '\n') && std::getline(std::cin, state)) { ... }
Вам нужно будет включить
для использования std::numeric_limits
. std::basic_istream<...>::ignore()
– это функция, которая отбрасывает определенное количество символов, пока не найдет разделитель или не достигнет конца streamа ( ignore()
также отбрасывает разделитель, если он находит его) , Функция max()
возвращает наибольшее количество символов, которые может принять stream.
Другим способом отбрасывания пробелов является использование функции std::ws
которая является манипулятором, предназначенным для извлечения и удаления начального пробела с начала входного streamа:
if (std::cin >> name && std::getline(std::cin >> std::ws, state)) { ... }
Какая разница?
Разница заключается в том, что ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 без parsingа отбрасывает символы, пока он не сбрасывает символы count
, находит разделитель (указанный вторым аргументом delim
) или попадает в конец streamа. std::ws
используется только для отбрасывания пробельных символов с начала streamа.
Если вы микшируете форматированный вход с неформатированным вводом и вам нужно отбросить остаточные пробелы, используйте std::ws
. В противном случае, если вам нужно очистить недопустимый ввод независимо от того, что это такое, используйте ignore()
. В нашем примере нам нужно только очистить пробелы, так как stream использовал ваш вход "John"
для переменной name
. Осталось только символ новой строки.
1: std::skipws
– манипулятор, который сообщает входному streamу удалять ведущие пробелы при выполнении форматированного ввода. Это можно отключить с std::noskipws
манипулятора std::noskipws
.
2: входные streamи по умолчанию определяют определенные символы как пробельные символы, такие как символ пробела, символ новой строки, подача формы, возврат каретки и т. Д.
3: Это подпись std::basic_istream<...>::ignore()
. Вы можете называть его нулевыми аргументами, чтобы отбросить один символ из streamа, один аргумент, чтобы отбросить определенное количество символов или два аргумента, чтобы отбросить символы count
или до тех пор, пока он не достигнет delim
, в зависимости от того, что наступит раньше. Обычно вы используете std::numeric_limits
как значение count
если вы не знаете, сколько символов есть перед разделителем, но вы все равно хотите их отбросить.
Все будет в порядке, если вы измените исходный код следующим образом:
if ((cin >> name).get() && std::getline(cin, state))
Это происходит из-за того, что неявный фид строки, также известный как символ новой строки \n
, добавляется ко всем пользовательским вводам терминала, поскольку он сообщает streamу начать новую строку. Вы можете смело учитывать это, используя std::getline
при проверке нескольких строк ввода пользователя. Поведение std::getline
умолчанию будет читать все до и включая символ новой строки \n
из объекта входного streamа, который в этом случае является std::cin
.
#include #include int main() { std::string name; std::string state; if (std::getline(std::cin, name) && std::getline(std::cin, state)) { std::cout << "Your name is " << name << " and you live in " << state; } return 0; }
Input: "John" "New Hampshire" Output: "Your name is John and you live in New Hampshire"
данныеInput: "John" "New Hampshire" Output: "Your name is John and you live in New Hampshire"