Ficheiros e validação

Abertura, leitura, escrita, estado dos fluxos e separação entre linhas e registos.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

As notas podem ficar num ficheiro de texto para serem lidas noutra execução. Usamos uma linha por registo, com um nome sem espaços e uma nota inteira de 0 a 20.

Admite espaços antes, entre e depois dos campos. A biblioteca extrai os valores. O programa confirma o formato completo e o intervalo.

Abrir, escrever e fechar

std::ifstream abre para leitura e std::ofstream para escrita. Por defeito, ofstream substitui o conteúdo de um ficheiro existente. Usa std::ios::app quando o contrato é acrescentar ao fim. O caminho relativo é resolvido a partir da pasta de trabalho do processo, não da pasta onde está o código.

Programa completo para compilar localmente:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    {
        std::ofstream saida("notas.txt");
        if (!saida) { std::cout << "Erro de abertura\n"; return 0; }
        saida << "Ana 15\nRui 12\n";
        saida.close();
        if (!saida) { std::cout << "Erro de escrita\n"; return 0; }
    }
    std::ifstream entrada("notas.txt");
    if (!entrada) { std::cout << "Erro de leitura\n"; return 0; }
    std::string nome;
    int nota = 0;
    int total = 0;
    while (entrada >> nome >> nota) {
        std::cout << nome << ": " << nota << "\n";
        total += nota;
    }
    if (entrada.bad()) { std::cout << "Erro de I/O\n"; return 0; }
    std::cout << "Total: " << total << "\n";
}

Saída:

Ana: 15
Rui: 12
Total: 27

O programa cria notas.txt na pasta de trabalho e corre localmente, porque o executor do navegador não cria ficheiros; fecha a escrita antes de leres e verifica close(), pois o destrutor fecha mas não trata um erro final de escrita.

A leitura por tokens acima serve um ficheiro conhecido; para dados externos, ler por linhas distingue um registo incompleto do fim normal, validando cada linha em três passos: ler nome e nota, confirmar que não sobra texto e confirmar o intervalo.

Ler sem antecipar o fim

O ciclo while (entrada >> nome >> nota) só entra no corpo depois de ler os dois campos. eof() indica que uma operação anterior encontrou o fim; não prevê se a próxima leitura vai resultar.

Por isso while (!entrada.eof()) pode repetir o último registo ou usar dados incompletos.

Os fluxos conservam estado. fail() sinaliza falha de uma operação ou conversão. bad() sinaliza uma falha grave de I/O. eof() indica encontro do fim. Estes estados podem coexistir.

Depois de uma falha, novas leituras não prosseguem normalmente até limpar o estado com clear() e tratar a entrada problemática. Limpar o estado sem consumir ou corrigir a causa pode criar um ciclo sem progresso.

Separar linhas e registos

A leitura por tokens ignora espaços e fins de linha. Se o formato exige exatamente um registo por linha, lê primeiro a linha e interpreta-a num istringstream. Esta classe permite tratar uma string como um fluxo, sem abrir outro ficheiro.

Validar cada registo de uma entrada
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string linha;
    int numero = 0;
    while (std::getline(std::cin, linha)) {
        ++numero;
        std::istringstream registo(linha);
        std::string nome;
        std::string extra;
        int nota = 0;
        bool leu = static_cast<bool>(registo >> nome >> nota);
        bool sobra = static_cast<bool>(registo >> extra);
        bool intervalo = (nota >= 0 && nota <= 20);
        if (!leu || sobra || !intervalo) {
            std::cout << "Linha " << numero << " invalida\n";
            continue;
        }
        std::cout << nome << ": " << nota << "\n";
    }
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

Ana: 15
Linha 2 invalida
Linha 3 invalida
Rui: 20

No exemplo, Ana 15 lê nome e 15, sem sobra e dentro do intervalo: válida; Eva 21 lê nome e 21, sem sobra, mas fora do intervalo: inválida; Leo 12 lixo lê 12 e ainda encontra lixo: inválida; Eva 12abc lê 12 e deixa abc por consumir: inválida. Uma linha vazia falha logo na primeira leitura.

