Arquivos texto
Nesta aula, você aprenderá a manipular arquivos texto em C usando as funções fopen, fclose, fprintf e fscanf, além de entender os modos de abertura e como tratar erros de forma robusta. Vamos explorar exemplos práticos e boas práticas para programação defensiva.
Nesta aula, vamos explorar a manipulação de arquivos texto em C, um tópico essencial para qualquer programador que deseja persistir dados entre execuções do programa. Arquivos texto são aqueles que armazenam dados em formato legível, como números e caracteres, organizados em linhas. Diferente de arquivos binários, eles podem ser abertos em qualquer editor de texto, o que facilita a depuração e a interoperabilidade.
Começaremos com as funções básicas de abertura e fechamento, passando pelos modos de abertura, e depois focaremos nas funções de entrada e saída formatada (fprintf e fscanf). Por fim, discutiremos como tratar erros de forma eficaz, garantindo que seus programas sejam confiáveis e não falhem silenciosamente. Ao final, você terá uma base sólida para criar programas que leem e escrevem arquivos texto com segurança.
fopen e fclose
A função fopen é a porta de entrada para trabalhar com arquivos em C. Ela é declarada em <stdio.h> e recebe dois argumentos: o nome do arquivo (ou caminho) e o modo de abertura. Retorna um ponteiro para FILE que será usado em todas as operações subsequentes, ou NULL em caso de erro. O protótipo é:
FILE *fopen(const char *filename, const char *mode);O ponteiro FILE é um tipo opaco (não precisamos saber sua estrutura interna) que armazena informações sobre o arquivo, como posição atual, buffer e estado. É fundamental verificar se fopen retornou NULL antes de prosseguir, pois isso indica que a abertura falhou (arquivo não existe, permissões insuficientes, etc.).
A função fclose fecha o arquivo associado ao ponteiro FILE. Ela libera os recursos alocados e garante que todos os dados pendentes sejam gravados no disco. O protótipo é:
int fclose(FILE *stream);Retorna 0 em sucesso ou EOF (uma constante negativa) em caso de erro. Embora o sistema feche os arquivos automaticamente quando o programa termina, é uma boa prática fechá-los explicitamente para evitar vazamento de recursos e garantir a integridade dos dados, especialmente em programas longos.
Exemplo básico:
#include <stdio.h>
int main() {
FILE *fp = fopen("exemplo.txt", "w");
if (fp == NULL) {
perror("Erro ao abrir arquivo");
return 1;
}
fprintf(fp, "Olá, mundo!\n");
fclose(fp);
return 0;
}Modos
Os modos de abertura determinam como o arquivo será acessado. Eles são passados como uma string no segundo argumento do fopen. Os modos mais comuns para arquivos texto são:
"r": abre para leitura. O arquivo deve existir; caso contrário, retornaNULL."w": abre para escrita. Se o arquivo existir, seu conteúdo é truncado (apagado). Se não existir, cria um novo."a": abre para anexar (append). Escreve no final do arquivo. Se não existir, cria um novo."r+": abre para leitura e escrita. O arquivo deve existir."w+": abre para leitura e escrita, mas trunca o arquivo se existir."a+": abre para leitura e escrita, mas a escrita é sempre no final.
Além disso, podemos adicionar a letra b para modo binário (ex.: "rb"), mas nesta aula focaremos em texto. É importante notar que, em sistemas Unix/Linux, não há diferença entre texto e binário, mas em Windows, o modo texto faz conversões de quebras de linha automaticamente (CRLF para LF na leitura e vice-versa na escrita).
Exemplo de uso de modos:
#include <stdio.h>
int main() {
FILE *fp;
// Leitura
fp = fopen("dados.txt", "r");
if (fp == NULL) { perror("Erro"); return 1; }
fclose(fp);
// Escrita (trunca)
fp = fopen("dados.txt", "w");
if (fp == NULL) { perror("Erro"); return 1; }
fclose(fp);
// Anexar
fp = fopen("dados.txt", "a");
if (fp == NULL) { perror("Erro"); return 1; }
fclose(fp);
return 0;
}fprintf e fscanf
As funções fprintf e fscanf são as versões para arquivos das funções printf e scanf. Elas permitem escrever e ler dados formatados de um arquivo texto, respectivamente. Seus protótipos são:
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);fprintf retorna o número de caracteres escritos, ou um valor negativo em caso de erro. fscanf retorna o número de itens lidos com sucesso, ou EOF se o fim do arquivo for atingido ou ocorrer um erro.
Um ponto crucial é que fscanf lê os dados de acordo com o formato, ignorando espaços em branco (incluindo quebras de linha) automaticamente, exceto quando usamos especificadores como %c ou %[. Isso pode causar problemas se os dados não estiverem bem formatados, então é essencial verificar o valor de retorno.
Exemplo prático: criar um arquivo com dados de alunos e depois ler esses dados.
#include <stdio.h>
int main() {
FILE *fp;
int id;
char nome[50];
float nota;
// Escrita
fp = fopen("alunos.txt", "w");
if (fp == NULL) { perror("Erro"); return 1; }
fprintf(fp, "%d %s %.2f\n", 1, "Maria", 9.5);
fprintf(fp, "%d %s %.2f\n", 2, "João", 7.8);
fclose(fp);
// Leitura
fp = fopen("alunos.txt", "r");
if (fp == NULL) { perror("Erro"); return 1; }
while (fscanf(fp, "%d %49s %f", &id, nome, ¬a) == 3) {
printf("ID: %d, Nome: %s, Nota: %.2f\n", id, nome, nota);
}
fclose(fp);
return 0;
}Note que usamos %49s para evitar estouro de buffer, pois %s não verifica limites. Além disso, o loop while verifica se a leitura retornou 3 itens, garantindo que o formato estava correto.
Tratamento de erros
Tratar erros corretamente é vital para a robustez do programa. As funções de arquivo geralmente retornam indicadores de erro que devem ser verificados. Além de verificar o retorno de fopen, fclose, fprintf e fscanf, podemos usar a função ferror para verificar se ocorreu um erro no fluxo, e feof para verificar se o fim do arquivo foi atingido.
A função perror é útil para imprimir uma mensagem de erro descritiva baseada na variável global errno. Ela é declarada em <stdio.h> e <errno.h>. Exemplo:
FILE *fp = fopen("arquivo.txt", "r");
if (fp == NULL) {
perror("Erro ao abrir arquivo");
exit(1);
}Outra boa prática é usar feof e ferror em loops de leitura para distinguir entre fim de arquivo e erro real. Por exemplo:
while (!feof(fp)) {
if (fscanf(fp, "%d", &valor) != 1) {
if (feof(fp)) break;
else { perror("Erro de leitura"); break; }
}
// processa valor
}Além disso, devemos sempre fechar o arquivo mesmo em caso de erro, para evitar vazamento de recursos. Podemos usar goto ou estruturas de controle para garantir o fechamento, mas uma abordagem simples é fechar antes de retornar.
Exemplo completo com tratamento de erros:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("dados.txt", "w");
if (fp == NULL) {
perror("Erro ao criar arquivo");
return EXIT_FAILURE;
}
if (fprintf(fp, "Linha de teste\n") < 0) {
perror("Erro ao escrever");
fclose(fp);
return EXIT_FAILURE;
}
if (fclose(fp) != 0) {
perror("Erro ao fechar arquivo");
return EXIT_FAILURE;
}
printf("Arquivo gravado com sucesso!\n");
return EXIT_SUCCESS;
}Boas práticas e observações finais
Quando trabalhamos com arquivos, devemos sempre lembrar de:
- Verificar o retorno de todas as funções de arquivo.
- Fechar os arquivos explicitamente.
- Usar limites em
%spara evitar estouro de buffer. - Preferir
fgetspara ler linhas inteiras, pois é mais seguro quefscanfcom%s. - Considerar a portabilidade entre sistemas (quebras de linha).
Em resumo, dominar a manipulação de arquivos texto é essencial para criar programas que interagem com o sistema de arquivos de forma eficaz. Com as funções apresentadas e o tratamento de erros adequado, você estará pronto para desenvolver aplicações mais complexas.
Referências
- fopen - cppreference.com
- fclose - cppreference.com
- fprintf - cppreference.com
- fscanf - cppreference.com
- ferror - cppreference.com
- feof - cppreference.com
- Streams and Cookies - GNU C Library
Exercícios
- Escreva um programa que crie um arquivo chamado "notas.txt" e grave nele o nome e a nota de 3 alunos (pode ser fixo). Depois, leia o arquivo e imprima os dados no terminal.
- Qual é a diferença entre os modos "w" e "a"? Dê um exemplo de situação onde cada um é apropriado.
- Crie um programa que leia um arquivo texto chamado "entrada.txt" contendo números inteiros (um por linha) e escreva em "saida.txt" apenas os números pares.
- Explique como verificar se um erro ocorreu durante uma operação de leitura usando
ferrorefeof. Dê um exemplo. - Escreva um programa que abra um arquivo para escrita, escreva uma linha, e depois tente abrir o mesmo arquivo para leitura. Se a abertura para leitura falhar, exiba uma mensagem de erro usando
perror. Trate todos os possíveis erros.
#include <stdio.h>
int main() {
FILE *fp = fopen("notas.txt", "w");
if (fp == NULL) { perror("Erro"); return 1; }
fprintf(fp, "Ana 8.5\n");
fprintf(fp, "Bruno 7.2\n");
fprintf(fp, "Carla 9.0\n");
fclose(fp);
fp = fopen("notas.txt", "r");
if (fp == NULL) { perror("Erro"); return 1; }
char nome[50];
float nota;
while (fscanf(fp, "%49s %f", nome, ¬a) == 2) {
printf("%s: %.1f\n", nome, nota);
}
fclose(fp);
return 0;
}#include <stdio.h>
int main() {
FILE *in = fopen("entrada.txt", "r");
if (in == NULL) { perror("Erro ao abrir entrada"); return 1; }
FILE *out = fopen("saida.txt", "w");
if (out == NULL) { perror("Erro ao abrir saída"); fclose(in); return 1; }
int num;
while (fscanf(in, "%d", &num) == 1) {
if (num % 2 == 0) {
fprintf(out, "%d\n", num);
}
}
fclose(in);
fclose(out);
return 0;
}feof(fp) para saber se o fim do arquivo foi atingido, e ferror(fp) para saber se ocorreu um erro. Se fscanf não retornar o número esperado de itens, devemos verificar essas funções para distinguir entre fim de arquivo e erro real. Exemplo: int valor;
while (fscanf(fp, "%d", &valor) == 1) {
// processa
}
if (feof(fp)) {
printf("Fim do arquivo alcançado\n");
} else if (ferror(fp)) {
perror("Erro de leitura");
}#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("teste.txt", "w");
if (fp == NULL) { perror("Erro ao abrir para escrita"); return EXIT_FAILURE; }
if (fprintf(fp, "Linha de teste\n") < 0) {
perror("Erro ao escrever");
fclose(fp);
return EXIT_FAILURE;
}
if (fclose(fp) != 0) { perror("Erro ao fechar"); return EXIT_FAILURE; }
fp = fopen("teste.txt", "r");
if (fp == NULL) { perror("Erro ao abrir para leitura"); return EXIT_FAILURE; }
char buffer[100];
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("Lido: %s", buffer);
} else {
perror("Erro ao ler");
}
fclose(fp);
return EXIT_SUCCESS;
}