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:

ModoDescriçã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 fopen retornou um ponteiro válido antes de usar.
  • Feche o arquivo assim que terminar de usá-lo, preferencialmente no mesmo escopo.
  • Use fgets para ler linhas completas quando o conteúdo tiver espaços.
  • Limite o tamanho de strings em fscanf para evitar estouro de buffer (ex.: %49s).
  • Trate erros de forma explícita, usando perror ou strerror(errno).
  • Considere usar fwrite e fread para dados binários, mas para texto, fprintf e fscanf sã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

  1. Escreva um programa que crie um arquivo texto chamado alunos.txt e grave nele o nome e a nota de 3 alunos (dados fixos no código). Use o modo "w" e fprintf.

    ✓ 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;
    }
  2. Leia o arquivo alunos.txt criado no exercício 1 e exiba cada nome e nota no terminal. Use fscanf e 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, &nota) == 2) {
            printf("%s: %.2f\n", nome, nota);
        }
        fclose(arquivo);
        return 0;
    }
  3. Crie um programa que adicione um novo aluno ao arquivo alunos.txt sem 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;
    }
  4. Escreva um programa que leia um arquivo texto linha por linha usando fgets e imprima cada linha com um número de linha na frente (ex.: 1: conteúdo). Teste com o arquivo alunos.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;
    }
  5. Implemente uma função chamada contar_linhas que recebe o nome de um arquivo e retorna o número de linhas. Use fopen e fgets, 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;
    }

Referências