EntradaleusobranotaDecisão
Ana 15simnão15válida
Ananãonão0inválida
Eva 12abcsimsim12inválida
vazianãonão0inválida

Cada iteração cria um fluxo novo para a linha, por isso uma falha de conversão não contamina a seguinte. Se o formato permitisse nomes com espaços, teria de usar outra separação, por exemplo um delimitador, e definir como esse delimitador pode aparecer nos campos. Depois de uma falha de conversão no fluxo principal, limpa o estado e consome a entrada problemática antes de continuar:

// Excerto: recupera de uma leitura falhada.
#include <limits>
entrada.clear();
entrada.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

Binário não é memória bruta portátil

std::ios::binary evita transformações de texto próprias da plataforma. Não define por si só um formato de dados. Escrever os bytes de uma struct depende do padding, da ordem dos bytes e da representação dos tipos. Um formato portátil especifica tamanhos, ordem dos bytes e codificação, ou usa uma biblioteca que trate essas decisões.

Caso resolvido: fim depois de um nome

Com a linha Ana, sem nota, getline resulta porque leu uma linha, mas a extração do nome resulta e a da nota falha. Por isso leu é falso e o programa rejeita o registo sem usar o valor anterior de nota.

Num ficheiro vazio, getline falha antes do corpo do ciclo e não existe registo a validar. Os dois casos chegam ao fim dos dados com significados diferentes:

  • registo incompleto: existe uma linha, mas falta um campo ou o formato é inválido;
  • ausência de registo: não existe linha nenhuma para validar.

Só o primeiro caso produz uma mensagem de linha inválida.

Exercícios

O fim não prevê a leitura

Um ficheiro contém Ana 15 e termina com um fim de linha. Um ciclo testa !entrada.eof() antes de tentar ler nome e nota. Explica por que pode executar o corpo mais uma vez e como corrigir.

Pista

Lembra-te de que eof descreve uma tentativa anterior.

Solução

Depois do registo válido, ainda pode faltar a tentativa que deteta o fim. O ciclo entra outra vez, a extração falha e o corpo pode usar valores antigos. A condição deve ser while (entrada >> nome >> nota), para só usar um par lido com sucesso. Para um formato de uma linha por registo, lê e valida cada linha.

Confere a tua resposta:

  • Distingui encontro do fim de previsão da próxima leitura.
  • Expliquei o risco de usar valores antigos.
  • Usei a própria leitura como condição.
Voltar à explicação
Escrever a validação em três passos

Escreve bool registo_valido(const std::string& linha) que aceita só linhas com um nome sem espaços e um inteiro de 0 a 20, sem conteúdo extra. São permitidos espaços iniciais e finais. Testa Ana 15, Ana, Eva 12abc, a linha vazia, Rui 20 e Eva 21. Justifica cada rejeição e confirma que um erro numa linha não impede a leitura da seguinte.

Pista

Trata separadamente campos em falta, conteúdo extra e nota fora do intervalo.

Solução

Ana 15 dá leu verdadeiro, sobra falso e intervalo verdadeiro: válida. Ana falha em leu. Eva 12abc tem sobra verdadeira. A vazia falha em leu. A condição final é leu && !sobra && intervalo. Rui 20 é válido porque só sobram espaços; Eva 21 é inválido pelo intervalo. Uma implementação completa é:

#include <sstream>
#include <string>

bool registo_valido(const std::string& linha) {
    std::istringstream entrada(linha);
    std::string nome, extra;
    int nota = 0;
    if (!(entrada >> nome >> nota)) return false;
    if (entrada >> extra) return false;
    return 0 <= nota && nota <= 20;
}

Cada chamada constrói outro fluxo, por isso uma falha não contamina a linha seguinte.

Confere a tua resposta:

  • Separei ler, detetar sobra e confirmar intervalo.
  • Rejeitei Ana sem nota, Eva 12abc e linha vazia.
  • Aceitei espaços iniciais e finais, mas rejeitei conteúdo extra e notas fora do intervalo.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.