cblc/aulas/16-rwfiles/README.org
2025-05-07 08:54:19 -03:00

8 KiB

Curso Básico da Linguagem C

Aula 16: Abertura de arquivos para leitura e escrita

Vídeo desta aula

Modos de abertura para leitura e escrita

As strings de modos "r", "w" e "a", da função fopen, quando sucedidas do caractere +, indicam a abertura do arquivo também para suas respectivas operações contrarias:

Modo Leitura Escrita Truncar Offset Criar
"r" Sim Não Não Início Não
"r+" Sim Sim Não Início Não
"w" Não Sim Sim Início Sim
"w+" Sim Sim Sim Início Sim
"a" Não Sim Não Fim Sim
"a+" Sim Sim Não Fim Sim

De acordo com a tabela acima, nós precisamos observar alguns pontos importantes:

  • Se a string de modo iniciar com w, o arquivo sempre será truncado na sua abertura.
  • Se a string de modo iniciar com a, o ponteiro interno sempre será posicionado no fim do arquivo na sua abertura.
  • Se a string de modo iniciar com r, o ponteiro interno sempre será posicionado no início do arquivo na sua abertura.
  • Se não existir, o arquivo só será criado se a string de modo iniciar com w ou a.

Manipulação do ponteiro interno

O ponteiro interno do arquivo representa o número do byte que será lido ou escrito no próximo acesso: ou seja, seu offset (ou ponto de partida). Isso é particularmente importante quando abrimos arquivos para ambas as operações com as strings de modo "r+" ou "a+", visto que o modo "w+" truncará o conteúdo do arquivo.

A função fseek

As funções da família fseek (stdio.h) são utilizadas para posicionar ou obter informações sobre o ponteiro interno do arquivo:

Função Descrição Retorno
fseek Define a posição do ponteiro interno. Sucesso: 0 (int)
ftell Obtém a posição corrente do ponteiro interno. Sucesso: número do byte (long)
rewind Posiciona o ponteiro interno no byte 0. void
fsetpos Interface para fseek referenciando a definição do offset a partir do início do arquivo. Sucesso: 0 (int)
fgetpos Interface para ftell escrevendo o offset obtido em um dado endereço. Sucesso: 0 (int)

Notas:

  • Com a função fseek, o novo offset é definido com uma expressão do tipo long.
  • Com a função fsetpos, o novo offset é definido em um ponteiro do tipo fpos_t.
  • Com a função fgetpos, o offset corrente é escrito em um ponteiro do tipo fpos_t.

A função fseek trabalha com streams, mas é possível manipular o ponteiro interno a partir de descritores de arquivos com a chamada de sistema lseek.

Pontos de origem de fseek

A função fseek recebe 3 argumentos:

  • O stream associado ao arquivo;
  • A expressão do valor do offset (tipo long);
  • Um inteiro determinando o ponto de origem para a definição do novo offset.

Este é o seu protótipo na glibc (stdio.h):

int fseek(FILE *stream, long offset, int whence);

O termo whence significa "a partir de onde" e representa o ponto de origem do deslocamento do ponteiro interno do arquivo.

O terceiro argumento (whence) pode receber qualquer valor inteiro, mas a glibc oferece três macros para os pontos de origem mais comuns:

  • SEEK_SET: o início do arquivo;
  • SEEK_END: o fim do arquivo;
  • SEEK_CUR: o offset corrente.

Abertura de arquivos com "r+"

Como o offset inicial será o byte 0, se a primeira operação for uma escrita, os primeiros bytes do arquivo serão sobrescritos. Caso esta não seja a intenção, será preciso deslocar o ponteiro interno para o fim do arquivo:

// Abertura do arquivo para leitura e escrita...
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r+");

// Posicionar offset no fim do arquivo...
fseek(stream, 0, SEEK_END);

// Rotinas de escrita...

Aqui, nós deslocamos o ponteiro interno em zero bytes a partir do fim do arquivo (origem em SEEK_END).

Abertura de arquivos com "a+"

O offset inicial será o fim do arquivo e, se quisermos começar a leitura a partir de seu início, será preciso deslocar o ponteiro interno para lá:

// Abertura do arquivo para leitura e escrita...
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r+");

// Posicionar offset no início do arquivo...
fseek(stream, 0, SEEK_SET);

// Rotinas de leitura...

Desta vez, nós deslocamos o ponteiro interno em zero bytes a partir do início do arquivo (origem em SEEK_SET).

Procedimentos úteis

Para encerrar esta aula (e o nosso curso), aqui estão alguns exemplos de procedimentos gerais úteis envolvendo a manipulação de arquivos.

Quantidade de linhas

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

int main(void) {
    // Abertura do arquivo para leitura...
    char *file = "meuarquivo.txt";
    FILE *stream = fopen(file, "r");
    if (!stream) {
        fprintf(stderr, "Erro ao abrir o arquivo!\n");
        return EXIT_FAILURE;
    }

    // Contagem de linhas...
    char buf[BUFSIZ]; // Buffer para ler blocos do arquivo (8192 bytes).
    int lines = 0;    // Total de linhas encontradas.

    // Lê uma linha até encontrar \n, até BUFSIZ-1 ou até EOF...
    while (fgets(buf, BUFSIZ, stream) != NULL) {
        // Só conta uma linha se houver \n no buffer...
        if (buf[strlen(buf) - 1] == '\n' ) lines++;
    }
    // Se a última linha não contiver \n, ela também será contada...
    if (lines > 0 && buf[strlen(buf) - 1] != '\n') lines++;
    
    // Fechamento do arquivo...
    fclose(stream);

    // Impressão do resultado...
    printf("%d linhas", lines);

    return EXIT_SUCCESS;
}

Obter o tamanho do arquivo

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // Abertura do arquivo...
    char *file = "meuarquivo.txt";
    FILE *stream = fopen(file, "rb");
    if (!stream) {
        perror("Erro ao abrir o arquivo");
        return EXIT_FAILURE;
    }

    // Posiciona o ponteiro de leitura no final do arquivo...
    if (fseek(stream, 0, SEEK_END) != 0) {
        perror("Erro ao deslocar o offset para o final");
        fclose(stream);
        return EXIT_FAILURE;
    }

    // Obtém o offset corrente (tamanho do arquivo em bytes)...
    long fsize = ftell(stream);
    if (fsize == -1L) {
        perror("Erro ao obter o offset corrente");
        fclose(stream);
        return EXIT_FAILURE;
    }

    // Exibe o tamanho do arquivo...
    printf("Tamanho do arquivo: %ld bytes\n", fsize);

    // Fechamento do arquivo...
    fclose(stream);
    return EXIT_SUCCESS;
}

Testar se um arquivo está vazio (0 bytes)

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // Abertura do arquivo...
    char *file = "meuarquivo.txt";
    FILE *stream = fopen(file, "r");
    if (!stream) {
        perror("Erro na abertura do arquivo");
        return EXIT_FAILURE;
    }

    // is_empty recebe 1 se o arquivo estiver vazio...
    int is_empty = (fseek(stream, 0, SEEK_END) == 0 && ftell(stream) == 0);

    // Fechamento do arquivo...
    fclose(stream);

    // O programa terminará com sucesso (0) se o arquivo estiver vazio...
    return !is_empty;
}