Почему std::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 "

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


person 0x499602D2    schedule 05.02.2014    source источник
comment
Я считаю, что std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state) также должен работать должным образом. (В дополнение к ответам ниже).   -  person jww    schedule 11.11.2018


Ответы (4)


Почему это происходит?

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

"10\n"

К вашему вводу всегда добавляется новая строка, когда вы выбираете 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);

or

// Formatted I/O
int age;
std::string first_name, last_name;
std::cin >> age >> first_name >> last_name;

Если вы решите читать все как строки, используя неформатированные операции, вы можете впоследствии преобразовать их в соответствующие типы.

person 0x499602D2    schedule 05.02.2014
comment
Почему бы не просто if (getline(std::cin, name) && getline(std::cin, state))? - person Fred Larson; 19.08.2016
comment
@FredLarson Хороший вопрос. Хотя это не сработает, если первое извлечение имеет целое число или что-то, что не является строкой. - person 0x499602D2; 19.08.2016
comment
Конечно, здесь это не так, и нет смысла делать одно и то же двумя разными способами. Для целого числа вы можете преобразовать строку в строку, а затем использовать std::stoi(), но тогда не так ясно, есть ли преимущество. Но я предпочитаю просто использовать std::getline() для линейного ввода, а затем заниматься синтаксическим анализом строки любым удобным способом. Я думаю, что это менее подвержено ошибкам. - person Fred Larson; 19.08.2016
comment
@FredLarson Согласен. Может быть, я добавлю это, если у меня будет время. - person 0x499602D2; 19.08.2016
comment
Так что, если бы у меня была программа, которая сначала вводит число X, а затем запускает цикл, который вводит строки X количество раз, как бы я это сделал? Должен ли я написать код первой итерации вне цикла с помощью cin.ignore(), а затем запустить цикл X-1 раз? - person Albin; 03.04.2020
comment
@Albin Причина, по которой вы можете использовать std::getline(), заключается в том, что вы хотите захватить все символы до заданного разделителя и ввести их в строку, по умолчанию это новая строка. Если эти X строки представляют собой просто отдельные слова/токены, то эту работу можно легко выполнить с помощью >>. В противном случае вы должны ввести первое число в целое число с >>, вызвать cin.ignore() в следующей строке, а затем запустить цикл, в котором вы используете getline(). - person 0x499602D2; 03.04.2020

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

if ((cin >> name).get() && std::getline(cin, state))
person Boris    schedule 26.03.2014
comment
Спасибо. Это также будет работать, потому что get() потребляет следующий символ. Есть также (std::cin >> name).ignore(), который я предложил ранее в своем ответе. - person 0x499602D2; 26.03.2014
comment
..работать, потому что get()... Да, именно так. Извините за ответ без подробностей. - person Boris; 26.03.2014
comment
Почему бы не просто if (getline(std::cin, name) && getline(std::cin, state))? - person Fred Larson; 19.08.2016

Это происходит из-за того, что неявный перевод строки, также известный как символ новой строки \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"
person Justin Randall    schedule 21.02.2018

Поскольку все выше ответили на проблему для ввода 10\nMr Whisker\n, я хотел бы ответить на другой подход. все вышеприведенное решение опубликовало код, если буфер похож на 10\nMr Whisker\n. но что, если мы не знаем, как пользователь будет вести себя при вводе данных. пользователь может ввести 10\n\nMr. Whisker\n или 10 \n\n Mr. whisker\n по ошибке. в этом случае приведенные выше коды могут не работать. поэтому я использую приведенную ниже функцию для ввода строки для решения проблемы.

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 \n10\n \nmr. whiskey; Чтобы проверить, является ли ввод int действительным или нет, эту функцию можно использовать для проверки ввода int (программа будет иметь неопределенное поведение, если в качестве ввода задано char вместо int):


//instead of "std::cin>>age;" use "get_untill_int(&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-------------*/
}
person Miraz    schedule 13.06.2021