Aula 39b — Arquivos em C Parte 2 de 2

Arquivos binários — fread, fwrite, fseek e acesso direto a registros

1. Por que Arquivos Binários?

Na Aula 39a usamos fprintf para gravar dados como texto legível. Funciona, mas tem limitações:

Aspecto Arquivo Texto Arquivo Binário
Leitura humana ✅ Abre no Bloco de Notas ❌ Bytes brutos — ilegível
Gravar/ler struct completa ❌ Campo por campo — trabalhoso ✅ A struct inteira de uma vez
Nomes com espaços ⚠️ Precisa de tratamento extra ✅ Funciona — grava os bytes como estão
Acesso direto ao registro N ❌ Precisa varrer o arquivo ✅ Cálculo de offset imediato
Velocidade para dados grandes Mais lento — conversão para string ✅ Mais rápido — cópia direta de memória
Em arquivos binários, os dados são gravados exatamente como estão na memória — sem conversão para texto. Uma struct Aluno de 60 bytes no programa ocupa exatamente 60 bytes no arquivo. Isso torna a leitura e escrita triviais e eficientes.

2. Escrevendo Dados Binários: fwrite

fwrite(ptr, tamanho, quantidade, arquivo) — grava quantidade blocos de tamanho bytes a partir do endereço ptr no arquivo. Retorna o número de blocos gravados com sucesso.
#include <stdio.h>

typedef struct {
    int   matricula;
    char  nome[50];
    float nota;
} Aluno;                                   // sizeof(Aluno) ≈ 58 bytes

int main() {
    FILE *f = fopen("alunos.bin", "wb");   // "wb" = write binary
    if (f == NULL) { printf("Erro!\n"); return 1; }

    Aluno a1 = {1001, "Ana Silva",  8.5f};
    Aluno a2 = {1002, "Bruno Lima", 6.0f};
    Aluno a3 = {1003, "Carla Mota", 9.5f};

    /* grava 1 struct por vez */
    fwrite(&a1, sizeof(Aluno), 1, f);
    fwrite(&a2, sizeof(Aluno), 1, f);
    fwrite(&a3, sizeof(Aluno), 1, f);

    fclose(f);
    printf("3 alunos gravados em alunos.bin\n");
    return 0;
}

O arquivo alunos.bin terá exatamente 3 × sizeof(Aluno) bytes. Não há separadores, quebras de linha nem conversão — são os bytes puros de cada struct.

Gravando um array inteiro de uma vez

Aluno turma[3] = {
    {1001, "Ana Silva",  8.5f},
    {1002, "Bruno Lima", 6.0f},
    {1003, "Carla Mota", 9.5f}
};

/* grava os 3 de uma vez — equivalente a 3 chamadas separadas */
fwrite(turma, sizeof(Aluno), 3, f);

3. Lendo Dados Binários: fread

fread(ptr, tamanho, quantidade, arquivo) — lê quantidade blocos de tamanho bytes do arquivo e os armazena a partir do endereço ptr. Retorna o número de blocos lidos com sucesso.
#include <stdio.h>

typedef struct {
    int   matricula;
    char  nome[50];
    float nota;
} Aluno;

int main() {
    FILE *f = fopen("alunos.bin", "rb");   // "rb" = read binary
    if (f == NULL) { printf("Arquivo não encontrado.\n"); return 1; }

    Aluno a;

    printf("%-6s | %-20s | %s\n", "Matr.", "Nome", "Nota");
    printf("------------------------------------\n");

    /* lê uma struct por vez até acabar o arquivo */
    while (fread(&a, sizeof(Aluno), 1, f) == 1) {
        printf("%-6d | %-20s | %.1f\n",
               a.matricula, a.nome, a.nota);
    }

    fclose(f);
    return 0;
}
Matr. | Nome | Nota
------------------------------------
1001 | Ana Silva | 8.5
1002 | Bruno Lima | 6.0
1003 | Carla Mota | 9.5
Simetria total entre fwrite e fread: O que fwrite grava, fread lê exatamente da mesma forma. Não há parsing, não há conversão, não há preocupação com espaços em nomes — os bytes saem da struct e entram de volta na struct sem nenhuma transformação.

4. Acesso Direto: fseek, ftell e rewind

Uma das grandes vantagens do arquivo binário com structs de tamanho fixo é o acesso direto: você pode ir diretamente ao registro N sem ler todos os anteriores.

fseek(arquivo, offset, origem) move o cursor do arquivo para uma posição específica.
ftell(arquivo) retorna a posição atual do cursor (em bytes desde o início).
rewind(arquivo) volta o cursor ao início — equivalente a fseek(f, 0, SEEK_SET).
Origem (3º parâmetro) Significado Exemplo
SEEK_SET Desde o início do arquivo fseek(f, 0, SEEK_SET) — vai ao início
SEEK_CUR Relativo à posição atual fseek(f, 10, SEEK_CUR) — avança 10 bytes
SEEK_END Relativo ao fim do arquivo fseek(f, 0, SEEK_END) — vai ao final

