Почему это происходит?
Это мало связано с вводом, который вы предоставили сами, а скорее с показаниями поведения по умолчанию std::getline(). Когда вы предоставили свой ввод для name ( std::cin >> name), вы не только отправили следующие символы, но и неявный символ новой строки был добавлен к потоку:
"John\n"
Новая строка всегда добавляется к вашему вводу, когда вы выбираете Enterили Returnотправляете с терминала. Он также используется в файлах для перехода к следующей строке. Новая строка остается в буфере после извлечения в nameдо следующей операции ввода-вывода, где она либо отбрасывается, либо потребляется. Когда поток управления достигнет std::getline(), новая строка будет отброшена, но ввод немедленно прекратится. Причина, по которой это происходит, заключается в том, что функциональность этой функции по умолчанию диктует, что она должна (она пытается прочитать строку и останавливается, когда находит новую строку).
Поскольку эта начальная новая строка препятствует ожидаемой функциональности вашей программы, из этого следует, что ее нужно как-то пропустить и игнорировать. Один из вариантов - позвонить std::cin.ignore()после первого извлечения. Он отбросит следующий доступный символ, чтобы новая строка больше не мешала.
std::getline(std::cin.ignore(), state)
Подробное объяснение:
Это перегрузка того, std::getline()что вы вызвали:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
Другая перегрузка этой функции принимает разделитель типа charT. Символ-разделитель - это символ, который представляет границу между последовательностями ввода. Эта конкретная перегрузка input.widen('\n')по умолчанию устанавливает в качестве разделителя символ новой строки , поскольку он не был предоставлен.
Вот несколько условий, при которых std::getline()завершается ввод:
- Если поток извлек максимальное количество символов, которое
std::basic_string<charT>может содержать
- Если был найден символ конца файла (EOF)
- Если разделитель найден
Мы имеем дело с третьим условием. Ваш вклад в stateпредставлен следующим образом:
"John\nNew Hampshire"
^
|
next_pointer
где next_pointerследующий символ для анализа. Поскольку символ, сохраненный в следующей позиции во входной последовательности, является разделителем, std::getline()он незаметно отбрасывает этот символ, переходит next_pointerк следующему доступному символу и останавливает ввод. Это означает, что остальные символы, которые вы предоставили, все еще остаются в буфере для следующей операции ввода-вывода. Вы заметите, что если вы выполните еще одно чтение из строки в state, ваше извлечение даст правильный результат в качестве последнего вызова для удаления std::getline()разделителя.
Возможно, вы заметили, что обычно вы не сталкиваетесь с этой проблемой при извлечении с помощью оператора форматированного ввода ( operator>>()). Это связано с тем, что входные потоки используют пробелы в качестве разделителей для входных данных, а манипулятор std::skipws1 установлен по умолчанию. Потоки будут отбрасывать ведущие пробелы из потока, когда начинают выполнять форматированный ввод. 2
В отличие от операторов форматированного ввода, std::getline()это неформатированная функция ввода. И все функции неформатированного ввода имеют общий код:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Вышеупомянутый объект-часовой, который создается во всех форматированных / неформатированных функциях ввода-вывода в стандартной реализации C ++. Объекты Sentry используются для подготовки потока к вводу-выводу и определения того, находится ли он в состоянии отказа. Вы только обнаружите, что в неформатированных функциях ввода вторым аргументом конструктора часового является true. Этот аргумент означает, что ведущие пробелы не будут отбрасываться с начала входной последовательности. Вот соответствующая цитата из Стандарта [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] Если noskipwsравен нулю и is.flags() & ios_base::skipwsотличен от нуля, функция извлекает и отбрасывает каждый символ, пока следующий доступный входной символ cявляется пробельным символом. [...]
Поскольку указанное выше условие ложно, объект-часовой не отбрасывает пробелы. Причина, noskipwsпо trueкоторой эта функция установлена, состоит в том, что цель std::getline()состоит в том, чтобы считать неформатированные символы в std::basic_string<charT>объект.
Решение:
Невозможно остановить такое поведение std::getline(). Что вам нужно сделать, так это самостоятельно удалить новую строку перед std::getline()запуском (но сделать это после форматированного извлечения). Это можно сделать, используя ignore()для удаления остальной части ввода, пока мы не дойдем до новой новой строки:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
Вам нужно будет включить, <limits>чтобы использовать std::numeric_limits. std::basic_istream<...>::ignore()- это функция, которая отбрасывает указанное количество символов до тех пор, пока не найдет разделитель или не достигнет конца потока ( ignore()также отбрасывает разделитель, если он его находит). max()Функция возвращает наибольшее количество символов , что поток может принять.
Другой способ отбросить пробелы - использовать std::wsфункцию, которая представляет собой манипулятор, предназначенный для извлечения и удаления ведущих пробелов из начала входного потока:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
Какая разница?
Разница в том, что ignore(std::streamsize count = 1, int_type delim = Traits::eof())3 без разбора отбрасывает символы до тех пор, пока он не отбросит countсимволы, не найдет разделитель (указанный вторым аргументом delim) или не достигнет конца потока. std::wsиспользуется только для отбрасывания пробелов из начала потока.
Если вы смешиваете форматированный ввод с неформатированным вводом и вам нужно отбросить остаточные пробелы, используйте std::ws. В противном случае, если вам нужно очистить недопустимый ввод независимо от того, что это такое, используйте ignore(). В нашем примере нам нужно только очистить пробелы, поскольку поток потреблял ваш ввод "John"для nameпеременной. Все, что осталось, это символ новой строки.
1: std::skipwsэто манипулятор, который сообщает входному потоку, чтобы он отбрасывал ведущие пробелы при выполнении форматированного ввода. Это можно отключить с помощью std::noskipwsманипулятора.
2: входные потоки по умолчанию рассматривают определенные символы как пробелы, такие как пробел, символ новой строки, подача формы, возврат каретки и т. Д.
3: Это подпись std::basic_istream<...>::ignore(). Вы можете вызвать его с нулевыми аргументами, чтобы отбросить один символ из потока, одним аргументом, чтобы отбросить определенное количество символов, или двумя аргументами, чтобы отбросить countсимволы или пока он не достигнет delim, в зависимости от того, какой из них наступит раньше. Обычно вы используете std::numeric_limits<std::streamsize>::max()в качестве значения, countесли вы не знаете, сколько символов стоит перед разделителем, но вы все равно хотите их отбросить.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)что он также должен работать, как ожидалось. (В дополнение к ответам ниже).