У меня есть следующий фрагмент кода, который запрашивает у пользователя свое имя и статус:
#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::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
что он также должен работать, как ожидалось. (В дополнение к ответам ниже).Ответы:
Почему это происходит?
Это мало связано с вводом, который вы предоставили сами, а скорее с показаниями поведения по умолчанию
std::getline()
. Когда вы предоставили свой ввод для name (std::cin >> name
), вы не только отправили следующие символы, но и неявный символ новой строки был добавлен к потоку:Новая строка всегда добавляется к вашему вводу, когда вы выбираете Enterили Returnотправляете с терминала. Он также используется в файлах для перехода к следующей строке. Новая строка остается в буфере после извлечения в
name
до следующей операции ввода-вывода, где она либо отбрасывается, либо потребляется. Когда поток управления достигнетstd::getline()
, новая строка будет отброшена, но ввод немедленно прекратится. Причина, по которой это происходит, заключается в том, что функциональность этой функции по умолчанию диктует, что она должна (она пытается прочитать строку и останавливается, когда находит новую строку).Поскольку эта начальная новая строка препятствует ожидаемой функциональности вашей программы, из этого следует, что ее нужно как-то пропустить и игнорировать. Один из вариантов - позвонить
std::cin.ignore()
после первого извлечения. Он отбросит следующий доступный символ, чтобы новая строка больше не мешала.Подробное объяснение:
Это перегрузка того,
std::getline()
что вы вызвали:Другая перегрузка этой функции принимает разделитель типа
charT
. Символ-разделитель - это символ, который представляет границу между последовательностями ввода. Эта конкретная перегрузкаinput.widen('\n')
по умолчанию устанавливает в качестве разделителя символ новой строки , поскольку он не был предоставлен.Вот несколько условий, при которых
std::getline()
завершается ввод:std::basic_string<charT>
может содержатьМы имеем дело с третьим условием. Ваш вклад в
state
представлен следующим образом:где
next_pointer
следующий символ для анализа. Поскольку символ, сохраненный в следующей позиции во входной последовательности, является разделителем,std::getline()
он незаметно отбрасывает этот символ, переходитnext_pointer
к следующему доступному символу и останавливает ввод. Это означает, что остальные символы, которые вы предоставили, все еще остаются в буфере для следующей операции ввода-вывода. Вы заметите, что если вы выполните еще одно чтение из строки вstate
, ваше извлечение даст правильный результат в качестве последнего вызова для удаленияstd::getline()
разделителя.Возможно, вы заметили, что обычно вы не сталкиваетесь с этой проблемой при извлечении с помощью оператора форматированного ввода (
operator>>()
). Это связано с тем, что входные потоки используют пробелы в качестве разделителей для входных данных, а манипуляторstd::skipws
1 установлен по умолчанию. Потоки будут отбрасывать ведущие пробелы из потока, когда начинают выполнять форматированный ввод. 2В отличие от операторов форматированного ввода,
std::getline()
это неформатированная функция ввода. И все функции неформатированного ввода имеют общий код:Вышеупомянутый объект-часовой, который создается во всех форматированных / неформатированных функциях ввода-вывода в стандартной реализации C ++. Объекты Sentry используются для подготовки потока к вводу-выводу и определения того, находится ли он в состоянии отказа. Вы только обнаружите, что в неформатированных функциях ввода вторым аргументом конструктора часового является
true
. Этот аргумент означает, что ведущие пробелы не будут отбрасываться с начала входной последовательности. Вот соответствующая цитата из Стандарта [§27.7.2.1.3 / 2]:Поскольку указанное выше условие ложно, объект-часовой не отбрасывает пробелы. Причина,
noskipws
поtrue
которой эта функция установлена, состоит в том, что цельstd::getline()
состоит в том, чтобы считать неформатированные символы вstd::basic_string<charT>
объект.Решение:
Невозможно остановить такое поведение
std::getline()
. Что вам нужно сделать, так это самостоятельно удалить новую строку передstd::getline()
запуском (но сделать это после форматированного извлечения). Это можно сделать, используяignore()
для удаления остальной части ввода, пока мы не дойдем до новой новой строки:Вам нужно будет включить,
<limits>
чтобы использоватьstd::numeric_limits
.std::basic_istream<...>::ignore()
- это функция, которая отбрасывает указанное количество символов до тех пор, пока не найдет разделитель или не достигнет конца потока (ignore()
также отбрасывает разделитель, если он его находит).max()
Функция возвращает наибольшее количество символов , что поток может принять.Другой способ отбросить пробелы - использовать
std::ws
функцию, которая представляет собой манипулятор, предназначенный для извлечения и удаления ведущих пробелов из начала входного потока:Какая разница?
Разница в том, что
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 (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, но тогда не так ясно, есть ли преимущество. Но я предпочитаю просто использоватьstd::getline()
строчно-ориентированный ввод, а затем разбирать строку любым разумным способом. Я думаю, что это менее подвержено ошибкам.std::getline()
- это если вы хотите захватить все символы до заданного разделителя и ввести их в строку, по умолчанию это новая строка. Если этоX
количество строк - это просто отдельные слова / токены, тогда эту работу можно легко выполнить>>
. В противном случае вы должны ввести первое число в целое число с помощью>>
, вызватьcin.ignore()
следующую строку, а затем запустить цикл, в котором вы используетеgetline()
.Все будет в порядке, если вы измените исходный код следующим образом:
источник
get()
потребляет следующий символ. Также есть то,(std::cin >> name).ignore()
что я предлагал ранее в своем ответе.if (getline(std::cin, name) && getline(std::cin, state))
?Это происходит потому, что неявный перевод строки, также известный как символ новой строки,
\n
добавляется ко всему пользовательскому вводу с терминала, поскольку он сообщает потоку о начале новой строки. Вы можете безопасно учесть это, используяstd::getline
при проверке нескольких строк пользовательского ввода. Поведение по умолчаниюstd::getline
будет читать все, включая символ новой строки\n
из объекта входного потока, которыйstd::cin
в данном случае есть.источник