¿Por qué std::getline() omite la entrada después de una extracción formateada?

8 minutos de lectura

¿Por que stdgetline omite la entrada despues de una
david g

Tengo el siguiente código que solicita al usuario la edad y el nombre de su gato:

#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;
    }
}

Lo que encuentro es que se ha leído correctamente la edad, pero no el nombre. Aquí está la entrada y la salida:

Input:

"10"
"Mr. Whiskers"

Output:

"My cat is 10 years old and their name is "

¿Por qué se ha omitido el nombre de la salida? He dado la entrada adecuada, pero el código de alguna manera la ignora. ¿Por qué pasó esto?

  • Yo creo std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state) también debería funcionar como se esperaba. (Además de las respuestas a continuación).

    – jww

    11/11/2018 a las 17:30


¿Por que stdgetline omite la entrada despues de una
david g

¿Por qué pasó esto?

Esto tiene poco que ver con la entrada que proporcionó usted mismo, sino con el comportamiento predeterminado std::getline() posee. Cuando proporcionó su entrada para la edad (std::cin >> age), no solo envió los siguientes caracteres, sino que también se agregó una nueva línea implícita a la secuencia cuando escribió Ingresar:

"10\n"

Siempre se agrega una nueva línea a su entrada cuando selecciona Ingresar o Regreso cuando se envía desde una terminal. También se usa en archivos para pasar a la siguiente línea. La nueva línea se deja en el búfer después de la extracción en age hasta la próxima operación de E/S donde se descarta o se lee. Cuando el flujo de control alcanza std::getline()se verá "\nMr. Whiskers" y la nueva línea al principio se descartará, pero la operación de entrada se detendrá inmediatamente. La razón por la que esto sucede es porque el trabajo de std::getline() es intentar leer caracteres y detenerse cuando encuentra una nueva línea. Entonces, el resto de su entrada se deja en el búfer sin leer.

Solución

cin.ignore()

Para solucionar esto, una opción es omitir la nueva línea antes de hacer std::getline(). Puedes hacerlo llamando std::cin.ignore() después de la primera operación de entrada. Descartará el siguiente carácter (el carácter de nueva línea) para que ya no estorbe.

std::cin >> age;
std::cin.ignore();
std::getline(std::cin, name);

Une las operaciones

Cuando se encuentra con un problema como este, generalmente se debe a que está combinando operaciones de entrada con formato con operaciones de entrada sin formato. Una operación de entrada formateada es cuando toma una entrada y la formatea para un tipo determinado. eso es lo que operator>>() es para. Las operaciones de entrada sin formato son cualquier otra cosa que eso, como std::getline(), std::cin.read(), std::cin.get()etc. Esas funciones no se preocupan por el formato de la entrada y solo procesan texto sin formato.

Si se limita a usar un solo tipo de formato, puede evitar este molesto problema:

// Unformatted I/O
std::string age, name;
std::getline(std::cin, age);
std::getline(std::cin, name);

o

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

Si elige leer todo como cadenas usando las operaciones sin formato, puede convertirlas en los tipos apropiados después.

  • ¿Por qué no simplemente if (getline(std::cin, name) && getline(std::cin, state))?

    – Fred Larson

    19/08/2016 a las 19:41

  • @FredLarson Buen punto. Aunque no funcionaría si la primera extracción es de un número entero o algo que no sea una cadena.

    – David G.

    19/08/2016 a las 20:30


  • Por supuesto, ese no es el caso aquí y no tiene sentido hacer lo mismo de dos maneras diferentes. Para un número entero, puede convertir la línea en una cadena y luego usar std::stoi(), pero entonces no está tan claro que haya una ventaja. Pero tiendo a preferir simplemente usar std::getline() para la entrada orientada a la línea y luego trate de analizar la línea de cualquier manera que tenga sentido. Creo que es menos propenso a errores.

    – Fred Larson

    19/08/2016 a las 20:35

  • @FredLarson De acuerdo. Tal vez agregaré eso si tengo tiempo.

    – David G.

    19/08/2016 a las 20:39

  • @Albin La razón por la que podría querer usar std::getline() es si desea capturar todos los caracteres hasta un delimitador determinado e ingresarlo en una cadena, de forma predeterminada, esa es la nueva línea. si esos X número de cadenas son solo palabras/tokens individuales, entonces este trabajo se puede lograr fácilmente con >>. De lo contrario, ingresaría el primer número en un entero con >>llamada cin.ignore() en la siguiente línea, y luego ejecuta un ciclo donde usas getline().

    – David G.

    3 abr 2020 a las 18:54


1647668349 536 ¿Por que stdgetline omite la entrada despues de una
boris

Todo estará bien si cambia su código inicial de la siguiente manera:

if ((cin >> name).get() && std::getline(cin, state))

  • Gracias. Esto también funcionará porque get() consume el siguiente carácter. También hay (std::cin >> name).ignore() que sugerí anteriormente en mi respuesta.

    – David G.

    26 de marzo de 2014 a las 12:14

  • “… trabajo porque get ()…” Sí, exactamente. Perdón por dar la respuesta sin detalles.

    – Borís

    26 de marzo de 2014 a las 13:14


  • ¿Por qué no simplemente if (getline(std::cin, name) && getline(std::cin, state))?

    – Fred Larson

    19/08/2016 a las 19:41

1647668349 231 ¿Por que stdgetline omite la entrada despues de una
justin randall

Esto sucede porque un avance de línea implícito también conocido como carácter de nueva línea \n se agrega a todas las entradas del usuario desde una terminal, ya que le indica a la transmisión que comience una nueva línea. Puede dar cuenta de esto de forma segura utilizando std::getline al comprobar si hay varias líneas de entrada del usuario. El comportamiento predeterminado de std::getline leerá todo hasta e incluyendo el carácter de nueva línea \n del objeto de flujo de entrada que es std::cin en este caso.

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

¿Por que stdgetline omite la entrada despues de una
Miráz

Dado que todos los anteriores han respondido el problema para la entrada 10\nMr Whisker\n, Me gustaría responder a un enfoque diferente. toda la solución anterior publicó el código para si el búfer es como 10\nMr Whisker\n. pero qué pasa si no sabemos cómo se comportará el usuario al dar su entrada. el usuario puede escribir 10\n\nMr. Whisker\n o 10 \n\n Mr. whisker\n por error. en ese caso, los códigos anteriores pueden no funcionar. entonces, uso la función a continuación para tomar una entrada de cadena para abordar el problema.

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();
}

Entonces, la respuesta sería:

#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;
    
}

Extra:

Si el usuario ingresa a \n10\n \nmr. whiskey; para comprobar si int entrada es válida o no, esta función se puede utilizar para comprobar int entrada (el programa tendrá un comportamiento indefinido si char se da como entrada en lugar de 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-------------*/
}

¿Por que stdgetline omite la entrada despues de una
Armin Montigny

Realmente me estoy preguntando. C ++ tiene una función dedicada para consumir cualquier espacio en blanco restante o lo que sea. Se llama estándar::ws. Y luego, simplemente puede usar

std::getline(std::cin >> std::ws, name);

Ese debería ser el enfoque idomático. Para cada transición entre la entrada con formato y sin formato que se debe utilizar.

Si no estamos hablando de espacios en blanco, sino ingresando, por ejemplo, letras donde se espera un número, entonces debemos seguir la referencia CPP y usar

.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); para eliminar las cosas malas.

Por favor lee aquí

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad