Pourquoi std:: getline() saute l'ENTRÉE après une extraction formatée?

j'ai le code suivant qui demande à l'utilisateur son nom et son état:

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

Ce que je trouve est que le nom a été extrait, mais pas l'état. Voici l'entrée et la sortie:

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

Pourquoi le nom de l'état été omis de la sortie? J'ai donné la bonne entrée, mais le code l'ignore. Pourquoi est-ce arrivé?

75
demandé sur πάντα ῥεῖ 2014-02-05 06:01:25

3 réponses

pourquoi cela arrive-t-il?

cela n'a rien à voir avec l'entrée que vous vous êtes donnée mais plutôt avec le comportement par défaut std::getline() . Lorsque vous avez fourni votre entrée pour le nom ( std::cin >> name ), vous avez non seulement soumis les caractères suivants, mais aussi une nouvelle ligne implicite a été ajoutée au flux:

"John\n"

une nouvelle ligne est toujours ajoutée à votre entrée lorsque vous sélectionnez Inscrivez ou retour lors de la soumission à partir d'un terminal. Il est également utilisé dans les fichiers pour avancer vers la ligne suivante. La ligne est laissée dans le tampon après l'extraction dans name jusqu'à la prochaine opération d'entrée/sortie où elle est soit jetée ou consommée. Lorsque le flux de contrôle atteint std::getline() , la nouvelle ligne sera ignoré, mais l'entrée cessera immédiatement. La raison à cela est parce que la fonctionnalité par défaut de cette fonction dicte qu'il devrait (il tente de lire une ligne et s'arrête lorsqu'il trouve un saut de ligne).

parce que cette nouvelle de premier plan inhibe la fonctionnalité attendue de votre programme, il s'ensuit qu'il doit être sauté notre ignoré en quelque sorte. Une option est d'appeler std::cin.ignore() après la première extraction. Il rejettera le prochain personnage disponible afin que la nouvelle ligne ne soit plus intrusive.


Explication Détaillée:

C'est la surcharge de std::getline() que vous avez appelé:

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

une autre surcharge de cette fonction prend un délimiteur de type charT . Un caractère délimiteur est un personnage qui représente la limite entre les séquences d'entrée. Cette surcharge particulière place par défaut le délimiteur sur le caractère newline input.widen('\n') , puisqu'il n'a pas été fourni.

Maintenant, ce sont quelques-uns des conditions dans lesquelles std::getline() met fin à l'entrée:

  • si le flux a extrait la quantité maximale de caractères a std::basic_string<charT> peut contenir
  • si le caractère de fin de fichier (EOF) a été trouvé
  • si le délimiteur a été trouvé

la troisième condition est celle que nous traitons. Votre entrée dans state est représentée ainsi:

"John\nNew Hampshire"
     ^
     |
 next_pointer

next_pointer est le prochain caractère à être interprété. Puisque le caractère stocké à la position suivante dans la séquence d'entrée est le délimiteur, std::getline() va discrètement jeter ce caractère, incrémenter next_pointer au prochain caractère disponible, et arrêter l'entrée. Cela signifie que le reste des personnages que vous avez fournies restent encore dans la mémoire tampon pour la prochaine opération d'e/S. Vous remarquerez que si vous effectuez une autre lecture de la ligne dans state , votre extraction donnera le bon résultat puisque le dernier appel à std::getline() a écarté le délimiteur.


vous avez peut-être remarqué que vous ne rencontrez généralement pas ce problème lors de l'extraction avec l'opérateur d'entrée formaté ( operator>>() ). Ceci est dû au fait que les flux d'entrée utilisent des espaces blancs comme délimiteurs pour l'entrée et ont le std::skipws 1 manipulateur activé par défaut. Les flux seront jeter l'espace principal depuis le flux lorsque vous commencez à effectuer des entrées formatées. 2

Contrairement aux opérateurs d'entrée formatés, std::getline() est une fonction d'entrée non formatée . Et toutes les fonctions d'entrée non formatées ont le code suivant un peu en commun:

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

