variable sirve que para leer dev c++ input iostream istream c++-faq

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



getline(cin variable) (3)

¿Por qué pasó esto?

Esto tiene poco que ver con la entrada que proporcionó usted mismo, sino más bien con el comportamiento predeterminado std::getline() exhibits. 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 transmisión:

"John/n"

Siempre se agrega una nueva línea a su entrada cuando selecciona Ingresar o Volver al enviar desde una terminal. También se usa en archivos para avanzar hacia la siguiente línea. La línea nueva se deja en el búfer después de la extracción en el name hasta la siguiente operación de E / S donde se descarta o se consume. Cuando el flujo de control llegue a std::getline() , la nueva línea se descartará, 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 líder inhibe la funcionalidad esperada de su programa, se deduce que debe ignorarse de alguna manera. Una opción es llamar a std::cin.ignore() después de la primera extracción. Se descartará el siguiente carácter disponible para que la nueva línea ya no sea intrusiva.

Explicación en profundidad:

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

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 que std::getline() finaliza la entrada:

  • Si la transmisión ha extraído la cantidad máxima de caracteres, 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 el state se representa así:

"John/nNew Hampshire" ^ | next_pointer

donde next_pointer es el siguiente personaje a analizar. Dado que el carácter almacenado en la siguiente posición en la secuencia de entrada es el delimitador, std::getline() descartará ese carácter en silencio, 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 al state , su extracción arrojará el resultado correcto ya que la última llamada a std::getline() descartó el delimitador.

Puede haber 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 manera predeterminada. Las transmisiones descartarán el espacio en blanco inicial de la transmisión al comenzar 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 no formateadas 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 crea una instancia en todas las funciones de E / S formateadas / sin formatear en una implementación estándar de C ++. Los objetos centinela se utilizan para preparar el flujo de E / S y determinar si está o no en estado de falla. Solo encontrará que en las funciones de entrada no formateadas , el segundo argumento para el constructor de 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 del Estándar [§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. [...]

Como la condición anterior es falsa, el objeto centinela no descartará el espacio en blanco. El motivo por el que noskipws se establece como true por esta función es porque el punto de std::getline() es para leer caracteres sin formato, sin procesar, 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 std::getline() ejecute (pero hágalo después de la extracción formateada). Esto se puede hacer utilizando ignore() para descartar el resto de la entrada hasta que lleguemos 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 espacios en blanco iniciales 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 caracteres hasta descartar los caracteres de count , encuentra el delimitador (especificado por el segundo argumento delim ) o golpea el final de la corriente. std::ws solo se usa para descartar caracteres en espacios en blanco desde el comienzo de la transmisión.

Si está mezclando una entrada formateada con una entrada sin formato y necesita descartar espacio en blanco residual, use std::ws . De lo contrario, si necesita borrar una entrada no válida independientemente de cuál 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 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 al realizar una entrada formateada. Esto se puede desactivar con el manipulador std::noskipws .

2: las secuencias de entrada consideran determinados 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 los caracteres de count o hasta que llegue a delim , lo que ocurra primero. Normalmente utiliza std::numeric_limits<std::streamsize>::max() como el valor del count si no sabe cuántos caracteres hay antes del delimitador, pero desea descartarlos de todos modos.

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 información adecuada, pero el código de alguna manera lo ignora. ¿Por qué pasó esto?


Esto sucede porque una línea de alimentación implícita también conocida como carácter de nueva línea /n se agrega a toda la entrada del usuario desde un terminal, ya que le dice a la transmisión que comience una nueva línea. Puede contabilizar esto de forma segura utilizando std::getline al verificar múltiples 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"


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

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