Por que chamaríamos cin.clear () e cin.ignore () após ler a entrada?

86

O tutorial C ++ da Google Code University costumava ter este código:

// Description: Illustrate the use of cin to get input
// and how to recover from errors.

#include <iostream>
using namespace std;

int main()
{
  int input_var = 0;
  // Enter the do while loop and stay there until either
  // a non-numeric is entered, or -1 is entered.  Note that
  // cin will accept any integer, 4, 40, 400, etc.
  do {
    cout << "Enter a number (-1 = quit): ";
    // The following line accepts input from the keyboard into
    // variable input_var.
    // cin returns false if an input operation fails, that is, if
    // something other than an int (the type of input_var) is entered.
    if (!(cin >> input_var)) {
      cout << "Please enter numbers only." << endl;
      cin.clear();
      cin.ignore(10000,'\n');
    }
    if (input_var != -1) {
      cout << "You entered " << input_var << endl;
    }
  }
  while (input_var != -1);
  cout << "All done." << endl;

  return 0;
}

Qual é o significado de cin.clear()e cin.ignore()? Por que os parâmetros 10000e \nsão necessários?

JacKeown
fonte
1
Esta é, de longe, a minha postagem com maior número de votos. Devo ter atingido o pico no ensino médio.
JacKeown

Respostas:

102

O cin.clear()apaga o sinalizador de erro cin(para que futuras operações de I / O funcionem corretamente) e, em seguida, cin.ignore(10000, '\n')pula para a próxima nova linha (para ignorar qualquer outra coisa na mesma linha do não-número para que não cause outra falha de análise) . Ele pulará apenas até 10.000 caracteres, portanto, o código pressupõe que o usuário não colocará uma linha muito longa e inválida.

Jeremiah Willcock
fonte
57
+1. Quero adicionar que, em vez de ignorar até 10.000 caracteres, seria melhor usar cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Dennis
30
Se você quiser usar std::numeric_limits, certifique-se de usar #include <limits>.
gbmhunter
1
Alguém pode explicar a sintaxe std::numeric_limits<std::streamsize>::max()?
Minh Tran
4
@Minh Tran: std :: streamsize é integral com sinal que fornece o número de caracteres de I / O transferidos ou o tamanho do buffer de I / O. Aqui, usando a classe de modelo "numeric_limits", queríamos saber o limite máximo do buffer de E / S ou do caractere transferido.
Pankaj de
1
@Minh Tran: apenas uma pequena correção "tamanho do fluxo" não é uma classe, é apenas um tipo integral. Podemos obter o limite de outros também, ou seja. int, char etc. por favor verifique em [link] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj
46

Você entra no

if (!(cin >> input_var))

declaração se ocorrer um erro ao obter a entrada de cin. Se ocorrer um erro, um sinalizador de erro será definido e as tentativas futuras de obter dados falharão. É por isso que você precisa

cin.clear();

para se livrar do sinalizador de erro. Além disso, a entrada que falhou ficará no que presumo ser algum tipo de buffer. Quando você tentar obter a entrada novamente, ele lerá a mesma entrada no buffer e falhará novamente. É por isso que você precisa

cin.ignore(10000,'\n');

Ele retira 10.000 caracteres do buffer, mas para se encontrar uma nova linha (\ n). O 10000 é apenas um grande valor genérico.

voar
fonte
11
Apenas adicionando que o padrão para ignorar é pular um único caractere, então você precisa de um número maior para pular uma linha inteira.
Bo Persson
22

Por que usamos:

1) cin.ignore

2) cin.clear

?

Simplesmente:

1) Para ignorar (extrair e descartar) valores que não queremos no fluxo

2) Para limpar o estado interno do fluxo. Depois de usar cin.clear, o estado interno é definido novamente como goodbit, o que significa que não há 'erros'.

Versão longa:

Se algo for colocado em 'stream' (cin), então deve ser retirado de lá. Por 'levado' queremos dizer 'usado', 'removido', 'extraído' do fluxo. O fluxo tem um fluxo. Os dados estão fluindo como água no fluxo. Você simplesmente não pode parar o fluxo de água;)

Olhe para o exemplo:

string name; //line 1
cout << "Give me your name and surname:"<<endl;//line 2
cin >> name;//line 3
int age;//line 4
cout << "Give me your age:" <<endl;//line 5
cin >> age;//line 6

O que acontece se o usuário responder: "Arkadiusz Wlodarczyk" para a primeira pergunta?

