# Ficheiros e validação

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

Página: https://resumos.rgo.pt/cadeiras/p/ficheiros/

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.

**main.cpp**

```cpp
#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";
    }
}
```

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.

| Entrada | `leu` | `sobra` | `nota` | Decisão |
| --- | --- | --- | --- | --- |
| `Ana 15` | sim | não | 15 | válida |
| `Ana` | não | não | 0 | inválida |
| `Eva 12abc` | sim | sim | 12 | inválida |
| vazia | não | não | 0 | invá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](https://resumos.rgo.pt/cadeiras/p/ficheiros/#ler-sem-antecipar-o-fim)

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](https://resumos.rgo.pt/cadeiras/p/ficheiros/#separar-linhas-e-registos)
