Почему std :: getline () пропускает ввод после форматированного извлечения?

У меня есть следующий фрагмент кода, который запрашивает у пользователя его имя и состояние:

#include <iostream>
#include <string>

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 "

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

81

Решение

Это не имеет ничего общего с вводом, который вы предоставили сами, а скорее с поведением по умолчанию std::getline() экспонаты. Когда вы предоставили свой ввод для имени (std::cin >> name), вы не только отправили следующие символы, но и неявный символ новой строки был добавлен в поток:

"John\n"

Новая строка всегда добавляется к вашему вводу при выборе Войти или же Вернуть при отправке из терминала. Он также используется в файлах для перехода к следующей строке. Новая строка остается в буфере после извлечения в name до следующей операции ввода / вывода, когда она либо сбрасывается, либо потребляется. Когда поток управления достигает std::getline(), новая строка будет отброшена, но ввод немедленно прекратится. Причина, по которой это происходит, заключается в том, что функциональность по умолчанию этой функции диктует, что она должна (она пытается прочитать строку и останавливается, когда находит новую строку).

Поскольку этот ведущий символ новой строки запрещает ожидаемую функциональность вашей программы, из этого следует, что ее нужно как-то игнорировать. Один из вариантов — позвонить std::cin.ignore() после первого извлечения. Он будет отбрасывать следующий доступный символ, чтобы новая строка больше не была навязчивой.


Это перегрузка 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 если вы не знаете, сколько символов перед разделителем, но вы все равно хотите их удалить.

94

Другие решения

Все будет хорошо, если вы измените свой исходный код следующим образом:

if ((cin >> name).get() && std::getline(cin, state))
10

Это происходит потому, что неявный перевод строки также известен как символ перевода строки \n добавляется ко всем пользовательским вводам с терминала, поскольку он сообщает потоку начать новую строку. Вы можете смело объяснить это, используя std::getline при проверке нескольких строк пользовательского ввода. Поведение по умолчанию std::getline будет читать все, вплоть до символа новой строки \n из объекта входного потока, который является std::cin в этом случае.

#include <iostream>
#include <string>

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"
0
По вопросам рекламы [email protected]