Execute o programa para ver por si mesmo.

Você verá no console "Arkadiusz", mas o programa não pedirá 'idade'. Ele vai terminar imediatamente após a impressão de "Arkadiusz".

E "Wlodarczyk" não é mostrado. Parece que foi embora (?) *

O que aconteceu? ;-)

Porque há um espaço entre "Arkadiusz" e "Wlodarczyk".

O caractere de "espaço" entre o nome e o sobrenome é um sinal para o computador de que há duas variáveis ​​esperando para serem extraídas no fluxo de 'entrada'.

O computador pensa que você está tentando enviar para a entrada mais de uma variável. Esse sinal de "espaço" é um sinal para ele interpretar dessa forma.

Portanto, o computador atribui "Arkadiusz" ao 'nome' (2) e, como você colocou mais de uma string no fluxo (entrada), o computador tentará atribuir o valor "Wlodarczyk" à variável 'idade' (!). O usuário não terá a chance de colocar nada no 'cin' na linha 6 porque essa instrução já foi executada (!). Por quê? Porque ainda havia algo em funcionamento. E como eu disse antes, o fluxo está em um fluxo, então tudo deve ser removido dele o mais rápido possível. E a possibilidade surgiu quando o computador viu a ciência da instrução;

O computador não sabe que você criou uma variável que armazena a idade de alguém (linha 4). 'idade' é apenas um rótulo. Pois a 'era' do computador também poderia ser chamada de: 'afsfasgfsagasggas' e seria o mesmo. Para ele, é apenas uma variável que tentará atribuir "Wlodarczyk" porque você ordenou / instruiu o computador a fazê-lo na linha (6).

É errado fazer isso, mas foi você quem fez isso! É sua culpa! Bem, talvez usuário, mas ainda ...


Tudo bem, tudo bem. Mas como consertar ?!

Vamos tentar brincar um pouco com esse exemplo antes de corrigi-lo adequadamente para aprender mais algumas coisas interessantes :-)

Prefiro fazer uma abordagem onde entendemos as coisas. Consertar algo sem saber como a gente fazia não dá satisfação, não acha? :)

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate(); //new line is here :-)

Depois de invocar o código acima, você notará que o estado do seu stream (cin) é igual a 4 (linha 7). O que significa que seu estado interno não é mais igual a goodbit. Algo está bagunçado. É bastante óbvio, não é? Você tentou atribuir um valor de tipo de string ("Wlodarczyk") à variável de tipo int 'idade'. Tipos não correspondem. É hora de informar que algo está errado. E o computador faz isso mudando o estado interno do fluxo. É como: "Seu f ****-up cara, me conserta por favor. Eu te informo 'gentilmente' ;-)"

Você simplesmente não pode mais usar 'cin' (stream). Está preso. Como se você tivesse colocado grandes toras de madeira no riacho. Você deve consertá-lo antes de poder usá-lo. Os dados (água) não podem mais ser obtidos desse riacho (cin) porque a tora de madeira (estado interno) não permite que você faça isso.

Ah, então se houver um obstáculo (toras de madeira), podemos simplesmente removê-lo usando ferramentas feitas para isso?

Sim!

o estado interno de cin definido como 4 é como um alarme que está uivando e fazendo barulho.

cin.clear limpa o estado de volta ao normal (goodbit). É como se você tivesse vindo e silenciado o alarme. Você apenas adia. Você sabe que algo aconteceu, então você diz: "Tudo bem parar de fazer barulho, eu sei que algo já está errado, cale a boca (limpe)".

Tudo bem, vamos fazer isso! Vamos usar cin.clear ().

Chame o código abaixo usando "Arkadiusz Wlodarczyk" como primeira entrada:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl; 
cin.clear(); //new line is here :-)
cout << cin.rdstate()<< endl;  //new line is here :-)

Certamente podemos ver depois de executar o código acima que o estado é igual a goodbit.

Ótimo então o problema está resolvido?

Chame o código abaixo usando "Arkadiusz Wlodarczyk" como primeira entrada:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl;; 
cin.clear(); 
cout << cin.rdstate() << endl; 
cin >> age;//new line is here :-)

Mesmo que o estado seja definido como goodbit após a linha 9, o usuário não é questionado sobre "idade". O programa pára.

PORQUE?!

Oh cara ... Você acabou de disparar o alarme, que tal a tora de madeira dentro de uma água? * Volte para o texto onde falamos sobre "Wlodarczyk" como supostamente tinha sumido.

