Eu tenho o seguinte trecho de código que solicita ao usuário seu nome e 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;
}
}
O que descobri é que o nome foi extraído com sucesso, mas não o estado. Aqui está a entrada e a saída resultante:
Input: "John" "New Hampshire" Output: "Your name is John and you live in "
Por que o nome do estado foi omitido da saída? Dei a entrada adequada, mas o código de alguma forma a ignora. Por que isso acontece?
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
que também deve funcionar conforme o esperado. (Além das respostas abaixo).Respostas:
Por que isso acontece?
Isso tem pouco a ver com a entrada que você forneceu, mas com as
std::getline()
exibições de comportamento padrão . Quando você forneceu sua entrada para o nome (std::cin >> name
), não apenas enviou os seguintes caracteres, mas também uma nova linha implícita foi anexada ao fluxo:Uma nova linha é sempre anexada à sua entrada quando você seleciona Enterou Returnao enviar de um terminal. Também é usado em arquivos para mover para a próxima linha. A nova linha é deixada no buffer após a extração
name
até a próxima operação de E / S, onde é descartada ou consumida. Quando o fluxo de controle chegarstd::getline()
, a nova linha será descartada, mas a entrada cessará imediatamente. A razão para isso acontecer é porque a funcionalidade padrão dessa função determina que ela deve (ela tenta ler uma linha e para quando encontra uma nova linha).Como essa nova linha inibe a funcionalidade esperada de seu programa, ela deve ser ignorada ou ignorada de alguma forma. Uma opção é chamar
std::cin.ignore()
após a primeira extração. Ele descartará o próximo caractere disponível para que a nova linha não atrapalhe.Explicação detalhada:
Essa é a sobrecarga do
std::getline()
que você chamou:Outra sobrecarga dessa função leva um delimitador de tipo
charT
. Um caractere delimitador é um caractere que representa o limite entre as sequências de entrada. Essa sobrecarga específica define o delimitador para o caractere de nova linhainput.widen('\n')
por padrão, pois um não foi fornecido.Agora, essas são algumas das condições pelas quais
std::getline()
termina a entrada:std::basic_string<charT>
pode conterA terceira condição é aquela com a qual estamos lidando. Sua entrada em
state
é representada da seguinte forma:onde
next_pointer
é o próximo caractere a ser analisado. Visto que o caractere armazenado na próxima posição na sequência de entrada é o delimitador,std::getline()
descartará silenciosamente esse caractere, aumentaránext_pointer
para o próximo caractere disponível e interromperá a entrada. Isso significa que o restante dos caracteres fornecidos ainda permanecem no buffer para a próxima operação de E / S. Você notará que se realizar outra leitura da linha para dentrostate
, sua extração produzirá o resultado correto como a última chamada parastd::getline()
descartar o delimitador.Você deve ter notado que normalmente não encontra esse problema ao extrair com o operador de entrada formatado (
operator>>()
). Isso ocorre porque os fluxos de entrada usam espaços em branco como delimitadores de entrada e têm ostd::skipws
1 manipulador ativado por padrão. Streams irá descartar o espaço em branco inicial do stream ao começar a realizar a entrada formatada. 2Ao contrário dos operadores de entrada formatados,
std::getline()
é uma função de entrada não formatada . E todas as funções de entrada não formatadas têm o seguinte código um tanto em comum:O acima é um objeto sentinela que é instanciado em todas as funções de E / S formatadas / não formatadas em uma implementação C ++ padrão. Objetos de sentinela são usados para preparar o fluxo para E / S e determinar se ele está ou não em um estado de falha. Você descobrirá apenas que nas funções de entrada não formatadas , o segundo argumento para o construtor de sentinela é
true
. Esse argumento significa que o espaço em branco inicial não será descartado do início da sequência de entrada. Aqui está a citação relevante do Padrão [§27.7.2.1.3 / 2]:Visto que a condição acima é falsa, o objeto sentinela não descartará o espaço em branco. O motivo
noskipws
definidotrue
por essa função é porque o objetivo destd::getline()
é ler caracteres brutos e não formatados em umstd::basic_string<charT>
objeto.A solução:
Não há como impedir esse comportamento de
std::getline()
. O que você terá que fazer é descartar a nova linha antes destd::getline()
executá-la (mas faça isso após a extração formatada). Isso pode ser feito usandoignore()
para descartar o resto da entrada até chegarmos a uma nova linha:Você precisará incluir
<limits>
para usarstd::numeric_limits
.std::basic_istream<...>::ignore()
é uma função que descarta uma quantidade especificada de caracteres até encontrar um delimitador ou chegar ao final do fluxo (ignore()
também descarta o delimitador se o encontrar). Amax()
função retorna a maior quantidade de caracteres que um fluxo pode aceitar.Outra maneira de descartar o espaço em branco é usar a
std::ws
função, que é um manipulador projetado para extrair e descartar o espaço em branco inicial do início de um fluxo de entrada:Qual é a diferença?
A diferença é que
ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 descarta caracteres indiscriminadamente até que descarta oscount
caracteres, encontre o delimitador (especificado pelo segundo argumentodelim
) ou atinja o final do fluxo.std::ws
é usado apenas para descartar caracteres de espaço em branco do início do fluxo.Se você estiver misturando entrada formatada com entrada não formatada e precisar descartar os espaços em branco residuais, use
std::ws
. Caso contrário, se você precisar limpar a entrada inválida independentemente do que seja, useignore()
. Em nosso exemplo, só precisamos limpar os espaços em branco, pois o fluxo consumiu sua entrada"John"
para aname
variável. Tudo o que restou foi o caractere de nova linha.1:
std::skipws
é um manipulador que diz ao fluxo de entrada para descartar os espaços em branco iniciais ao realizar a entrada formatada. Isso pode ser desligado com ostd::noskipws
manipulador.2: Os fluxos de entrada consideram certos caracteres como espaços em branco por padrão, como o caractere de espaço, caractere de nova linha, avanço de formulário, retorno de carro, etc.
3: Esta é a assinatura de
std::basic_istream<...>::ignore()
. Você pode chamá-lo com zero argumentos para descartar um único caractere do fluxo, um argumento para descartar uma certa quantidade de caracteres ou dois argumentos para descartarcount
caracteres ou até atingirdelim
, o que vier primeiro. Você normalmente usastd::numeric_limits<std::streamsize>::max()
como o valor decount
se não souber quantos caracteres existem antes do delimitador, mas deseja descartá-los de qualquer maneira.fonte
if (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, mas não fica tão claro que há uma vantagem. Mas eu tendo a preferir usar apenasstd::getline()
para entrada orientada por linha e, em seguida, lidar com a análise da linha de qualquer maneira que faça sentido. Acho que é menos sujeito a erros.std::getline()
é se você deseja capturar todos os caracteres até um determinado delimitador e inseri-lo em uma string, por padrão que é a nova linha. Se esseX
número de strings forem apenas palavras / tokens individuais, esse trabalho pode ser facilmente realizado com>>
. Caso contrário, você entraria com o primeiro número em um inteiro com>>
, chamariacin.ignore()
na próxima linha e, em seguida, executaria um loop onde usargetline()
.Tudo ficará bem se você alterar seu código inicial da seguinte maneira:
fonte
get()
consome o próximo caractere. Também há o(std::cin >> name).ignore()
que sugeri anteriormente em minha resposta.if (getline(std::cin, name) && getline(std::cin, state))
?Isso acontece porque uma alimentação de linha implícita, também conhecida como caractere de nova linha,
\n
é anexada a todas as entradas do usuário de um terminal, pois está informando ao fluxo para iniciar uma nova linha. Você pode considerar isso com segurança usandostd::getline
ao verificar várias linhas de entrada do usuário. O comportamento padrão destd::getline
lerá tudo até e incluindo o caractere de nova linha\n
do objeto de fluxo de entrada que éstd::cin
neste caso.fonte