ci-dessus est une sentinelle de l'objet qui est instancié dans tous formatés/sans mise en forme des fonctions d'e/S standard C++ application. Les objets Sentry sont utilisés pour préparer le flux pour l'entrée/sortie et déterminer si oui ou non il est dans un État de défaillance. Vous ne trouverez que dans les fonctions d'entrée unformatted , le second argument du constructeur sentry est true . Cet argument signifie que les blancs de tête et non seront écartés du début de la séquence d'entrée. Voici la citation pertinente de la norme [§27.7.2.1.3 / 2]:

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

[...] Si noskipws est zéro et is.flags() & ios_base::skipws est non nul, la fonction extrait et écarte chaque caractère tant que le prochain caractère d'entrée disponible c est un caractère d'espace. [...]

puisque la condition ci-dessus est fausse, l'objet sentinelle ne se défausse pas de l'espace. La raison noskipws est définie à true par cette fonction est parce que le point de std::getline() est lisez les caractères bruts non formatés dans un objet std::basic_string<charT> .


La Solution:

Il n'y a aucun moyen d'arrêter ce comportement de std::getline() . Ce que vous aurez à faire est de jeter la nouvelle ligne vous-même avant std::getline() exécute (mais le faire après l'extraction formatée). Cela peut être fait en utilisant ignore() pour rejeter le reste de l'entrée jusqu'à ce que nous atteignions une nouvelle ligne:

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

vous devez inclure <limits> pour utiliser std::numeric_limits . std::basic_istream<...>::ignore() est une fonction qui écarte un certain nombre de caractères jusqu'à ce qu'elle trouve un délimiteur ou atteigne la fin du flux ( ignore() écarte également le délimiteur si elle le trouve). La fonction max() renvoie le plus grand nombre de caractères qu'un flux peut accepter.

une autre façon de se débarrasser de l'espace blanc est d'utiliser la fonction std::ws qui est un manipulateur conçu pour extraire et rejeter les espaces principaux dès le début d'un flux d'entrée:

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

Quelle est la différence?

la différence est que ignore(std::streamsize count = 1, int_type delim = Traits::eof()) 3 écarte les caractères sans discernement jusqu'à ce qu'il écarte count les caractères, trouve le délimiteur (spécifié par le deuxième argument delim ) ou frappe la fin du flux. std::ws n'est utilisé que pour rejeter les caractères blancs depuis le début du flux.

si vous mélangez des entrées formatées avec des entrées non formatées et que vous devez éliminer les espaces résiduels, utilisez std::ws . Dans le cas contraire, si vous devez effacer les entrées invalides, utilisez ignore() . Dans notre exemple, nous n'avons qu'à effacer les espaces puisque le flux a consommé votre entrée de "John" pour la variable name . Tout ce qui était à gauche était le personnage de newline.


1: std::skipws est un manipulateur qui dit au flux d'entrée de rejeter les espaces principaux lors de l'exécution d'une entrée formatée. Cela peut être désactivé avec le manipulateur std::noskipws .

2: les flux entrants considèrent certains caractères comme des espaces par défaut, tels que le caractère d'espace, le caractère de ligne, le flux de forme, le retour de chariot, etc.

3: c'est la signature de std::basic_istream<...>::ignore() . Vous pouvez l'appeler avec des arguments zéro pour rejeter un seul caractère du flux, un argument pour rejeter un certain nombre de caractères, ou deux arguments pour rejeter count caractères ou jusqu'à ce qu'il atteigne delim , celui qui vient en premier. Vous utilisez normalement std::numeric_limits<std::streamsize>::max() comme valeur de count si vous ne savez pas combien de caractères il y a avant le délimiteur, mais vous voulez les jeter de toute façon.

88
répondu 0x499602D2 2016-08-02 03:56:26

tout ira bien si vous changez votre code initial de la façon suivante:

if ((cin >> name).get() && std::getline(cin, state))
10
répondu Boris 2014-03-26 12:21:13

cela se produit parce qu'un flux de ligne implicite aussi connu sous le nom de caractère newline \n est ajouté à toutes les entrées de l'utilisateur à partir d'un terminal car il dit au flux de démarrer une nouvelle ligne. Vous pouvez en tenir compte en toute sécurité en utilisant std::getline lors de la vérification de plusieurs lignes d'entrée utilisateur. Le comportement par défaut de std::getline Lira tout jusqu'à et y compris le caractère newline \n de l'objet input stream qui est std::cin dans ce cas.

#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"
0
répondu Justin Randall 2018-02-21 01:19:49