C++ Validar a Entrada
Você pede a idade e o usuário digita vinte. Pede um número de 1 a 12 e ele digita 13. Pede a quantidade e recebe 12 cadernos. Usuários erram (e às vezes testam de propósito), e um programa de verdade não pode quebrar, travar ou seguir com valores errados por causa disso. Nesta aula você vai ver o que o std::cin faz quando recebe algo inesperado, como colocá-lo de pé de novo e como escrever um laço que só aceita respostas válidas.
- O que acontece com
std::cin >> idadequando a pessoa digita letras:cin.fail(). - Por que um
cinem estado de falha cria um laço infinito. - Como recuperar o
cin:cin.clear()ecin.ignore(…). - Como repetir a pergunta até receber um número válido e dentro da faixa.
- O jeito mais robusto: ler a linha inteira com
getlinee converter comstd::stoidentro detry. - O que fazer quando a entrada acaba (Ctrl+D).
Todos os programas desta aula foram compilados com o g++ 15 na máquina fictícia da Ana, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. Nas sessões de terminal, o que vem depois de cada pergunta é o que a Ana digitou. No editor do guia, as respostas já vêm preparadas na caixa (você vai ver as entradas erradas de propósito).
A ideia: o porteiro que confere o ingresso
Na entrada de um show, o porteiro não deixa passar qualquer papel. Ele confere: é um ingresso? É deste show? É de hoje? Se algo está errado, ele explica o problema e pede de novo; não deixa a pessoa entrar "mais ou menos".
Validar a entrada é colocar um porteiro na frente do seu programa. Toda informação que vem de fora (teclado, arquivo, rede) passa por duas conferências:
- Formato: é mesmo um número? (
vintenão é;12 cadernostambém não é só um número.) - Faixa: o número faz sentido? (Idade
-5ou200, mês13, não fazem.)
1. Quando o cin falha
Veja o que acontece quando a Ana digita uma palavra onde o programa espera um número:
#include <iostream>
int main() {
int idade = -1;
int ano = -1;
std::cout << "Sua idade: ";
std::cin >> idade;
std::cout << "Ano de nascimento: ";
std::cin >> ano;
std::cout << "\nidade = " << idade << ", ano = " << ano << '\n';
std::cout << "cin.fail() = " << std::cin.fail() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra idade.cpp -o idade $ ./idade Sua idade: vinte Ano de nascimento: « o programa não esperou a Ana digitar o ano! idade = 0, ano = -1 cin.fail() = 1
Três coisas estranhas aconteceram:
idadevirou 0. O>>tentou ler umint, encontrou a letrave desistiu. Desde o C++11, quando a leitura falha, ele guarda0na variável.- O
cinentrou em estado de falha, estd::cin.fail()passou a responder1(verdadeiro). A palavravintecontinua lá, esperando para ser lida. - A segunda leitura nem tentou. Um
cinem estado de falha ignora todas as leituras seguintes, sem esperar o teclado e sem mexer na variável. Por issoanocontinuou com o-1do começo.
É como um caixa de supermercado que travou: enquanto ninguém destravar, nenhuma compra passa. O cin não destrava sozinho.
O laço infinito clássico
Agora imagine isso dentro de um menu, que repete até a pessoa escolher "Sair":
#include <iostream>
int main() {
int opcao = 0;
int voltas = 0;
while (opcao != 3) {
std::cout << "1) Ver saldo 2) Depositar 3) Sair\nOpção: ";
std::cin >> opcao;
++voltas;
if (opcao == 1) std::cout << "Saldo: R$ 100\n";
else if (opcao == 2) std::cout << "Depósito feito\n";
}
std::cout << "Até logo! (" << voltas << " voltas)\n";
}
Se alguém digitar x, o cin falha, opcao vira 0, e o laço volta a perguntar. Mas o cin travado não espera mais nada, então o laço gira sem parar, enchendo a tela. Para ver sem travar o terminal, rodamos com timeout 3 (mata o programa em 3 segundos) e head (mostra só o começo):
$ printf "x\n" | timeout 3 ./menu | head -8 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair Opção: 1) Ver saldo 2) Depositar 3) Sair $ printf "x\n" | timeout 3 ./menu | wc -l 45366933 « 45 milhões de linhas em 3 segundos
O printf "x\n" | entrega a letra x ao programa como se alguém a digitasse. Se isso acontecer com você no terminal, aperte Ctrl+C para parar o programa.
2. Destravar: cin.clear() e cin.ignore()
Para recuperar o cin são precisos dois passos, sempre nesta ordem:
#include <iostream>
#include <limits>
int main() {
int idade = 0;
std::cout << "Sua idade: ";
std::cin >> idade;
if (std::cin.fail()) {
std::cout << "Isso não é um número.\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Tente de novo: ";
std::cin >> idade;
}
std::cout << "Idade: " << idade << '\n';
}
$ g++ -std=c++23 -Wall -Wextra corrigir.cpp -o corrigir $ ./corrigir Sua idade: vinte Isso não é um número. Tente de novo: 29 Idade: 29
- std::cin.fail()
- Pergunta: "a última leitura falhou?". Verdadeiro quando o que foi digitado não servia para o tipo da variável.
- std::cin.clear();
- Passo 1: destravar. Limpa o estado de falha (clear = "limpar"). Depois disso, o
cinvolta a aceitar leituras. Mas ovintecontinua lá, e a próxima leitura daria de cara com ele de novo. - std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
- Passo 2: jogar fora o que sobrou.
ignoredescarta caracteres até encontrar o'\n'(o Enter), inclusive. O primeiro argumento é o máximo de caracteres a descartar, estd::numeric_limits<std::streamsize>::max()é "o maior número possível", ou seja, "sem limite". Precisa de#include <limits>.
clear, depois ignore
Com o cin em falha, tudo é ignorado, inclusive o próprio ignore. Se você chamar o ignore antes do clear, ele não faz nada, e o vinte continua lá.
Esse programa só dá uma segunda chance. Se a Ana errar de novo, a idade fica 0. Para insistir até acertar, precisamos de um laço.
3. Repetir até receber um número válido
#include <iostream>
#include <limits>
int main() {
int idade = 0;
std::cout << "Sua idade: ";
while (!(std::cin >> idade) || idade < 0 || idade > 130) {
if (std::cin.eof()) {
std::cout << "\nEntrada encerrada.\n";
return 1;
}
if (std::cin.fail()) {
std::cout << "Digite um número inteiro.\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
} else {
std::cout << "A idade deve estar entre 0 e 130.\n";
}
std::cout << "Sua idade: ";
}
std::cout << "Idade registrada: " << idade << '\n';
}
$ g++ -std=c++23 -Wall -Wextra laco.cpp -o laco $ ./laco Sua idade: abc Digite um número inteiro. Sua idade: -5 A idade deve estar entre 0 e 130. Sua idade: 200 A idade deve estar entre 0 e 130. Sua idade: 29 Idade registrada: 29
- while (!(std::cin >> idade) || idade < 0 || idade > 130)
- A condição faz a leitura e as duas conferências. O
std::cin >> idadedevolve o própriocin, que vale "falso" se a leitura falhou; o!inverte. Leia assim: "enquanto a leitura falhar ou a idade for menor que 0 ou maior que 130, repita". Graças ao||(veja Operadores Lógicos), se a leitura falhou, as comparações nem são feitas. - if (std::cin.eof())
- A entrada acabou (veja abaixo). Não adianta perguntar de novo: o programa desiste com código 1.
- if (std::cin.fail()) { … } else { … }
- Separa os dois problemas: formato errado (destrava e descarta) e faixa errada (o número foi lido direitinho, só não serve).
Missão: a caixa vai "digitar" oito, depois 11, depois 8.5. Do jeito que está, o programa aceita a primeira resposta e registra nota 0. Transforme a leitura num laço, como o laco.cpp, que recusa o que não é número (com clear e ignore) e o que está fora da faixa de 0 a 10, até registrar 8.5.
#include <iostream>
#include <limits>
int main() {
double nota = 0.0;
std::cout << "Nota (0 a 10): ";
std::cin >> nota;
// transforme a leitura acima num laço que só termina com uma nota válida
std::cout << "Nota registrada: " << nota << '\n';
}
#include <iostream>
#include <limits>
int main() {
double nota = 0.0;
std::cout << "Nota (0 a 10): ";
while (!(std::cin >> nota) || nota < 0 || nota > 10) {
if (std::cin.eof()) {
return 1;
}
if (std::cin.fail()) {
std::cout << "Digite um número.\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
} else {
std::cout << "A nota vai de 0 a 10.\n";
}
std::cout << "Nota (0 a 10): ";
}
std::cout << "Nota registrada: " << nota << '\n';
}
Quando a entrada acaba: Ctrl+D
No Linux, apertar Ctrl+D numa linha vazia diz ao programa "a entrada acabou" (end of file, "fim do arquivo"). O mesmo acontece quando a entrada vem de um arquivo ou de um printf … | e as linhas terminam. Depois disso, nenhuma leitura vai dar certo: o clear destrava, mas não há mais nada para ler. Sem o teste do eof(), o laco.cpp viraria um laço infinito. Com ele:
$ ./laco Sua idade: abc Digite um número inteiro. Sua idade: « aqui a Ana apertou Ctrl+D Entrada encerrada. $ echo $? 1
4. O que sobra na linha
O >> lê o maior pedaço que serve e para no primeiro caractere que não serve. Isso engana:
#include <iostream>
#include <string>
int main() {
int quantidade = 0;
std::string resto;
std::cout << "Quantidade: ";
std::cin >> quantidade;
std::getline(std::cin, resto);
std::cout << "quantidade = " << quantidade << ", sobrou [" << resto << "]\n";
}
$ ./sobra Quantidade: 12 cadernos quantidade = 12, sobrou [ cadernos] $ ./sobra Quantidade: 3.7 quantidade = 3, sobrou [.7]
Nos dois casos, o cin não falhou: ele achou um número no começo e ficou feliz. 12 cadernos virou 12 e 3.7 virou 3, e o resto ficou esperando, pronto para atrapalhar a próxima leitura. O laco.cpp aceitaria 29 anos como 29. Às vezes tudo bem; quando não estiver, o jeito da próxima seção resolve.
5. O jeito mais robusto: linha inteira + std::stoi
Em vez de deixar o >> decidir o que ler, leia a linha inteira com std::getline (veja Ler uma Linha) e converta você mesmo com std::stoi (veja Texto ↔ Número). Vantagens: o cin nunca entra em falha (um texto sempre "serve" para um std::string), nada sobra na linha, e os erros de conversão chegam como exceções, que você trata com try/catch.
#include <iostream>
#include <stdexcept>
#include <string>
// Lê uma linha e devolve o número; repete até ser válido e estar entre min e max.
int ler_inteiro(const std::string& pergunta, int min, int max) {
std::string linha;
while (true) {
std::cout << pergunta;
if (!std::getline(std::cin, linha)) {
throw std::runtime_error("a entrada acabou");
}
try {
std::size_t usados = 0;
int n = std::stoi(linha, &usados);
if (usados != linha.size()) {
std::cout << "Só o número, por favor.\n";
} else if (n < min || n > max) {
std::cout << "Digite um valor entre " << min << " e " << max << ".\n";
} else {
return n;
}
} catch (const std::invalid_argument&) {
std::cout << "Isso não é um número.\n";
} catch (const std::out_of_range&) {
std::cout << "Número grande demais.\n";
}
}
}
int main() {
int idade = ler_inteiro("Sua idade: ", 0, 130);
int mes = ler_inteiro("Mês do aniversário (1-12): ", 1, 12);
std::cout << "Idade " << idade << ", aniversário no mês " << mes << '\n';
}
$ g++ -std=c++23 -Wall -Wextra linha.cpp -o linha $ ./linha Sua idade: abc Isso não é um número. Sua idade: 29 anos Só o número, por favor. Sua idade: 99999999999 Número grande demais. Sua idade: 200 Digite um valor entre 0 e 130. Sua idade: 29 Mês do aniversário (1-12): 13 Digite um valor entre 1 e 12. Mês do aniversário (1-12): 7 Idade 29, aniversário no mês 7
- int ler_inteiro(const std::string& pergunta, int min, int max)
- Toda a validação numa função, reaproveitada para a idade e para o mês. Escreva uma vez, use sempre.
- if (!std::getline(std::cin, linha))
- Se nem a linha deu para ler, a entrada acabou: em vez de repetir para sempre, a função lança uma
std::runtime_error. - std::stoi(linha, &usados)
- Converte o texto para
int. O segundo argumento, opcional, recebe quantos caracteres foram usados na conversão. Em"29 anos", só 2 dos 7 foram usados. - if (usados != linha.size())
- Sobrou algo depois do número: recusa. É isso que barra o
29 anos, que o>>aceitaria. - catch (const std::invalid_argument&)
- O
stoilança esta quando o texto não começa com número (abc). Como não usamos a variável da exceção, ela nem ganhou nome. - catch (const std::out_of_range&)
- E esta quando o número não cabe num
int(99999999999).
Sem o try, o primeiro abc derrubaria o programa (veja a missão final). Repare também: o stoi aceita espaços antes do número (" 29" vira 29), mas não depois, por causa do nosso teste com usados.
Qual forma usar
| Forma | Vantagens | Cuidados |
|---|---|---|
cin >> x + fail/clear/ignore | Curta; lê vários números na mesma linha (3 4 5). | Aceita "lixo" depois do número (12 cadernos); fácil esquecer o clear e travar. |
getline + std::stoi/std::stod em try | Confere a linha inteira; o cin nunca trava; não sobra nada para a próxima leitura. | Um pouco mais de código (por isso vale uma função como ler_inteiro). |
Para programas interativos (menus, cadastros), prefira getline + conversão. Ele também evita a armadilha de misturar cin >> com getline, explicada em Ler uma Linha.
Quando dá errado
O que aconteceu: uma leitura com >> falhou e o cin ficou em estado de falha. Todas as leituras seguintes são ignoradas (como o Ano de nascimento: do idade.cpp).
Como resolver: teste std::cin.fail() depois de ler, e destrave com clear() + ignore(…).
O que aconteceu: o laço pergunta de novo, mas o cin continua em falha (faltou o clear), ou o texto errado continua lá (faltou o ignore), ou a entrada acabou (faltou testar o eof()).
Como resolver: Ctrl+C para parar. Depois confira os três itens, ou troque para getline + stoi.
what(): stoi
$ ./ingressos Quantos ingressos (1 a 6)? dois terminate called after throwing an instance of 'std::invalid_argument' what(): stoi Abortado (imagem do núcleo gravada) ./ingressos $ echo $? 134
O que aconteceu: o std::stoi recebeu um texto que não começa com número e lançou uma exceção, que ninguém pegou (veja Exceções). No editor do guia, o mesmo erro aparece como libc++abi: terminating due to uncaught exception of type std::invalid_argument: stoi: no conversion, também com o código 134.
Como resolver: coloque o stoi dentro de um try com catch (const std::invalid_argument&) e, de quebra, catch (const std::out_of_range&).
#include <limits> e compilou mesmo assim
O que aconteceu: no g++ 15, o <iostream> traz o <limits> "de carona", e o std::numeric_limits funciona sem o #include. Em outro compilador ou outra versão, o mesmo código pode dar error: ‘numeric_limits’ is not a member of ‘std’.
Como resolver: inclua sempre o cabeçalho de tudo o que você usa: #include <limits> para o numeric_limits.
Referência rápida
| Escrita | Significado |
|---|---|
if (std::cin >> x) | Lê e testa: verdadeiro se a leitura deu certo. |
std::cin.fail() | A última leitura falhou (formato errado ou fim da entrada). |
std::cin.eof() | A entrada acabou (Ctrl+D, fim do arquivo). |
std::cin.clear(); | Destrava o cin (limpa o estado de falha). |
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); | Descarta o resto da linha. Precisa de <limits>. |
std::getline(std::cin, linha) | Lê a linha inteira como texto. |
std::stoi(texto, &usados) | Converte para int e diz quantos caracteres usou. Lança invalid_argument ou out_of_range. |
std::stod(texto) | O mesmo, para double. |
Exercícios
1. Destravar
Complete o primeiro passo para tirar o cin do estado de falha:
2. Conferir a faixa
Complete a condição do laço que repete enquanto a leitura falhar ou o mês estiver fora de 1 a 12:
3. Converter a linha
Complete para converter o texto linha num int:
4. Com int q; std::cin >> q;, a pessoa digita 12 cadernos. O que acontece?
5. Por que o clear() tem de vir antes do ignore()?
Missão final: venda de ingressos
Missão: a caixa vai digitar dois, depois 0, depois 3. Rode e veja o programa abortar no dois. Coloque a leitura num laço que repete enquanto a resposta não for um número de 1 a 6: o std::stoi vai dentro de um try, e o catch mostra uma mensagem e deixa o laço continuar. No fim deve aparecer Total: R$ 60.
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::string linha;
int ingressos = 0;
std::cout << "Quantos ingressos (1 a 6)? ";
std::getline(std::cin, linha);
ingressos = std::stoi(linha);
std::cout << "Total: R$ " << ingressos * 20 << '\n';
}
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::string linha;
int ingressos = 0;
while (true) {
std::cout << "Quantos ingressos (1 a 6)? ";
if (!std::getline(std::cin, linha)) {
return 1;
}
try {
ingressos = std::stoi(linha);
if (ingressos >= 1 && ingressos <= 6) {
break;
}
std::cout << "Entre 1 e 6, por favor.\n";
} catch (const std::exception&) {
std::cout << "Digite um número.\n";
}
}
std::cout << "Total: R$ " << ingressos * 20 << '\n';
}
Resumo
- Toda entrada vinda de fora deve ser conferida em formato (é número?) e faixa (faz sentido?).
- Quando
cin >> xrecebe algo que não serve,xvira 0,cin.fail()fica verdadeiro e todas as leituras seguintes são ignoradas. - Para recuperar:
std::cin.clear();e depoisstd::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');. - Repita com
while (!(std::cin >> x) || fora_da_faixa), e testecin.eof()para não girar para sempre. - O
>>aceita o começo numérico (12 cadernos→ 12) e deixa o resto. - O jeito mais robusto:
getline+std::stoiemtry, conferindo se a linha inteira foi usada.