Pourquoi cela arrive-t-il?
Cela n'a pas grand-chose à voir avec l'entrée que vous avez fournie vous-même mais plutôt avec les std::getline()
expositions de comportement par défaut . Lorsque vous avez fourni votre entrée pour le nom ( std::cin >> name
), vous avez non seulement soumis les caractères suivants, mais également 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 Enterou Returnlors de la soumission depuis un terminal. Il est également utilisé dans les fichiers pour passer à la ligne suivante. La nouvelle ligne est laissée dans la mémoire tampon après l'extraction name
jusqu'à la prochaine opération d'E / S où elle est soit rejetée, soit consommée. Lorsque le flux de contrôle atteint std::getline()
, la nouvelle ligne sera rejetée, mais l'entrée cessera immédiatement. La raison pour laquelle cela se produit est que la fonctionnalité par défaut de cette fonction l'exige (elle tente de lire une ligne et s'arrête lorsqu'elle trouve une nouvelle ligne).
Étant donné que cette nouvelle ligne principale inhibe la fonctionnalité attendue de votre programme, il s'ensuit qu'elle doit être ignorée d'une manière ou d'une autre. Une option consiste à appeler std::cin.ignore()
après la première extraction. Il supprimera le prochain caractère disponible pour que la nouvelle ligne ne gêne plus.
std::getline(std::cin.ignore(), state)
Explication détaillée:
C'est la surcharge de ce 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 caractère qui représente la limite entre les séquences d'entrée. Cette surcharge particulière définit le délimiteur sur le caractère de nouvelle ligne input.widen('\n')
par défaut, car il n'en a pas été fourni.
Voici quelques-unes des conditions dans lesquelles se std::getline()
termine l'entrée:
- Si le flux a extrait le nombre maximum de caractères que
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 dont nous traitons. Votre entrée dans state
est représentée ainsi:
"John\nNew Hampshire"
^
|
next_pointer
où next_pointer
est le prochain caractère à analyser. Étant donné que le caractère stocké à la position suivante dans la séquence d'entrée est le délimiteur, std::getline()
supprime silencieusement ce caractère, passe next_pointer
au caractère disponible suivant et arrête l'entrée. Cela signifie que le reste des caractères que vous avez fournis restent 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 vers state
, votre extraction donnera le résultat correct comme dernier appel à std::getline()
ignorer 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>>()
). Cela est dû au fait que les flux d'entrée utilisent des espaces comme délimiteurs pour l'entrée et que le manipulateur std::skipws
1 est activé par défaut. Streams supprimera le premier espace blanc du flux lorsqu'il commence à effectuer une entrée formatée. 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 quelque peu en commun:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Ce qui précède est un objet sentinelle qui est instancié dans toutes les fonctions d'E / S formatées / non formatées dans une implémentation C ++ standard. Les objets Sentry sont utilisés pour préparer le flux pour les E / S et déterminer s'il est ou non en état d'échec. Vous constaterez uniquement que dans les fonctions d'entrée non formatées , le deuxième argument du constructeur sentinelle est true
. Cet argument signifie que les espaces blancs de début ne seront pas supprimés à partir 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 égal à zéro et is.flags() & ios_base::skipws
différent de zéro, la fonction extrait et supprime chaque caractère tant que le prochain caractère d'entrée disponible c
est un caractère d'espacement. [...]
Puisque la condition ci-dessus est fausse, l'objet sentinelle ne rejettera pas l'espace blanc. La raison noskipws
est définie true
par cette fonction est que le but de std::getline()
est de lire des caractères bruts et non formatés dans un std::basic_string<charT>
objet.
La solution:
Il n'y a aucun moyen d'arrêter ce comportement de std::getline()
. Ce que vous devrez faire est de supprimer vous-même la nouvelle ligne avant l' std::getline()
exécution (mais faites-le après l'extraction formatée). Cela peut être fait en utilisant ignore()
pour supprimer 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 devrez inclure <limits>
pour utiliser std::numeric_limits
. std::basic_istream<...>::ignore()
est une fonction qui supprime un nombre spécifié de caractères jusqu'à ce qu'elle trouve un délimiteur ou atteigne la fin du flux ( ignore()
supprime également le délimiteur s'il le trouve). La max()
fonction renvoie le plus grand nombre de caractères qu'un flux peut accepter.
Une autre façon de supprimer l'espace blanc est d'utiliser la std::ws
fonction qui est un manipulateur conçu pour extraire et supprimer les espaces blancs de début depuis 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 supprime les caractères sans discernement jusqu'à ce qu'il rejette les count
caractères, trouve le délimiteur (spécifié par le deuxième argument delim
) ou atteigne la fin du flux. std::ws
est uniquement utilisé pour supprimer les caractères d'espacement depuis le début du flux.
Si vous mélangez une entrée formatée avec une entrée non formatée et que vous devez supprimer les espaces blancs résiduels, utilisez std::ws
. Sinon, si vous devez effacer les entrées non valides quelle que soit leur nature, utilisez ignore()
. Dans notre exemple, nous n'avons besoin que d'effacer les espaces puisque le flux a consommé votre entrée de "John"
pour la name
variable. Tout ce qui restait était le caractère de nouvelle ligne.
1: std::skipws
est un manipulateur qui indique au flux d'entrée de supprimer les espaces de début lors de l'exécution d'une entrée formatée. Cela peut être désactivé avec le std::noskipws
manipulateur.
2: Les flux d'entrée considèrent certains caractères comme des espaces par défaut, tels que le caractère espace, le caractère de nouvelle ligne, le saut de page, le retour chariot, etc.
3: Ceci est la signature de std::basic_istream<...>::ignore()
. Vous pouvez l'appeler avec zéro argument pour supprimer un seul caractère du flux, un argument pour supprimer un certain nombre de caractères ou deux arguments pour supprimer des count
caractères ou jusqu'à ce qu'il atteigne delim
, selon le premier des deux. 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 quand même les ignorer.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
devrait également fonctionner comme prévu. (En plus des réponses ci-dessous).