Ler arquivos é uma operação fundamental em programas C, seja para carregar configurações, processar dados ou importar informações. Nesta aula, você vai dominar as funções fgetc e fgets, que são as principais ferramentas para leitura de arquivos em C. Além disso, entenderemos o papel do marcador EOF (End of File) e como detectá-lo para saber quando parar de ler.

Vamos explorar desde a abertura do arquivo com fopen até a leitura segura de linhas e caracteres, com exemplos práticos que você pode compilar e testar. Ao final, você terá uma base sólida para manipular arquivos de texto em seus programas.

fgets e fgetc

As funções fgets e fgetc são utilizadas para ler dados de um arquivo. fgetc lê um único caractere por vez, enquanto fgets lê uma linha inteira (até um limite de caracteres ou até encontrar uma quebra de linha). Ambas retornam um valor especial quando o arquivo termina, que veremos mais adiante.

fgetc recebe como argumento o ponteiro para o arquivo (retornado por fopen) e retorna o caractere lido como um int. É importante que o retorno seja int e não char para podermos comparar com EOF, que é um valor negativo (geralmente -1).

Exemplo de uso de fgetc:

#include <stdio.h>

int main() {
    FILE *arquivo = fopen("exemplo.txt", "r");
    if (arquivo == NULL) {
        printf("Erro ao abrir arquivo!\n");
        return 1;
    }

    int caractere;
    while ((caractere = fgetc(arquivo)) != EOF) {
        putchar(caractere); // imprime o caractere na tela
    }

    fclose(arquivo);
    return 0;
}

Já fgets é mais segura para ler linhas, pois você especifica o tamanho máximo do buffer, evitando estouro de memória. Ela lê até n-1 caracteres ou até encontrar uma nova linha (\n), incluindo-a no buffer, e adiciona o terminador nulo \0 ao final.

Exemplo de uso de fgets:

#include <stdio.h>

int main() {
    FILE *arquivo = fopen("exemplo.txt", "r");
    if (arquivo == NULL) {
        printf("Erro ao abrir arquivo!\n");
        return 1;
    }

    char linha[100];
    while (fgets(linha, sizeof(linha), arquivo) != NULL) {
        printf("%s", linha); // a linha já contém a quebra de linha
    }

    fclose(arquivo);
    return 0;
}

Note que fgets retorna NULL quando não consegue ler mais nada (fim de arquivo ou erro). Isso facilita o loop de leitura.

Lendo linha a linha

Ler um arquivo linha a linha é uma necessidade comum, especialmente para arquivos de texto com dados estruturados, como CSVs ou logs. A função fgets é ideal para isso, pois lê uma linha por chamada, respeitando o limite de tamanho do buffer.

Vamos considerar um arquivo chamado alunos.txt com o seguinte conteúdo:

Maria,20
João,22
Ana,19

Podemos ler e processar cada linha da seguinte forma:

#include <stdio.h>
#include <string.h>

int main() {
    FILE *arquivo = fopen("alunos.txt", "r");
    if (arquivo == NULL) {
        printf("Erro ao abrir arquivo!\n");
        return 1;
    }

    char linha[100];
    int numeroLinha = 1;
    while (fgets(linha, sizeof(linha), arquivo) != NULL) {
        // Remove a quebra de linha do final, se houver
        linha[strcspn(linha, "\n")] = '\0';

        // Divide a linha em nome e idade
        char *nome = strtok(linha, ",");
        char *idade = strtok(NULL, ",");

        if (nome != NULL && idade != NULL) {
            printf("Aluno %d: %s, idade %s\n", numeroLinha, nome, idade);
        } else {
            printf("Linha %d inválida\n", numeroLinha);
        }
        numeroLinha++;
    }

    fclose(arquivo);
    return 0;
}

Observe que usamos strcspn para remover o \n que fgets inclui. Isso é importante se você quiser manipular os dados sem a quebra de linha. Caso a linha seja maior que o buffer (99 caracteres), fgets lerá em partes, o que pode quebrar a lógica de processamento. Por isso, escolha um tamanho de buffer adequado ou use outras técnicas (como getline, que não é padrão, mas disponível em alguns sistemas).

EOF

EOF (End of File) é uma constante definida em stdio.h que indica o fim de um arquivo. Em C, EOF é um valor inteiro negativo (geralmente -1). Quando você tenta ler além do último caractere, as funções de leitura retornam EOF (no caso de fgetc) ou NULL (no caso de fgets).

É crucial verificar essas condições para evitar loops infinitos ou leitura de dados inválidos. No exemplo com fgetc, o loop continua enquanto o caractere lido for diferente de EOF. Já no fgets, o loop para quando a função retorna NULL.

Além disso, existe a função feof que verifica se o indicador de fim de arquivo está ativo. No entanto, é recomendado usar o retorno das funções de leitura em vez de feof diretamente, pois feof só se torna verdadeiro após uma tentativa de leitura que ultrapasse o final. Veja um exemplo:

#include <stdio.h>

int main() {
    FILE *arquivo = fopen("exemplo.txt", "r");
    if (arquivo == NULL) {
        printf("Erro ao abrir arquivo!\n");
        return 1;
    }

    char buffer[100];
    while (1) {
        if (fgets(buffer, sizeof(buffer), arquivo) == NULL) {
            if (feof(arquivo)) {
                printf("Fim do arquivo alcançado.\n");
            } else {
                printf("Erro de leitura.\n");
            }
            break;
        }
        printf("Linha: %s", buffer);
    }

    fclose(arquivo);
    return 0;
}

Esse código usa feof para distinguir entre fim de arquivo e erro, mas o loop é controlado pelo retorno de fgets.

Cuidados

