¿Por qué pasó esto?
Esto tiene poco que ver con la información que proporcionó usted mismo, sino más bien con las std::getline()
exhibiciones de comportamiento predeterminado . 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 agrega una nueva línea a su entrada cuando selecciona Entero Returncuando envía desde una terminal. También se utiliza en archivos para avanzar hacia la siguiente línea. La nueva línea se deja en el búfer después de la extracción name
hasta la siguiente operación de E / S en la que se descarta o se consume. Cuando llegue el flujo de control std::getline()
, se descartará la nueva línea, pero la entrada cesará inmediatamente. 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 principal inhibe la funcionalidad esperada de su programa, se deduce que debe omitirse o ignorarse de alguna manera. Una opción es llamar std::cin.ignore()
después de la primera extracción. Descartará el siguiente carácter disponible para que la nueva línea ya no esté en el camino.
std::getline(std::cin.ignore(), state)
Explicación en profundidad:
Esta es la sobrecarga de lo std::getline()
que llamaste:
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 en 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 transmisión ha extraído la cantidad máxima de caracteres que
std::basic_string<charT>
puede contener
- Si se ha encontrado el carácter de fin de archivo (EOF)
- Si se ha encontrado el delimitador
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 analizará. Dado que 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 siguiente operación de E / S. Notará que si realiza otra lectura desde la línea hacia state
, su extracción producirá el resultado correcto como la última llamada para std::getline()
descartar 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 los flujos de entrada utilizan espacios en blanco como delimitadores para la entrada y tienen el manipulador std::skipws
1 activado de forma predeterminada. Las transmisiones descartarán los espacios en blanco iniciales de la transmisión 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 algo en común el siguiente código:
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 / no formateadas en una implementación estándar de C ++. Los objetos centinela se utilizan para preparar el flujo para E / S y determinar si está en un estado de falla o no. Solo encontrará que en las funciones de entrada sin formato , el segundo argumento del constructor sentry es true
. Ese argumento significa que los espacios en blanco iniciales no se descartarán desde el principio 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
es distinto de cero, la función extrae y descarta cada carácter siempre que el siguiente carácter de entrada disponible c
sea un carácter de espacio en blanco. [...]
Dado que la condición anterior es falsa, el objeto centinela no descartará el espacio en blanco. La razón noskipws
que establece true
esta función es que el objetivo de std::getline()
es leer caracteres sin formato y sin formato en un std::basic_string<charT>
objeto.
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 std::getline()
ejecute (pero hágalo después de la extracción formateada). Esto se puede hacer usando ignore()
para descartar el resto de la entrada hasta que alcancemos 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 max()
función devuelve la mayor cantidad de caracteres que puede aceptar una secuencia.
Otra forma de descartar los espacios en blanco es usar la std::ws
función que es un manipulador diseñado para extraer y descartar los espacios en blanco iniciales desde el principio de un flujo 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 caracteres indiscriminadamente hasta que descarta count
caracteres, encuentra el delimitador (especificado por el segundo argumento delim
) o llega al final de la secuencia. std::ws
solo se utiliza para descartar caracteres de espacio en blanco desde el principio de la transmisión.
Si está mezclando una entrada formateada con una entrada sin formato y necesita descartar los espacios en blanco residuales, use std::ws
. De lo contrario, si necesita borrar una entrada no válida independientemente de cuál sea, utilice ignore()
. En nuestro ejemplo, solo necesitamos borrar los espacios en blanco ya que la transmisión consumió su entrada de "John"
para la name
variable. Todo lo que quedaba era el carácter de nueva línea.
1: std::skipws
es un manipulador que le dice al flujo de entrada que descarte los espacios en blanco iniciales al realizar una entrada formateada. Esto se puede apagar con el std::noskipws
manipulador.
2: Los flujos 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 página, 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 count
caracteres o hasta que llegue delim
, lo que ocurra primero. Normalmente lo 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.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
que también debería funcionar como se esperaba. (Además de las respuestas a continuación).