E/S de Arquivo

Um arquivo é como um cofre — você deve abri-lo antes de usar (fopen), seguir as regras para guardar e retirar itens (modos de leitura/escrita) e sempre fechá-lo quando terminar (fclose), ou seus dados podem ser perdidos.

Conceitos Básicos de Arquivos

C trata todos os dispositivos externos como "fluxos". Existem dois tipos de fluxos de arquivo:

O sistema operacional gerencia arquivos abertos através de uma estrutura FILE, e nós operamos nela por meio de um ponteiro FILE *.

Ponteiro de Arquivo

C
FILE *fp;

Esse ponteiro é a "chave" para todas as operações subsequentes de arquivo. Sem ele, nada pode ser feito.

Abrindo e Fechando Arquivos

fopen

C
FILE *fopen(const char *filename, const char *mode);
Modo Significado Se o arquivo não existe Se o arquivo existe
"r" Somente leitura Erro Lê do início
"w" Somente escrita Cria Trunca o conteúdo
"a" Anexar Cria Anexa ao final
"r+" Leitura e escrita Erro Opera do início
"w+" Leitura e escrita Cria Trunca o conteúdo
"a+" Leitura e anexar Cria Anexa ao final
"rb" Somente leitura binária Erro Lê do início
"wb" Somente escrita binária Cria Trunca o conteúdo
"ab" Anexar binário Cria Anexa ao final
💡 Dica: O modo "w" trunca arquivos existentes! Esse é o erro mais comum que iniciantes cometem — usar "w" quando queriam anexar, resultando na perda de todos os dados.

fclose

C
int fclose(FILE *fp);

Fechar um arquivo descarrega o buffer e libera recursos. Retorna 0 em caso de sucesso, EOF em caso de falha.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        printf("Falha ao abrir arquivo\n");
        return 1;
    }
    fprintf(fp, "Olá, Arquivo!\n");
    fclose(fp);
    return 0;
}
⚠️ Atenção: Cada fopen deve ter um fclose correspondente. Esquecer de fechar um arquivo causa vazamentos de memória e perda de dados.

E/S de Arquivo de Texto

E/S de Caractere: fgetc / fputc

C
int fgetc(FILE *fp);
int fputc(int ch, FILE *fp);

fgetc lê um caractere, retornando EOF no final do arquivo. fputc escreve um caractere.

E/S de String: fgets / fputs

C
char *fgets(char *str, int n, FILE *fp);
int fputs(const char *str, FILE *fp);

fgets lê no máximo n-1 caracteres, parando em uma nova linha ou final do arquivo, e adiciona '\0' automaticamente.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("poema.txt", "w");
    if (fp == NULL) {
        return 1;
    }
    fputs("Pôr do sol à beira do rio\n", fp);
    fputs("O rio flui para o mar\n", fp);
    fclose(fp);

    fp = fopen("poema.txt", "r");
    if (fp == NULL) {
        return 1;
    }
    char line[256];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("%s", line);
    }
    fclose(fp);
    return 0;
}
TEXT
Pôr do sol à beira do rio
O rio flui para o mar

E/S de Arquivo Binário

fread / fwrite

C
size_t fread(void *ptr, size_t size, size_t count, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp);

Parâmetros: ptr é o buffer de dados, size é o tamanho em bytes de cada elemento, count é o número de elementos. O valor de retorno é o número real de elementos lidos ou escritos.

C
#include <stdio.h>

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main(void) {
    Student stu = {1, "Alice", 92.5f};

    FILE *fp = fopen("student.dat", "wb");
    if (fp == NULL) {
        return 1;
    }
    fwrite(&stu, sizeof(Student), 1, fp);
    fclose(fp);

    Student read_stu;
    fp = fopen("student.dat", "rb");
    if (fp == NULL) {
        return 1;
    }
    fread(&read_stu, sizeof(Student), 1, fp);
    fclose(fp);

    printf("ID: %d, Nome: %s, Nota: %.1f\n",
           read_stu.id, read_stu.name, read_stu.score);
    return 0;
}
TEXT
ID: 1, Nome: Alice, Nota: 92.5
💡 Dica: O modo binário é ideal para armazenar dados de structs — é eficiente e preserva o conteúdo exatamente. O modo de texto pode tratar caracteres de nova linha de forma diferente entre sistemas operacionais, enquanto o modo binário salva tudo como está.

E/S Formatada

fprintf / fscanf

C
int fprintf(FILE *fp, const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);

Seu uso é idêntico a printf/scanf, apenas com um parâmetro adicional de ponteiro de arquivo.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("notas.txt", "w");
    if (fp == NULL) {
        return 1;
    }
    fprintf(fp, "%s %d %.1f\n", "Bob", 2, 88.0);
    fprintf(fp, "%s %d %.1f\n", "Carol", 3, 95.5);
    fclose(fp);

    fp = fopen("notas.txt", "r");
    if (fp == NULL) {
        return 1;
    }
    char name[20];
    int id;
    float score;
    while (fscanf(fp, "%s %d %f", name, &id, &score) == 3) {
        printf("Nome: %s, ID: %d, Nota: %.1f\n", name, id, score);
    }
    fclose(fp);
    return 0;
}
TEXT
Nome: Bob, ID: 2, Nota: 88.0
Nome: Carol, ID: 3, Nota: 95.5

