Tengo el siguiente fragmento de código que solicita 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 se ha 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é pasó esto?
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).Respuestas:
¿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: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 controlstd::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.Explicación en profundidad:
Esta es la sobrecarga de lo
std::getline()
que llamaste: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íneainput.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:std::basic_string<charT>
puede contenerLa tercera condición es la que estamos tratando. Su entrada en
state
se representa así: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 haciastate
, su extracción producirá el resultado correcto como la última llamada parastd::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 manipuladorstd::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. 2A 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: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]:Dado que la condición anterior es falsa, el objeto centinela no descartará el espacio en blanco. La razón
noskipws
que establecetrue
esta función es que el objetivo destd::getline()
es leer caracteres sin formato y sin formato en unstd::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 sestd::getline()
ejecute (pero hágalo después de la extracción formateada). Esto se puede hacer usandoignore()
para descartar el resto de la entrada hasta que alcancemos una nueva línea nueva:Deberá incluir
<limits>
para usarstd::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). Lamax()
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:¿Cual es la diferencia?
La diferencia es que
ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 descarta caracteres indiscriminadamente hasta que descartacount
caracteres, encuentra el delimitador (especificado por el segundo argumentodelim
) 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, utiliceignore()
. En nuestro ejemplo, solo necesitamos borrar los espacios en blanco ya que la transmisión consumió su entrada de"John"
para laname
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 elstd::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 descartarcount
caracteres o hasta que lleguedelim
, lo que ocurra primero. Normalmente lo usastd::numeric_limits<std::streamsize>::max()
como valor decount
si no sabe cuántos caracteres hay antes del delimitador, pero desea descartarlos de todos modos.fuente
if (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, pero no está tan claro que haya una ventaja. Pero tiendo a preferir usar solostd::getline()
para entrada orientada a líneas y luego tratar con analizar la línea de la manera que tenga sentido. Creo que es menos propenso a errores.std::getline()
es si desea capturar todos los caracteres hasta un delimitador dado e ingresarlo en una cadena, por defecto es la nueva línea. Si eseX
número de cadenas son solo palabras / tokens, entonces este trabajo se puede realizar fácilmente con>>
. De lo contrario, ingresaría el primer número en un entero con>>
, llamaríacin.ignore()
en la siguiente línea y luego ejecutaría un bucle donde usegetline()
.Todo estará bien si cambia su código inicial de la siguiente manera:
fuente
get()
consume el siguiente carácter. También hay(std::cin >> name).ignore()
lo que sugerí anteriormente en mi respuesta.if (getline(std::cin, name) && getline(std::cin, state))
?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 dice a la secuencia que comience una nueva línea. Puede tener esto en cuenta de forma segura utilizandostd::getline
cuando compruebe varias líneas de entrada de usuario. El comportamiento predeterminado destd::getline
leerá todo hasta e incluido el carácter de nueva línea\n
del objeto de flujo de entrada que esstd::cin
en este caso.fuente