Ao trabalhar com leitura de arquivos, alguns cuidados são essenciais para evitar bugs e vulnerabilidades:

  • Sempre verifique se o arquivo foi aberto corretamente: fopen retorna NULL se houver erro. Você deve tratar isso antes de continuar.
  • Use buffers com tamanho adequado: Com fgets, defina um limite que não seja muito pequeno para não truncar linhas, nem muito grande para não desperdiçar memória. Se precisar de linhas longas, considere alocação dinâmica e a função getline (em POSIX).
  • Feche o arquivo com fclose: Isso libera recursos do sistema. Esquecer de fechar pode causar vazamentos de memória ou impedir que outros processos acessem o arquivo.
  • Trate erros de leitura: Além do EOF, pode haver erros de I/O. Use ferror para verificar erros após uma operação de leitura.
  • Cuidado com encoding e caracteres especiais: Se o arquivo contém caracteres multibyte (como UTF-8), a leitura caractere por caractere com fgetc pode não funcionar como esperado para caracteres acentuados. Prefira fgets ou bibliotecas de wide characters.

Exemplo de uso de ferror:

#include <stdio.h>

int main() {
    FILE *arquivo = fopen("dados.txt", "r");
    if (arquivo == NULL) {
        perror("Erro ao abrir arquivo");
        return 1;
    }

    char linha[256];
    while (fgets(linha, sizeof(linha), arquivo) != NULL) {
        // processa linha
    }

    if (ferror(arquivo)) {
        perror("Erro durante a leitura");
    }

    fclose(arquivo);
    return 0;
}

Boas Práticas

Além dos cuidados, algumas boas práticas tornam seu código mais robusto e legível:

  • Use nomes de variáveis claros e constantes para o tamanho do buffer.
  • Centralize o processamento de cada linha em uma função separada, se o arquivo tiver muitas linhas.
  • Considere usar fopen com modos como "rb" para arquivos binários, mas em arquivos de texto o modo "r" é suficiente.
  • Documente o formato esperado do arquivo no código.

Referências

Exercícios

  1. Escreva um programa que abra um arquivo chamado "entrada.txt" e use fgetc para contar quantos caracteres existem no arquivo (sem contar o EOF). Imprima o total no final.
  2. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        FILE *arquivo = fopen("entrada.txt", "r");
        if (arquivo == NULL) {
            printf("Erro ao abrir arquivo!\n");
            return 1;
        }
    
        int c;
        long contador = 0;
        while ((c = fgetc(arquivo)) != EOF) {
            contador++;
        }
    
        printf("Total de caracteres: %ld\n", contador);
        fclose(arquivo);
        return 0;
    }
    
  3. Crie um programa que leia um arquivo "linhas.txt" e imprima cada linha com seu número (começando em 1), usando fgets.
  4. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        FILE *arquivo = fopen("linhas.txt", "r");
        if (arquivo == NULL) {
            printf("Erro ao abrir arquivo!\n");
            return 1;
        }
    
        char linha[256];
        int numero = 1;
        while (fgets(linha, sizeof(linha), arquivo) != NULL) {
            printf("%d: %s", numero, linha);
            numero++;
        }
    
        fclose(arquivo);
        return 0;
    }
    
  5. Dado um arquivo "notas.txt" onde cada linha tem o nome do aluno e a nota separados por ponto e vírgula (ex: "João;8.5"), escreva um programa que calcule a média das notas.
  6. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main() {
        FILE *arquivo = fopen("notas.txt", "r");
        if (arquivo == NULL) {
            printf("Erro ao abrir arquivo!\n");
            return 1;
        }
    
        char linha[256];
        double soma = 0.0;
        int contador = 0;
    
        while (fgets(linha, sizeof(linha), arquivo) != NULL) {
            char *ponto = strchr(linha, ';');
            if (ponto != NULL) {
                double nota = atof(ponto + 1); // converte a string após ';' para double
                soma += nota;
                contador++;
            }
        }
    
        fclose(arquivo);
    
        if (contador > 0) {
            printf("Média das notas: %.2f\n", soma / contador);
        } else {
            printf("Nenhuma nota encontrada.\n");
        }
        return 0;
    }
    
  7. Explique por que é preferível usar fgets em vez de fgetc para ler linhas de texto, citando pelo menos duas razões. Escreva sua resposta em um comentário de código dentro de um programa simples que apenas abre um arquivo.
  8. ✓ Resposta:
    /*
     * Por que usar fgets em vez de fgetc para ler linhas?
     * 1. fgets lê uma linha inteira de uma vez, evitando a necessidade de
     *    montar a string caractere por caractere manualmente.
     * 2. fgets permite especificar um tamanho máximo de buffer, prevenindo
     *    estouro de buffer, enquanto fgetc exigiria controle manual do tamanho.
     */
    #include <stdio.h>
    
    int main() {
        FILE *arquivo = fopen("exemplo.txt", "r");
        if (arquivo == NULL) {
            printf("Erro ao abrir arquivo!\n");
            return 1;
        }
        // O programa não lê nada, apenas demonstra o comentário.
        fclose(arquivo);
        return 0;
    }
    
  9. Escreva um programa que leia um arquivo binário chamado "dados.bin" e conte quantos bytes ele possui, usando fread ou fgetc. Dica: abra o arquivo com modo "rb" e leia até EOF.
  10. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        FILE *arquivo = fopen("dados.bin", "rb");
        if (arquivo == NULL) {
            printf("Erro ao abrir arquivo!\n");
            return 1;
        }
    
        int byte;
        long total = 0;
        while ((byte = fgetc(arquivo)) != EOF) {
            total++;
        }
    
        printf("O arquivo tem %ld bytes.\n", total);
        fclose(arquivo);
        return 0;
    }