Tengo el siguiente trozo de código que se 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 me parece es que el nombre se ha extraído correctamente, 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é el nombre de el estado ha omitido de la salida? He dado la entrada correcta, pero el código de alguna manera lo ignora. ¿Por qué sucede esto?

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

OriginalEl autor 0x499602D2 | 2014-02-05

3 Comentarios

  1. 91

    ¿Por qué sucede esto?

    Esto tiene poco que ver con la entrada que se proporciona a sí mismo, sino con el comportamiento predeterminado std::getline() exposiciones. Cuando usted proporciona su entrada para el nombre (std::cin >> name), que no sólo presentó los siguientes personajes, pero también implícito un salto de línea se anexa a la corriente:

    "John\n"

    Una nueva línea siempre se anexan a su entrada cuando se selecciona Entrar o Regresar cuando la presentación de un terminal. También se utiliza en los archivos para avanzar hacia la siguiente línea. La nueva línea se queda en el buffer después de la extracción en name hasta la siguiente operación de e/S donde se descarta o se consume. Cuando el flujo de control alcanza std::getline(), la nueva línea será descartado, pero la entrada cesará de inmediato. La razón por la que esto sucede es porque la funcionalidad por defecto de esta función dicta que se debe (intenta leer una línea y se detiene cuando encuentra una nueva línea).

    Porque este líder newline inhibe la funcionalidad esperada de su programa, se sigue que debe ser omitido nuestro ignorado de alguna manera. Una opción es llamar a std::cin.ignore() después de la primera extracción. Se descartará la siguiente carácter de modo que el salto de línea es no intrusiva.


    Explicación Detallada:

    Esta es la sobrecarga de std::getline() que se llama:

    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 personaje que representa el límite entre las secuencias de entrada. Este particular sobrecarga establece el delimitador para el carácter de nueva línea input.widen('\n') por defecto, ya que no fue suministrado.

    Ahora, estas son algunas de las condiciones para que los std::getline() termina de entrada:

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

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

    "John\nNew Hampshire"
         ^
         |
     next_pointer

    donde next_pointer es el siguiente carácter que se va a analizar. Dado el carácter almacenado en la siguiente posición en la secuencia de entrada es el delimitador, std::getline() silencio descartar que el carácter, el incremento de next_pointer a la siguiente carácter, y la entrada de paro. Esto significa que el resto de los personajes que han proporcionado todavía permanecen en el búfer para la siguiente operación de e/S. Te darás cuenta que si realiza otra lectura de la línea en state, su extracción dará el resultado correcto, como la última llamada a std::getline() descartado el delimitador.


    Usted puede haber notado que, en general, no caer en este problema a la hora de extraer con el formato de entrada del operador (operator>>()). Esto es debido a que los flujos de entrada el uso de espacios como delimitadores para la entrada y tiene la std::skipws1 manipulador de establecer por defecto. Los flujos de descartar los principales espacios en blanco de la secuencia cuando se empieza a realizar formato de entrada.2

    A diferencia del formato de entrada de los operadores, std::getline() es un sin formato función de entrada. Y todo sin funciones de entrada tiene el siguiente código algo en común:

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

    El de arriba es un centinela objeto que es una instancia en todos los formato/formato de funciones de e/S en un estándar de la implementación en C++. Centinela de los objetos se utilizan para la preparación de la corriente de I/O y determinar si está o no en un error de estado. Sólo se va a encontrar que en el sin formato funciones de entrada, el segundo argumento de la centinela constructor es true. Ese argumento no significa que el líder en el espacio en blanco se no ser descartado desde el principio de la secuencia de entrada. Aquí es pertinente la cita 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 es distinto de cero, la función extrae y se desecha a cada personaje, mientras que el siguiente carácter de entrada c es un carácter de espacio en blanco. […]

    Desde la condición anterior es falso, el centinela objeto de no descartar el espacio en blanco. La razón noskipws se establece en true por esta función es debido a que el punto de std::getline() es leer raw, formato de caracteres en un std::basic_string<charT> objeto.


    La Solución:

    No hay manera de detener este comportamiento de std::getline(). Lo que tienes que hacer es desechar la nueva línea antes de std::getline() carreras (pero hacerlo después de el formato de extracción). Esto se puede hacer mediante el uso de ignore() para descartar el resto de la entrada hasta llegar a una nueva línea:

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

    Deberá incluir <limits> utilizar std::numeric_limits. std::basic_istream<...>::ignore() es una función que descarta una cantidad especificada de caracteres hasta que encuentra un delimitador o llega al final de la secuencia (ignore() también descarta el delimitador si la encuentra). El max() función devuelve la mayor cantidad de caracteres que una corriente puede aceptar.

    Otra manera de descartar el espacio en blanco es el uso de la std::ws función de que es un manipulador diseñado para extraer y descartar los principales espacios en blanco desde el principio de una secuencia de entrada:

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

    ¿Cuál es la diferencia?

    La diferencia es que ignore(std::streamsize count = 1, int_type delim = Traits::eof())3 indiscriminadamente descartes caracteres hasta que descartes count personajes, encuentra el delimitador (especificado por el segundo argumento delim) o llega al final de la secuencia. std::ws sólo se utiliza para descartar caracteres de espacios en blanco del principio de la secuencia.

    Si se mezcla con formato de entrada con formato de entrada y usted tendrá que descartar residual espacio en blanco, utilice std::ws. De lo contrario, si usted necesita para borrar la entrada no válida independientemente de lo que es, el uso de ignore(). En nuestro ejemplo, sólo tenemos que borrar los espacios en blanco ya que la corriente consumida su entrada de "John" para la name variable. Todo lo que quedaba era el carácter de nueva línea.


    1: std::skipws es manipulador que indica la secuencia de entrada para descartar los principales espacios en blanco cuando se realiza con formato de entrada. Esto puede ser desactivado con el std::noskipws manipulador.

    2: flujos de Entrada consideren ciertos caracteres como los espacios en blanco de forma predeterminada, el carácter de espacio, el carácter de nueva línea, forma de alimentación, retorno de carro, etc.

    3: Esta es la firma de std::basic_istream<...>::ignore(). Usted puede llamar 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 count caracteres o hasta que se llega a delim, cualquiera que ocurra primero. Normalmente uso std::numeric_limits<std::streamsize>::max() como el valor de count si usted no sabe cómo muchos de los personajes que hay antes de que el delimitador, pero desea deshacerse de ellos de todos modos.

    ¿Por qué no simplemente if (getline(std::cin, name) && getline(std::cin, state))?
    Buen punto. Aunque no iba a funcionar si la primera extracción es de un entero o cualquier cosa que no sea una cadena.
    Por supuesto, ese no es el caso aquí y no hay ningún punto en lo mismo de dos maneras diferentes. Para un entero se puede obtener la línea en una cadena y, a continuación, utilizar std::stoi(), pero entonces no es tan claro que no es una ventaja. Pero yo tiendo a preferir el uso de std::getline() para la línea orientada a la entrada y, a continuación, tratar con el análisis de la línea en cualquier forma que tenga sentido. Creo que es menos propenso a errores.
    De acuerdo. Tal vez voy a agregar que en si tengo tiempo.
    Gran explicación, +1.

    OriginalEl autor 0x499602D2

  2. 10

    Todo va a estar bien si usted cambia su código inicial de la siguiente manera:

    if ((cin >> name).get() && std::getline(cin, state))
    Gracias. Esto también funciona porque get() consume el siguiente carácter. También hay (std::cin >> name).ignore() que he indicado antes, en mi respuesta.
    «..el trabajo porque get()…» Sí, exactamente. Lo siento por dar la respuesta, sin más detalles.
    ¿Por qué no simplemente if (getline(std::cin, name) && getline(std::cin, state))?

    OriginalEl autor Boris

  3. 0

    Esto sucede porque implícito un avance de línea, también conocido como el carácter de nueva línea \n se anexa a todas las entradas de usuario de un terminal como se dice de la corriente a iniciar una nueva línea. Usted puede de manera segura cuenta de esto mediante el uso de std::getline cuando la comprobación de varias líneas de entrada del usuario. El comportamiento predeterminado de std::getline va a leer de todo, hasta e incluyendo el carácter de nueva línea \n de la secuencia de entrada de objeto que se 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"

    OriginalEl autor Justin Randall

Dejar respuesta

Please enter your comment!
Please enter your name here