Почему 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 " 

Почему имя состояния было исключено из вывода? Я дал правильный ввод, но код каким-то образом игнорирует его. Почему это происходит?

Почему это происходит?

Это не имеет никакого отношения к введенным вами данным, а скорее к поведению 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::max() как значение 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" 
Interesting Posts

Не удалось найти DHCP-демона для получения информации о Belkin G Wifi Router

Как я могу попросить Selenium-WebDriver ждать несколько секунд в Java?

Что означают круглые скобки вокруг имени функции?

Android-эмулятор ничего не показывает, кроме устройств с черным экраном и adb, показывает «устройство в автономном режиме»,

Найти местоположение узла с помощью xpath

Что такое конструктор по умолчанию для указателя на C ++?

Отладка против выпуска в CMAKE

Измерение фактического времени выполнения MySQL

Сравнение пакетных файлов переменной с константой

Изменяйте программный цвет с возможностью рисования

Как можно полиморфная десериализация Json String с использованием Java и библиотеки Jackson?

Исключить определенные сайты или URL-адреса из результатов поиска Google?

Есть ли какая-либо библиотека C / C ++ для подключения к удаленному NTP-серверу?

python3 print unicode для windows xp console encode cp437

Как сделать сравнение строк без учета регистра?

Давайте будем гением компьютера.