У меня есть следующий фрагмент кода, который запрашивает у пользователя его имя и состояние:
#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 "
Почему название штата было опущено в выводе? Я дал правильный ввод, но код почему-то игнорирует это. Почему это происходит?
Это не имеет ничего общего с вводом, который вы предоставили сами, а скорее с поведением по умолчанию 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>
может держатьТретье условие — это то, с которым мы имеем дело. Ваш вклад в 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
если вы не знаете, сколько символов перед разделителем, но вы все равно хотите их удалить.
Все будет хорошо, если вы измените свой исходный код следующим образом:
if ((cin >> name).get() && std::getline(cin, state))
Это происходит потому, что неявный перевод строки также известен как символ перевода строки \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"