Ponteiro de Posição do Arquivo

Todo arquivo aberto tem um "ponteiro de posição" indicando o local atual de leitura/escrita. É como um marcador de livro enquanto você lê — onde quer que você tenha lido, o marcador marca aquele ponto.

fseek

C
int fseek(FILE *fp, long offset, int origin);
origin Significado
SEEK_SET Início do arquivo
SEEK_CUR Posição atual
SEEK_END Final do arquivo

ftell / rewind

C
long ftell(FILE *fp);
void rewind(FILE *fp);

ftell retorna a posição atual (deslocamento em bytes desde o início do arquivo). rewind é equivalente a fseek(fp, 0, SEEK_SET).

Exemplo

Usando fseek e ftell para calcular o tamanho do arquivo:

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.txt", "rb");
    if (fp == NULL) {
        printf("Falha ao abrir arquivo\n");
        return 1;
    }
    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    rewind(fp);
    printf("Tamanho do arquivo: %ld bytes\n", size);
    fclose(fp);
    return 0;
}
▶ Experimente

Exemplo

Acesso aleatório a um registro específico em um arquivo binário:

C
#include <stdio.h>

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main(void) {
    Student students[3] = {
        {1, "Alice", 92.5f},
        {2, "Bob", 88.0f},
        {3, "Carol", 95.5f}
    };

    FILE *fp = fopen("students.dat", "wb");
    if (fp == NULL) {
        return 1;
    }
    fwrite(students, sizeof(Student), 3, fp);
    fclose(fp);

    fp = fopen("students.dat", "rb");
    if (fp == NULL) {
        return 1;
    }
    int target = 2;
    fseek(fp, (target - 1) * sizeof(Student), SEEK_SET);
    Student s;
    fread(&s, sizeof(Student), 1, fp);
    fclose(fp);

    printf("Registro %d: ID=%d, Nome=%s, Nota=%.1f\n",
           target, s.id, s.name, s.score);
    return 0;
}
▶ Experimente
TEXT
Registro 2: ID=2, Nome=Bob, Nota=88.0
💡 Dica: Esse é o princípio por trás do "acesso aleatório" em bancos de dados! Calculando o deslocamento, você pode saltar diretamente para uma posição específica sem percorrer desde o início.

Outras Funções de Arquivo

feof

C
int feof(FILE *fp);

Verifica se o final do arquivo foi alcançado. Retorna não zero no EOF. Nota: feof só se torna verdadeiro após uma operação de leitura ter falhado, não como verificação prévia.

fflush

C
int fflush(FILE *fp);

Força a descarga do buffer, escrevendo os dados em buffer no arquivo. Chamar fflush em um fluxo de entrada resulta em comportamento indefinido.

remove / rename

C
int remove(const char *filename);
int rename(const char *old, const char *new);

Exclui e renomeia arquivos. Retornam 0 em caso de sucesso.

Padrão Geral para Operações de Arquivo

A maioria das operações de arquivo segue este padrão:

  1. Abra o arquivo com fopen e verifique o valor de retorno
  2. Leia ou escreva dados em um laço
  3. Feche o arquivo com fclose
C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("input.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    fclose(fp);
    return 0;
}
⚠️ Atenção: Verificar o valor de retorno de fopen é uma regra absoluta! O arquivo pode não existir, você pode não ter permissões, ou o disco pode estar cheio — usar um ponteiro nulo sem verificar causará falha imediata.

❓ Perguntas Frequentes

P: fopen retorna NULL mas o arquivo claramente existe. O que está errado? R: Provavelmente é um problema de caminho. Caminhos relativos são baseados no diretório de trabalho do programa, não no diretório do arquivo fonte. Ao executar em uma IDE, o diretório de trabalho pode ser diferente do que você espera.

P: Qual a diferença entre modo texto e modo binário? R: No Windows, o modo texto traduz entre \n e \r\n durante leitura/escrita, enquanto o modo binário não faz nenhuma tradução. No Linux, não há diferença entre os dois.

P: Arquivos escritos com fwrite são compatíveis entre diferentes compiladores? R: Não necessariamente. Alinhamento de memória, ordem de bytes (endianness) e tamanhos de tipos básicos podem diferir. Código multiplataforma requer serialização manual.

P: Por que feof não pode ser usado como condição de laço? R: feof só se torna verdadeiro após uma operação de leitura ter passado do final do arquivo. Usá-lo como condição de laço faz com que a última iteração processe dados inválidos. Use o valor de retorno de fread/fgets em vez disso.

📖 Resumo

📝 Exercícios

  1. Escreva um programa que conte o número de linhas, palavras e caracteres em um arquivo de texto
  2. Implemente um programa simples de cópia de arquivo que leia um arquivo fonte em modo binário e escreva em um arquivo de destino
  3. Escreva um programa que salve um array de structs em um arquivo binário, depois os leia de volta e os imprima
100%