Почему это происходит?
Это мало связано с вводом, который вы предоставили сами, а скорее с показаниями поведения по умолчанию 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::skipws
1 установлен по умолчанию. Потоки будут отбрасывать ведущие пробелы из потока, когда начинают выполнять форматированный ввод. 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)
что он также должен работать, как ожидалось. (В дополнение к ответам ниже).