#c #getline
Вопрос:
У меня есть следующий фрагмент кода, который запрашивает у пользователя возраст и имя его кошки:
#include <iostream>
#include <string>
int main()
{
int age;
std::string name;
std::cin >> age;
std::getline(std::cin, name);
if (std::cin)
{
std::cout << "My cat is " << age << " years old and their name is " << name << std::endl;
}
}
Что я нахожу, так это то, что возраст был успешно прочитан, но не имя. Вот вход и выход:
Input: "10" "Mr. Whiskers" Output: "My cat is 10 years old and their name is "
Почему имя было опущено в выходных данных? Я ввел правильные данные, но код каким — то образом игнорирует их. Почему это происходит?
Комментарии:
1. Я считаю
std::cin >> name amp;amp; std::cin >> std::skipws amp;amp; std::getline(std::cin, state)
, что также должно работать так, как ожидалось. (В дополнение к ответам ниже).
Ответ №1:
Почему это происходит?
Это имеет мало общего с вводом, который вы предоставили сами, а скорее с поведением по умолчанию std::getline()
. Когда вы вводили данные для age ( std::cin >> age
), вы не только отправляли следующие символы, но и неявная новая строка добавлялась в поток при вводеEnter:
"10n"
Новая строка всегда добавляется к вашему вводу при выборе Enterили Returnпри отправке с терминала. Он также используется в файлах для перехода к следующей строке. Новая строка остается в буфере после извлечения age
до следующей операции ввода-вывода, где она либо отбрасывается, либо считывается. Когда поток управления достигнет std::getline()
, он увидит "nMr. Whiskers"
, и новая строка в начале будет отброшена, но операция ввода немедленно прекратится. Причина, по которой это происходит, заключается в том, что задача std::getline()
состоит в том, чтобы попытаться прочитать символы и остановиться, когда он найдет новую строку. Таким образом, остальная часть ваших входных данных остается в буфере непрочитанной.
Решение
cin.ignore()
Чтобы исправить это, один из вариантов-пропустить новую строку перед выполнением std::getline()
. Вы можете сделать это, позвонив std::cin.ignore()
после первой операции ввода. Он отбросит следующий символ (символ новой строки), чтобы он больше не мешал.
std::cin >> age;
std::cin.ignore();
std::getline(std::cin, name);
Сопоставьте операции
Когда вы сталкиваетесь с подобной проблемой, обычно это происходит из-за того, что вы объединяете операции ввода в формате с операциями ввода в формате без форматирования. Операция форматированного ввода-это когда вы берете ввод и форматируете его для определенного типа. Вот operator>>()
для чего это нужно. Неформатированные операции ввода-это что-то другое, например std::getline()
, std::cin.read()
, std::cin.get()
, и т. Д. Эти функции не заботятся о формате ввода и обрабатывают только необработанный текст.
Если вы придерживаетесь использования одного типа форматирования, вы можете избежать этой досадной проблемы:
// Unformatted I/O
std::string age, name;
std::getline(std::cin, age);
std::getline(std::cin, name);
или
// Formatted I/O
int age;
std::string first_name, last_name;
std::cin >> age >> first_name >> last_name;
Если вы решите читать все как строки, используя неформатированные операции, вы можете впоследствии преобразовать их в соответствующие типы.
Комментарии:
1. Почему бы просто не
if (getline(std::cin, name) amp;amp; getline(std::cin, state))
сделать это ?2. @FredLarson Хорошая мысль. Хотя это не сработало бы, если бы первое извлечение было целым числом или чем-то, что не является строкой.
3. Конечно, здесь это не так, и нет смысла делать одно и то же двумя разными способами. Для целого числа вы могли бы преобразовать строку в строку , а затем использовать
std::stoi()
, но тогда не так ясно, есть ли преимущество. Но я, как правило, предпочитаю просто использоватьstd::getline()
для линейно-ориентированного ввода, а затем разбирать строку любым разумным способом. Я думаю, что это менее подвержено ошибкам.4. @FredLarson Согласился. Может быть, я добавлю это, если у меня будет время.
5. @Albin Причина, по которой вы можете захотеть использовать
std::getline()
, заключается в том, что вы хотите записать все символы до заданного разделителя и ввести их в строку, по умолчанию это новая строка. Если этоX
количество строк-всего лишь отдельные слова/токены, то эту работу можно легко выполнить с>>
помощью . В противном случае вы бы ввели первое число в целое число с>>
помощью , вызвалиcin.ignore()
следующую строку, а затем запустили цикл, в котором вы используетеgetline()
.
Ответ №2:
Все будет в порядке, если вы измените свой исходный код следующим образом:
if ((cin >> name).get() amp;amp; std::getline(cin, state))
Комментарии:
1. Спасибо. Это также будет работать, потому
get()
что потребляет следующий символ. Есть также(std::cin >> name).ignore()
то, что я предлагал ранее в своем ответе.2. «..работайте, потому что получите()…» Да, именно так. Извините, что даю ответ без подробностей.
3. Почему бы просто не
if (getline(std::cin, name) amp;amp; getline(std::cin, state))
сделать это ?
Ответ №3:
Это происходит потому, что неявный поток строк, также известный как символ 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) amp;amp; 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"
Ответ №4:
Поскольку все вышеперечисленные ответили на проблему для ввода 10nMr Whiskern
, я хотел бы ответить на другой подход. все вышеприведенное решение опубликовало код для того, похож ли буфер 10nMr Whiskern
. но что, если мы не знаем, как будет вести себя пользователь, вводя данные. пользователь может ввести 10nnMr. Whiskern
или 10 nn Mr. whiskern
по ошибке. в этом случае приведенные выше коды могут не сработать. итак, я использую функцию ниже, чтобы ввести строку для решения проблемы.
string StringInput() //returns null-terminated string
{
string input;
getline(cin, input);
while(input.length()==0)//keep taking input until valid string is taken
{
getline(cin, input);
}
return input.c_str();
}
Итак, ответ будет таким::
#include <iostream>
#include <string>
int main()
{
int age;
std::string name;
std::cin >> age;
name = StringInput();
std::cout << "My cat is " << age << " years old and their name is " << name << std::endl;
}
Дополнительный:
Если вводит пользователь a n10n nmr. whiskey
; Чтобы проверить, является ли int
ввод допустимым или нет, эта функция может быть использована для проверки int
ввода (программа будет иметь неопределенное поведение, если char
задана в качестве ввода вместо int
):
//instead of "std::cin>>age;" use "get_untill_int(amp;age);" in main function.
void get_Untill_Int(int* pInput)//keep taking input untill input is `int or float`
{
cin>> *pInput;
/*-----------check input validation----------------*/
while (!cin)
{
cin.clear();
cin.ignore(100, 'n');
cout<<"Invalid Input Type.nEnter again: ";
cin >>*pInput;
}
/*-----------checked input validation-------------*/
}
Ответ №5:
Мне действительно интересно. В C есть специальная функция для заполнения любых оставшихся или любых других пробелов. Он называется std::ws. И тогда вы можете просто использовать
std::getline(std::cin >> std::ws, name);
Это должен быть идоматический подход. Для каждого перехода между отформатированным и неформатированным вводом следует использовать taht.