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.

Você vai aprender
  • O que acontece com std::cin >> idade quando a pessoa digita letras: cin.fail().
  • Por que um cin em estado de falha cria um laço infinito.
  • Como recuperar o cin: cin.clear() e cin.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 getline e converter com std::stoi dentro de try.
  • 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:

  1. Formato: é mesmo um número? (vinte não é; 12 cadernos também não é só um número.)
  2. Faixa: o número faz sentido? (Idade -5 ou 200, mês 13, 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:

  1. idade virou 0. O >> tentou ler um int, encontrou a letra v e desistiu. Desde o C++11, quando a leitura falha, ele guarda 0 na variável.
  2. O cin entrou em estado de falha, e std::cin.fail() passou a responder 1 (verdadeiro). A palavra vinte continua lá, esperando para ser lida.
  3. A segunda leitura nem tentou. Um cin em estado de falha ignora todas as leituras seguintes, sem esperar o teclado e sem mexer na variável. Por isso ano continuou com o -1 do 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 cin volta a aceitar leituras. Mas o vinte continua 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. ignore descarta caracteres até encontrar o '\n' (o Enter), inclusive. O primeiro argumento é o máximo de caracteres a descartar, e std::numeric_limits<std::streamsize>::max() é "o maior número possível", ou seja, "sem limite". Precisa de #include <limits>.
A ordem importa: primeiro 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 >> idade devolve o próprio cin, 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 stoi lanç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

FormaVantagensCuidados
cin >> x + fail/clear/ignoreCurta; 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 tryConfere 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 programa "pula" as perguntas seguintes

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(…).

A tela enche com a mesma pergunta, sem parar

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.

"terminate called after throwing an instance of 'std::invalid_argument'" com 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&).

Esqueci o #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

EscritaSignificado
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:

if (std::cin.fail()) { std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); }

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:

while (!(std::cin >> mes) || ) { … }

3. Converter a linha

Complete para converter o texto linha num int:

int n = ;

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 >> x recebe algo que não serve, x vira 0, cin.fail() fica verdadeiro e todas as leituras seguintes são ignoradas.
  • Para recuperar: std::cin.clear(); e depois std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
  • Repita com while (!(std::cin >> x) || fora_da_faixa), e teste cin.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::stoi em try, conferindo se a linha inteira foi usada.