Arquivos texto
Nesta aula, você aprenderá a manipular arquivos texto em C, desde a abertura e fechamento com fopen e fclose, passando pelos modos de abertura, até a leitura e escrita formatada com fprintf e fscanf, além de boas práticas para tratamento de erros.
Bem-vindo à aula 50 do nosso curso de Linguagem C! Hoje vamos explorar um dos recursos mais poderosos da linguagem: a manipulação de arquivos texto. Trabalhar com arquivos é essencial para criar programas que persistam dados, como configurações, logs, resultados de processamento e muito mais. Nesta aula, você aprenderá a abrir, ler, escrever e fechar arquivos texto de forma eficiente e segura, utilizando as funções padrão da biblioteca stdio.h.
Dominar a manipulação de arquivos é um marco no aprendizado de C, pois permite que seus programas interajam com o sistema de arquivos do computador, tornando-os muito mais úteis e versáteis. Vamos começar com os conceitos fundamentais e, ao final, você será capaz de criar programas que leem e gravam dados em arquivos texto com confiança.
fopen e fclose
A função fopen é a porta de entrada para qualquer operação com arquivos em C. Ela estabelece uma conexão entre o seu programa e um arquivo físico no disco, criando um fluxo de dados (stream) que pode ser usado para leitura ou escrita. A função recebe dois argumentos: o caminho do arquivo (uma string) e o modo de abertura (outra string). Ela retorna um ponteiro para uma estrutura FILE, que é usada em todas as operações subsequentes com aquele arquivo.
Por outro lado, fclose é a função que encerra essa conexão. Ela é crucial porque garante que todos os dados pendentes sejam gravados no disco (esvaziamento de buffers) e libera os recursos do sistema operacional associados ao arquivo. Esquecer de fechar um arquivo pode causar perda de dados ou vazamento de memória, especialmente em programas longos. Portanto, a regra de ouro é: para cada fopen bem-sucedido, deve haver um fclose correspondente.
Exemplo básico de abertura e fechamento:
#include <stdio.h>
int main() {
FILE *arquivo = fopen("dados.txt", "r");
if (arquivo == NULL) {
printf("Erro ao abrir o arquivo!\n");
return 1;
}
// Operações de leitura/escrita...
fclose(arquivo);
return 0;
}Observe que sempre verificamos se fopen retornou NULL, o que indica falha na abertura (por exemplo, arquivo inexistente ou permissão negada). Essa verificação é o primeiro passo do tratamento de erros, que veremos mais adiante.
Modos
O modo de abertura define como o arquivo será acessado: se para leitura, escrita, ou ambos, e também se o conteúdo existente será preservado ou sobrescrito. A escolha correta do modo é fundamental para evitar comportamentos inesperados. A tabela abaixo resume os modos mais comuns:
| Modo | Descrição |
|---|---|
"r" | Leitura. O arquivo deve existir; caso contrário, fopen retorna NULL. |
"w" | Escrita. Cria um arquivo novo ou sobrescreve o existente (trunca para tamanho zero). |
"a" | Adição (append). Abre para escrita no final do arquivo. Cria o arquivo se não existir. |
"r+" | Leitura e escrita. O arquivo deve existir; permite atualizar o conteúdo. |
"w+" | Leitura e escrita. Cria um arquivo novo ou sobrescreve; permite leitura e escrita. |
"a+" | Leitura e adição. Abre para leitura e escrita, sempre escrevendo no final. Cria se não existir. |
Além desses, podemos adicionar o sufixo b para modo binário (ex.: "rb", "wb"), mas nesta aula focaremos em arquivos texto, que são o padrão quando o modo binário não é especificado. Em sistemas Unix, texto e binário são tratados da mesma forma, mas no Windows a distinção é relevante (conversão de quebras de linha).
Exemplo de uso dos modos:
// Abre para leitura
FILE *f1 = fopen("entrada.txt", "r");
// Abre para escrita (sobrescreve)
FILE *f2 = fopen("saida.txt", "w");
// Abre para adição
FILE *f3 = fopen("log.txt", "a");Lembre-se: ao usar "w", o conteúdo anterior é perdido. Se precisar preservar, use "a" ou "r+" com cuidado.
fprintf e fscanf
As funções fprintf e fscanf são as versões para arquivos das conhecidas printf e scanf. Elas permitem escrever e ler dados formatados em um arquivo texto, o que é extremamente útil para armazenar informações de forma legível e estruturada.
fprintf recebe o ponteiro do arquivo como primeiro argumento, seguido da string de formato e dos valores a serem impressos. É idêntica ao printf no resto. Por exemplo:
FILE *arquivo = fopen("pessoas.txt", "w");
if (arquivo) {
fprintf(arquivo, "Nome: %s, Idade: %d\n", "Maria", 30);
fprintf(arquivo, "Nome: %s, Idade: %d\n", "João", 25);
fclose(arquivo);
}Isso criará um arquivo com duas linhas contendo os dados formatados.
fscanf funciona de forma análoga ao scanf, mas lê de um arquivo. Ela retorna o número de itens atribuídos com sucesso, ou EOF se o fim do arquivo for atingido antes de qualquer conversão. É importante verificar esse retorno para evitar leituras incorretas.
FILE *arquivo = fopen("pessoas.txt", "r");
char nome[50];
int idade;
while (fscanf(arquivo, "Nome: %49s, Idade: %d\n", nome, &idade) == 2) {
printf("Lido: %s tem %d anos\n", nome, idade);
}
fclose(arquivo);Observe o uso de %49s para limitar a leitura e evitar estouro de buffer. Essa é uma boa prática em C.
Um detalhe importante: fscanf pode ser traiçoeiro com strings que contenham espaços, pois %s lê até o primeiro espaço em branco. Para ler linhas inteiras, considere usar fgets em conjunto com sscanf para uma abordagem mais robusta.
Tratamento de erros
Lidar com erros é essencial em qualquer programa que interaja com o sistema de arquivos. As operações podem falhar por diversos motivos: arquivo não encontrado, permissões insuficientes, disco cheio, etc. Em C, a verificação de erros é feita principalmente através dos valores de retorno das funções.
Já vimos que fopen retorna NULL em caso de falha. Para fclose, o retorno é EOF se houver erro (como falha ao gravar dados pendentes). As funções de leitura/escrita (como fprintf, fscanf, fread, fwrite) retornam a quantidade de itens processados; se for menor que o esperado, houve erro.
Além disso, a variável global errno (definida em <errno.h>) pode ser usada para obter o código do erro, e a função perror imprime uma mensagem descritiva na saída padrão de erros. Exemplo:
#include <stdio.h>
#include <errno.h>
int main() {
FILE *arquivo = fopen("inexistente.txt", "r");
if (arquivo == NULL) {
perror("Erro ao abrir arquivo");
// Exibe: Erro ao abrir arquivo: No such file or directory
return 1;
}
// ...
if (fclose(arquivo) == EOF) {
perror("Erro ao fechar arquivo");
}
return 0;
}Outra função útil é feof, que verifica se o indicador de fim de arquivo foi acionado, e ferror, que verifica se ocorreu um erro de leitura/escrita. Elas são frequentemente usadas em loops de leitura.
while (!feof(arquivo)) {
// leitura
if (ferror(arquivo)) {
perror("Erro de leitura");
break;
}
}Uma boa prática é sempre verificar o retorno de fscanf e não usar feof como condição única de parada, pois feof só se torna verdadeiro após uma tentativa de leitura que falhou. Prefira verificar o retorno da função de leitura.
Boas práticas e observações finais
Para escrever código robusto e profissional, adote as seguintes práticas:
- Sempre verifique se
fopenretornou um ponteiro válido antes de usar. - Feche o arquivo assim que terminar de usá-lo, preferencialmente no mesmo escopo.
- Use
fgetspara ler linhas completas quando o conteúdo tiver espaços. - Limite o tamanho de strings em
fscanfpara evitar estouro de buffer (ex.:%49s). - Trate erros de forma explícita, usando
perroroustrerror(errno). - Considere usar
fwriteefreadpara dados binários, mas para texto,fprintfefscanfsão suficientes e mais legíveis.
Agora você está pronto para colocar a mão na massa! Os exercícios a seguir vão consolidar o aprendizado.
Exercícios
Escreva um programa que crie um arquivo texto chamado
alunos.txte grave nele o nome e a nota de 3 alunos (dados fixos no código). Use o modo"w"efprintf.✓ Resposta:#include <stdio.h> int main() { FILE *arquivo = fopen("alunos.txt", "w"); if (arquivo == NULL) { perror("Erro ao criar arquivo"); return 1; } fprintf(arquivo, "%s %.2f\n", "Ana", 8.5); fprintf(arquivo, "%s %.2f\n", "Bruno", 7.0); fprintf(arquivo, "%s %.2f\n", "Carla", 9.2); fclose(arquivo); return 0; }Leia o arquivo
alunos.txtcriado no exercício 1 e exiba cada nome e nota no terminal. Usefscanfe verifique o retorno.✓ Resposta:#include <stdio.h> int main() { FILE *arquivo = fopen("alunos.txt", "r"); if (arquivo == NULL) { perror("Erro ao abrir arquivo"); return 1; } char nome[50]; float nota; while (fscanf(arquivo, "%49s %f", nome, ¬a) == 2) { printf("%s: %.2f\n", nome, nota); } fclose(arquivo); return 0; }Crie um programa que adicione um novo aluno ao arquivo
alunos.txtsem apagar os dados existentes. Use o modo"a".✓ Resposta:#include <stdio.h> int main() { FILE *arquivo = fopen("alunos.txt", "a"); if (arquivo == NULL) { perror("Erro ao abrir arquivo"); return 1; } fprintf(arquivo, "%s %.2f\n", "Daniel", 6.8); fclose(arquivo); return 0; }Escreva um programa que leia um arquivo texto linha por linha usando
fgetse imprima cada linha com um número de linha na frente (ex.:1: conteúdo). Teste com o arquivoalunos.txt.✓ Resposta:#include <stdio.h> int main() { FILE *arquivo = fopen("alunos.txt", "r"); if (arquivo == NULL) { perror("Erro ao abrir arquivo"); return 1; } char linha[100]; int numero = 1; while (fgets(linha, sizeof(linha), arquivo) != NULL) { printf("%d: %s", numero, linha); numero++; } fclose(arquivo); return 0; }Implemente uma função chamada
contar_linhasque recebe o nome de um arquivo e retorna o número de linhas. Usefopenefgets, e trate erros retornando -1.✓ Resposta:#include <stdio.h> int contar_linhas(const char *nome_arquivo) { FILE *arquivo = fopen(nome_arquivo, "r"); if (arquivo == NULL) { return -1; } int linhas = 0; char buffer[256]; while (fgets(buffer, sizeof(buffer), arquivo) != NULL) { linhas++; } fclose(arquivo); return linhas; } int main() { int total = contar_linhas("alunos.txt"); if (total == -1) { printf("Erro ao abrir arquivo\n"); } else { printf("O arquivo tem %d linhas\n", total); } return 0; }