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

#c #c #input #stdin #fgets

Вопрос:

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

 #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.