Você precisa remover "Wlodarczyk" aquele pedaço de madeira do riacho. Desligar os alarmes não resolve o problema. Você acabou de silenciá-lo e acha que o problema desapareceu? ;)

Então é hora de outra ferramenta:

cin.ignore pode ser comparado a um caminhão especial com cordas que vem e remove as toras de madeira que travaram o riacho. Isso elimina o problema que o usuário do programa criou.

Então, poderíamos usá-lo antes mesmo de fazer o alarme disparar?

Sim:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;

O "Wlodarczyk" será removido antes de fazer o ruído na linha 7.

O que é 10000 e '\ n'?

Diz remover 10.000 caracteres (apenas no caso) até que '\ n' seja encontrado (ENTER). BTW, isso pode ser feito melhor usando numeric_limits, mas não é o tópico desta resposta.


Portanto, a principal causa do problema foi embora antes que o ruído fosse feito ...

Por que precisamos 'limpar' então?

E se alguém tivesse feito a pergunta 'dê-me sua idade' na linha 6, por exemplo: "vinte anos" em vez de escrever 20?

Tipos não correspondem novamente. O computador tenta atribuir string a int. E o alarme começa. Você nem mesmo tem chance de reagir em uma situação como essa. cin.ignore não o ajudará neste caso.

Portanto, devemos usar claro em casos como este:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;
cin.clear();
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

Mas você deve limpar o estado 'apenas no caso'?

Claro que não.

Se algo der errado (cin >> age;), a instrução irá informá-lo sobre isso retornando false.

Portanto, podemos usar a instrução condicional para verificar se o usuário colocou o tipo errado no fluxo

int age;
if (cin >> age) //it's gonna return false if types doesn't match
    cout << "You put integer";
else
    cout << "You bad boy! it was supposed to be int";

Tudo bem, então podemos corrigir nosso problema inicial como por exemplo:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

int age;
cout << "Give me your age:" << endl;
if (cin >> age)
  cout << "Your age is equal to:" << endl;
else
{
 cin.clear();
 cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
 cout << "Give me your age name as string I dare you";
 cin >> age;
}

Claro que isso pode ser melhorado, por exemplo, fazendo o que você fez em questão usando loop while.

BÔNUS:

Você pode estar se perguntando. E se eu quisesse que o nome e o sobrenome do usuário fossem na mesma linha? É mesmo possível usar cin se cin interpretar cada valor separado por "espaço" como uma variável diferente?

Claro, você pode fazer isso de duas maneiras:

1)

string name, surname;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin >> surname;

cout << "Hello, " << name << " " << surname << endl;

2) ou usando a função getline.

getline(cin, nameOfStringVariable);

e é assim que se faz:

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

A segunda opção pode sair pela culatra em caso de você usá-lo depois de usar 'cin' antes do getline.

Vamos dar uma olhada:

a)

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endl;

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Se você colocar "20" como idade, não será solicitado o nome e o sobrenome.

Mas se você fizer assim:

b)

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endll

tudo está bem.

O QUE?!

Cada vez que você coloca algo na entrada (fluxo), você deixa no final um caractere branco que é ENTER ('\ n'). Você tem que inserir valores de alguma forma para o console. Portanto, deve acontecer se os dados vierem do usuário.

b) as características do cin são que ele ignora os espaços em branco, então quando você está lendo informações de cin, o caractere de nova linha '\ n' não importa. É ignorado.

a) a função getline obtém toda a linha até o caractere de nova linha ('\ n'), e quando o caractere de nova linha é a primeira coisa, a função getline obtém '\ n', e isso é tudo para obter. Você extrai o caractere de nova linha que foi deixado na transmissão pelo usuário que colocou "20" na transmissão na linha 3.

Portanto, para consertar é sempre invocar cin.ignore (); cada vez que você usar cin para obter qualquer valor, se algum dia for usar getline () dentro de seu programa.

Portanto, o código adequado seria:

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cin.ignore(); // it ignores just enter without arguments being sent. it's same as cin.ignore(1, '\n') 
cout << "Your age is" << age << endl;


string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Espero que os streams sejam mais claros para você.

Hah me silencie por favor! :-)

Morfidon
fonte
1

use cin.ignore(1000,'\n')para limpar todos os chars do anterior cin.get()no buffer e irá escolher parar quando encontrar '\ n' ou 1000 charsprimeiro.

Phy Lieng
fonte