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

c++ input iostream istream c++-faq


Tengo el siguiente código que le pide al usuario su nombre y estado:

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

Lo que encuentro es que el nombre ha sido extraído con éxito,pero no el estado.Aquí está la entrada y la salida resultante:

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

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





Answer 1 0x499602D2


¿Por qué sucede esto?

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

"John\n"

Siempre se añade una nueva línea a la entrada cuando se seleccionaEnterorReturncuando se envía desde una terminal. También se usa en archivos para moverse hacia la siguiente línea. La nueva línea se deja en el búfer después de la extracción en el name hasta la próxima operación de E / S donde se descarta o se consume. Cuando el flujo de control alcanza std::getline() , la nueva línea se descartará, pero la entrada cesará de inmediato. La razón por la que esto sucede es porque la funcionalidad predeterminada de esta función dicta que debería (intenta leer una línea y se detiene cuando encuentra una nueva línea).

Debido a que esta nueva línea líder inhibe la funcionalidad esperada de su programa, se deduce que se debe omitir nuestro ignorado de alguna manera. Una opción es llamar a std::cin.ignore() después de la primera extracción. Descartará el siguiente personaje disponible para que la nueva línea ya no esté en el camino.

std::getline(std::cin.ignore(), state)

Explicación detallada:

Esta es la sobrecarga de std::getline() que llamó:

template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
                                    std::basic_string<charT>& str )

Otra sobrecarga de esta función toma un delimitador de tipo charT . Un carácter delimitador es un carácter que representa el límite entre las secuencias de entrada. Esta sobrecarga particular establece el delimitador en el carácter de nueva línea input.widen('\n') de forma predeterminada ya que no se proporcionó uno.

Ahora, estas son algunas de las condiciones por las cuales std::getline() termina la entrada:

  • Si la secuencia ha extraído la cantidad máxima de caracteres que puede contener un std::basic_string<charT>
  • Si se ha encontrado el carácter de fin de archivo (EOF)
  • Si se ha encontrado el delimitador

La tercera condición es con la que estamos lidiando. Su entrada en state se representa así:

"John\nNew Hampshire"
     ^
     |
 next_pointer

donde next_pointer es el siguiente carácter a analizar. Como el carácter almacenado en la siguiente posición en la secuencia de entrada es el delimitador, std::getline() descartará silenciosamente ese carácter, incrementará next_pointer al siguiente carácter disponible y detendrá la entrada. Esto significa que el resto de los caracteres que ha proporcionado aún permanecen en el búfer para la próxima operación de E / S. Notará que si realiza otra lectura desde la línea al state , su extracción arrojará el resultado correcto ya que la última llamada a std::getline() descartó el delimitador.


Es posible que haya notado que normalmente no se encuentra con este problema al extraer con el operador de entrada formateado ( operator>>() ). Esto se debe a que las secuencias de entrada utilizan espacios en blanco como delimitadores para la entrada y tienen el manipulador std::skipws 1 activado de forma predeterminada. Las secuencias descartarán el espacio en blanco inicial de la secuencia cuando comience a realizar una entrada formateada. 2

A diferencia de los operadores de entrada formateados, std::getline() es una función de entrada sin formato . Y todas las funciones de entrada sin formato tienen el siguiente código algo en común:

typename std::basic_istream<charT>::sentry ok(istream_object, true);

Lo anterior es un objeto centinela que se instancia en todas las funciones de E / S formateadas / sin formato en una implementación estándar de C ++. Los objetos centinela se usan para preparar la secuencia para E / S y determinar si está o no en estado de falla. Solo encontrará que en las funciones de entrada sin formato , el segundo argumento para el constructor centinela es true . Ese argumento significa que los espacios en blanco iniciales no se descartarán desde el comienzo de la secuencia de entrada. Aquí está la cita relevante de la Norma [§27.7.2.1.3 / 2]:

 explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

[...] Si noskipws es cero y is.flags() & ios_base::skipws no es cero, la función extrae y descarta cada carácter siempre que el siguiente carácter de entrada disponible c sea ​​un espacio en blanco. [...]

Como la condición anterior es falsa, el objeto centinela no descartará el espacio en blanco. La razón por la cual noskipws se establece en true por esta función es porque el objetivo de std::getline() es leer caracteres sin formato y sin formato en un objeto std::basic_string<charT> .


La solución:

No hay forma de detener este comportamiento de std::getline() . Lo que tendrá que hacer es descartar la nueva línea usted mismo antes de que se ejecute std::getline() (pero hágalo después de la extracción formateada). Esto se puede hacer usando ignore() para descartar el resto de la entrada hasta llegar a una nueva línea nueva:

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

Deberá incluir <limits> para usar std::numeric_limits . std::basic_istream<...>::ignore() es una función que descarta una cantidad específica de caracteres hasta que encuentra un delimitador o llega al final de la secuencia ( ignore() también descarta el delimitador si lo encuentra) . La función max() devuelve la mayor cantidad de caracteres que una secuencia puede aceptar.

Otra forma de descartar el espacio en blanco es usar la función std::ws , que es un manipulador diseñado para extraer y descartar el espacio en blanco inicial desde el comienzo de una secuencia de entrada:

if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }

¿Cual es la diferencia?

La diferencia es que ignore(std::streamsize count = 1, int_type delim = Traits::eof()) 3 descarta indiscriminadamente los caracteres hasta que descarta el count caracteres, encuentra el delimitador (especificado por el segundo argumento delim ) o llega al final de la corriente. std::ws solo se usa para descartar caracteres de espacio en blanco desde el comienzo de la secuencia.

Si está mezclando entradas formateadas con entradas no formateadas y necesita descartar espacios en blanco residuales, use std::ws . De lo contrario, si necesita borrar la entrada no válida independientemente de lo que sea, use ignore() . En nuestro ejemplo, solo necesitamos borrar espacios en blanco ya que la secuencia consumió su entrada de "John" para la variable de name . Todo lo que quedaba era el personaje de nueva línea.


1: std::skipws es un manipulador que le dice a la secuencia de entrada que descarte los espacios en blanco al realizar la entrada formateada. Esto se puede desactivar con el manipulador std::noskipws .

2: Las secuencias de entrada consideran ciertos caracteres como espacios en blanco de forma predeterminada, como el carácter de espacio, el carácter de nueva línea, el avance de formulario, el retorno de carro, etc.

3: Esta es la firma de std::basic_istream<...>::ignore() . Puede llamarlo con cero argumentos para descartar un solo carácter de la secuencia, un argumento para descartar una cierta cantidad de caracteres, o dos argumentos para descartar el count caracteres o hasta que llegue a la delim itación , lo que ocurra primero. Normalmente usa std::numeric_limits<std::streamsize>::max() como valor de count si no sabe cuántos caracteres hay antes del delimitador, pero desea descartarlos de todos modos.




Answer 2 Boris


Todo estará bien si cambias tu código inicial de la siguiente manera:

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



Answer 3 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 un terminal, ya que le indica a la secuencia que comience una nueva línea. Puede dar cuenta de esto de manera segura utilizando std::getline cuando verifica múltiples líneas de entrada del usuario. El comportamiento predeterminado de std::getline leerá todo, incluido 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"