Exemplo — ler o registro de índice N diretamente

#include <stdio.h>

typedef struct {
    int   matricula;
    char  nome[50];
    float nota;
} Aluno;

/* Lê o aluno na posição 'indice' (0-based) sem ler os outros */
int lerAlunoPorIndice(const char *caminho, int indice, Aluno *dest) {
    FILE *f = fopen(caminho, "rb");
    if (f == NULL) return 0;

    long offset = (long)indice * sizeof(Aluno);  // calcula o byte exato
    fseek(f, offset, SEEK_SET);                  // pula direto para lá
    int ok = (fread(dest, sizeof(Aluno), 1, f) == 1);

    fclose(f);
    return ok;
}

/* Conta quantos alunos existem no arquivo */
int contarAlunos(const char *caminho) {
    FILE *f = fopen(caminho, "rb");
    if (f == NULL) return 0;

    fseek(f, 0, SEEK_END);            // vai ao final
    long bytes = ftell(f);            // quantos bytes tem o arquivo
    fclose(f);

    return (int)(bytes / sizeof(Aluno));
}

int main() {
    int total = contarAlunos("alunos.bin");
    printf("Total de alunos no arquivo: %d\n\n", total);

    /* Lê diretamente o último aluno */
    Aluno a;
    if (lerAlunoPorIndice("alunos.bin", total - 1, &a))
        printf("Último: %d | %s | %.1f\n",
               a.matricula, a.nome, a.nota);

    /* Lê diretamente o primeiro */
    if (lerAlunoPorIndice("alunos.bin", 0, &a))
        printf("Primeiro: %d | %s | %.1f\n",
               a.matricula, a.nome, a.nota);

    return 0;
}
Total de alunos no arquivo: 3

Último: 1003 | Carla Mota | 9.5
Primeiro: 1001 | Ana Silva | 8.5
Por que isso é poderoso: Se o arquivo tiver 10.000 alunos e você precisar do de índice 9.999, fseek pula direto para lá em uma instrução — sem varrer os 9.998 anteriores. Isso é o equivalente ao acesso direto de um array, mas em disco.

5. Atualizando um Registro no Arquivo

Com fseek, é possível também sobrescrever um registro específico — sem recriar o arquivo inteiro:

#include <stdio.h>

typedef struct {
    int   matricula;
    char  nome[50];
    float nota;
} Aluno;

/* Atualiza o aluno na posição 'indice' sem tocar nos outros */
int atualizarAluno(const char *caminho, int indice, Aluno *novo) {
    FILE *f = fopen(caminho, "rb+");        // "rb+" = leitura + escrita binária
    if (f == NULL) return 0;

    long offset = (long)indice * sizeof(Aluno);
    fseek(f, offset, SEEK_SET);
    int ok = (fwrite(novo, sizeof(Aluno), 1, f) == 1);

    fclose(f);
    return ok;
}

int main() {
    /* Supondo que alunos.bin já existe com Ana, Bruno, Carla */
    Aluno corrigido = {1002, "Bruno Lima", 7.5f};    // nota de 6.0 para 7.5

    if (atualizarAluno("alunos.bin", 1, &corrigido))
        printf("Nota de Bruno atualizada.\n");

    return 0;
}
O modo "rb+" abre o arquivo binário existente para leitura e escrita simultâneas, sem apagar o conteúdo. É o modo correto para atualizar registros no lugar (in-place update).

6. Texto vs. Binário — Resumo de Escolha

Use arquivo TEXTO quando…
// Dados simples, configurações
// Logs legíveis por humanos
// Troca de dados com outros sistemas
// Nomes sem espaços ou com parse manual
fprintf(f, "%d %.1f\n", mat, nota);
Use arquivo BINÁRIO quando…
// Structs completas com todos os campos
// Nomes com espaços (funciona direto)
// Acesso direto a registros por índice
// Performance com grandes volumes
fwrite(&aluno, sizeof(Aluno), 1, f);
FunçãoDireçãoPara quê
fprintfEscrita textoGravar dados formatados como string
fscanfLeitura textoLer dados formatados — para no espaço
fgetsLeitura textoLer linha inteira com espaços
fwriteEscrita bináriaGravar struct ou array de bytes
freadLeitura bináriaLer struct ou array de bytes
fseekPosicionamentoMover cursor para offset específico
ftellPosição atualSaber quantos bytes desde o início
rewindVolta ao inícioAtalho para fseek(f,0,SEEK_SET)

Quiz — Aula 39b

1. Qual é a principal vantagem de usar fwrite com structs em vez de fprintf campo a campo?

2. Um arquivo binário contém 100 structs Aluno, cada uma com sizeof(Aluno) = 60 bytes. Qual chamada a fseek posiciona o cursor exatamente no início do aluno de índice 7 (0-based)?

3. Para abrir um arquivo binário já existente e modificar um registro específico sem apagar os outros, qual modo de abertura deve ser usado?

4. Como calcular o número de structs armazenadas em um arquivo binário usando fseek e ftell?