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:
- Fluxo de texto: composto por caracteres visíveis, caracteres de nova linha podem ser traduzidos, adequado para leitura humana
- Fluxo binário: sequência bruta de bytes, lido e escrito como está, adequado para processamento por programa
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
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
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 |
"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
int fclose(FILE *fp);
Fechar um arquivo descarrega o buffer e libera recursos. Retorna 0 em caso de sucesso, EOF em caso de falha.
#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;
}
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
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
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.
#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;
}
Pôr do sol à beira do rio
O rio flui para o mar
E/S de Arquivo Binário
fread / fwrite
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.
#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;
}
ID: 1, Nome: Alice, Nota: 92.5
E/S Formatada
fprintf / fscanf
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.
#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;
}
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
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
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:
#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;
}
Exemplo
Acesso aleatório a um registro específico em um arquivo binário:
#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;
}
Registro 2: ID=2, Nome=Bob, Nota=88.0
Outras Funções de Arquivo
feof
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
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
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:
- Abra o arquivo com
fopene verifique o valor de retorno - Leia ou escreva dados em um laço
- Feche o arquivo com
fclose
#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;
}
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
\ne\r\ndurante 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
- Abra arquivos com
fopene sempre verifique se o valor de retorno éNULL - Use
fgets/fputs,fprintf/fscanfpara modo texto, efread/fwritepara modo binário - O modo
"w"trunca o arquivo, o modo"a"anexa ao final — não os confunda fseek/ftell/rewindcontrolam o ponteiro de posição do arquivo para acesso aleatório- Cada
fopendeve ter umfclosecorrespondente — torne isso um hábito
📝 Exercícios
- Escreva um programa que conte o número de linhas, palavras e caracteres em um arquivo de texto
- 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
- Escreva um programa que salve um array de structs em um arquivo binário, depois os leia de volta e os imprima



