J'ai le morceau de 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, c'est que le nom a été extrait avec succès, mais pas l'état. Voici l'entrée et la sortie résultante:
Input: "John" "New Hampshire" Output: "Your name is John and you live in "
Pourquoi le nom de l'état a-t-il été omis de la sortie? J'ai donné la bonne entrée, mais le code l'ignore en quelque sorte. Pourquoi cela arrive-t-il?
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).Réponses:
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: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 atteintstd::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.Explication détaillée:
C'est la surcharge de ce
std::getline()
que vous avez appelé: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 ligneinput.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:std::basic_string<charT>
peut contenirLa troisième condition est celle dont nous traitons. Votre entrée dans
state
est représentée ainsi: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, passenext_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 versstate
, 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 manipulateurstd::skipws
1 est activé par défaut. Streams supprimera le premier espace blanc du flux lorsqu'il commence à effectuer une entrée formatée. 2Contrairement 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: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]:Puisque la condition ci-dessus est fausse, l'objet sentinelle ne rejettera pas l'espace blanc. La raison
noskipws
est définietrue
par cette fonction est que le but destd::getline()
est de lire des caractères bruts et non formatés dans unstd::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 utilisantignore()
pour supprimer le reste de l'entrée jusqu'à ce que nous atteignions une nouvelle ligne:Vous devrez inclure
<limits>
pour utiliserstd::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). Lamax()
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: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 lescount
caractères, trouve le délimiteur (spécifié par le deuxième argumentdelim
) 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, utilisezignore()
. Dans notre exemple, nous n'avons besoin que d'effacer les espaces puisque le flux a consommé votre entrée de"John"
pour laname
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 lestd::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 descount
caractères ou jusqu'à ce qu'il atteignedelim
, selon le premier des deux. Vous utilisez normalementstd::numeric_limits<std::streamsize>::max()
comme valeur decount
si vous ne savez pas combien de caractères il y a avant le délimiteur, mais vous voulez quand même les ignorer.la source
if (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, mais ce n'est pas si clair qu'il y a un avantage. Mais j'ai tendance à préférer uniquement utiliserstd::getline()
pour une entrée orientée ligne, puis à analyser la ligne de la manière qui a du sens. Je pense que c'est moins sujet aux erreurs.std::getline()
est si vous voulez capturer tous les caractères jusqu'à un délimiteur donné et le saisir dans une chaîne, par défaut, c'est la nouvelle ligne. Si ceX
nombre de chaînes ne sont que des mots / jetons uniques, ce travail peut être facilement accompli avec>>
. Sinon, vous saisissez le premier nombre dans un entier avec>>
, appelezcin.ignore()
sur la ligne suivante, puis exécutez une boucle où vous utilisezgetline()
.Tout ira bien si vous modifiez votre code initial de la manière suivante:
la source
get()
consomme le personnage suivant. Il y a aussi(std::cin >> name).ignore()
ce que j'ai suggéré plus tôt dans ma réponse.if (getline(std::cin, name) && getline(std::cin, state))
?Cela se produit car un saut de ligne implicite également connu sous le nom de caractère de nouvelle ligne
\n
est ajouté à toutes les entrées utilisateur d'un terminal car il indique au flux de commencer une nouvelle ligne. Vous pouvez en tenir compte en toute sécurité en utilisantstd::getline
lors de la vérification de plusieurs lignes d'entrée utilisateur. Le comportement par défaut destd::getline
lira tout jusqu'à et y compris le caractère\n
de nouvelle ligne de l'objet de flux d'entrée, ce qui eststd::cin
dans ce cas.la source