Persistência de dados — fopen, fprintf, fscanf e arquivos texto
Todos os programas criados até aqui têm um problema: quando a execução termina, todos os dados somem. Variáveis, structs, listas — tudo é destruído. A próxima vez que o usuário rodar o programa, começa do zero.
Arquivos resolvem isso. Eles permitem que dados sejam gravados em disco e recuperados na próxima execução — tornando o programa genuinamente útil no mundo real.
<stdio.h> — a mesma de printf e scanf. A abstração central é o tipo FILE *: um ponteiro que representa um arquivo aberto.
| Tipo | Conteúdo | Quando usar | Abre no Bloco de Notas? |
|---|---|---|---|
| Texto | Caracteres legíveis (letras, números, quebras de linha) | Dados simples, configurações, logs | ✅ Sim — legível |
| Binário | Bytes brutos (representação interna da memória) | Structs completas, dados grandes e eficientes | ❌ Não — aparece como caracteres estranhos |
Esta aula cobre arquivos texto. A Aula 39b cobre arquivos binários — usados no projeto da Aula 40.
fopen e fclosefopen(caminho, modo) abre um arquivo e retorna um ponteiro FILE *. Se o arquivo não puder ser aberto, retorna NULL. fclose(f) fecha o arquivo e garante que os dados pendentes sejam gravados em disco.
#include <stdio.h>
int main() {
FILE *f; // ponteiro para arquivo
f = fopen("dados.txt", "w"); // abre para escrita
if (f == NULL) { // SEMPRE verificar!
printf("Erro ao abrir o arquivo.\n");
return 1;
}
fprintf(f, "Olá, arquivo!\n"); // escreve no arquivo
fclose(f); // SEMPRE fechar!
printf("Arquivo gravado com sucesso.\n");
return 0;
}
| Modo | Significado | Arquivo não existe? | Apaga conteúdo anterior? |
|---|---|---|---|
"r" | Leitura | Retorna NULL (erro) | Não apaga |
"w" | Escrita | Cria o arquivo | ✅ Apaga tudo |
"a" | Append — acrescenta ao final | Cria o arquivo | Não apaga |
"r+" | Leitura + escrita | Retorna NULL (erro) | Não apaga |
"rb", "wb" | Binário (leitura/escrita) | Igual, mas modo binário | Ver Aula 39b |
fprintf e fputsfprintf funciona exatamente como printf — mas o primeiro argumento é o ponteiro de arquivo, não a tela.
#include <stdio.h>
int main() {
FILE *f = fopen("alunos.txt", "w");
if (f == NULL) { printf("Erro!\n"); return 1; }
fprintf(f, "Nome: Ana Souza\n");
fprintf(f, "Matrícula: %d\n", 1001);
fprintf(f, "Nota: %.1f\n", 8.5f);
fprintf(f, "---\n");
fprintf(f, "Nome: Bruno Lima\n");
fprintf(f, "Matrícula: %d\n", 1002);
fprintf(f, "Nota: %.1f\n", 6.0f);
fclose(f);
printf("alunos.txt criado.\n");
return 0;
}
FILE *f = fopen("out.txt", "w");
fprintf(f, "valor: %d\n", 42);
fclose(f);
FileWriter fw =
new FileWriter("out.txt");
fw.write("valor: 42\n");
fw.close();
fscanf e fgetsfscanf lê dados formatados de um arquivo — como scanf lê do teclado. fgets lê uma linha inteira, incluindo espaços.
#include <stdio.h>
int main() {
FILE *f = fopen("numeros.txt", "r");
if (f == NULL) { printf("Arquivo não encontrado.\n"); return 1; }
int n;
/* fscanf retorna o número de itens lidos — EOF (-1) quando o arquivo termina */
while (fscanf(f, "%d", &n) == 1) {
printf("Lido: %d\n", n);
}
fclose(f);
return 0;
}
#include <stdio.h>
#include <string.h>
int main() {
FILE *f = fopen("alunos.txt", "r");
if (f == NULL) { printf("Erro!\n"); return 1; }
char linha[200];
/* fgets lê até 199 caracteres OU até \n — retorna NULL no fim do arquivo */
while (fgets(linha, sizeof(linha), f) != NULL) {
linha[strcspn(linha, "\n")] = '\0'; // remove o \n do final
printf("[%s]\n", linha);
}
fclose(f);
return 0;
}
fgets para leitura linha a linha quando os dados têm espaços ou formato variável. Use fscanf para dados com formato fixo e conhecido. Para structs completas em formato eficiente, prefira arquivos binários (Aula 39b).
#include <stdio.h>
typedef struct {
int matricula;
char nome[50];
float nota;
} Aluno;
int gravarAlunos(const char *caminho, Aluno v[], int n) {
FILE *f = fopen(caminho, "w");
if (f == NULL) return 0;
fprintf(f, "%d\n", n); // 1ª linha: quantidade
for (int i = 0; i < n; i++)
fprintf(f, "%d %s %.1f\n",
v[i].matricula, v[i].nome, v[i].nota);
fclose(f);
return 1;
}
int lerAlunos(const char *caminho, Aluno v[], int maxN) {
FILE *f = fopen(caminho, "r");
if (f == NULL) return -1; // -1 = arquivo não existe
int n;
fscanf(f, "%d\n", &n);
if (n > maxN) n = maxN;
for (int i = 0; i < n; i++)
fscanf(f, "%d %s %f\n",
&v[i].matricula, v[i].nome, &v[i].nota);
fclose(f);
return n;
}
int main() {
Aluno turma[] = {
{1001, "Ana", 8.5f},
{1002, "Bruno", 6.0f},
{1003, "Carla", 9.5f}
};
if (gravarAlunos("turma.txt", turma, 3))
printf("Dados gravados em turma.txt\n");
Aluno recuperados[10];
int total = lerAlunos("turma.txt", recuperados, 10);
printf("\n=== Dados recuperados ===\n");
for (int i = 0; i < total; i++)
printf(" %d | %-15s | %.1f\n",
recuperados[i].matricula,
recuperados[i].nome,
recuperados[i].nota);
return 0;
}
fscanf("%s") para no primeiro espaço — "Ana Silva" seria lido apenas como "Ana". Para nomes com espaços, use fgets e parse manual, ou prefira arquivo binário (Aula 39b) que grava e lê a struct inteira de uma vez.
"a"O modo "a" (append) preserva o conteúdo existente e posiciona o cursor no final. Ideal para logs e históricos:
#include <stdio.h>
void registrarLog(const char *mensagem) {
FILE *f = fopen("log.txt", "a"); // abre ou cria sem apagar
if (f == NULL) return;
fprintf(f, "%s\n", mensagem);
fclose(f);
}
int main() {
registrarLog("Programa iniciado");
registrarLog("Usuário fez login");
registrarLog("Busca realizada: matrícula 1001");
registrarLog("Programa encerrado");
printf("Log gravado em log.txt\n");
return 0;
}
A cada nova execução do programa, novas linhas são adicionadas ao final — o histórico se acumula sem perder dados anteriores.
| Aspecto | C | Java |
|---|---|---|
| Biblioteca | <stdio.h> (inclusa por padrão) | java.io.* |
| Abrir | fopen(caminho, modo) | new FileWriter(...) / new BufferedReader(...) |
| Escrever | fprintf(f, "...") | fw.write("...") |
| Ler linha | fgets(buf, tam, f) | br.readLine() |
| Fechar | fclose(f) — manual e obrigatório | try-with-resources (automático) |
| Erro de abertura | fopen retorna NULL — você verifica | Lança IOException — compilador força o tratamento |
fopen e chamar fclose é responsabilidade exclusiva do programador — esquecer é um dos erros mais comuns em código C real.