Manipulação de arquivos

Até aqui, temos trabalhado apenas com a memória principal do computador. Ou seja: os dados que geramos durante o uso do programa não são persistidos na memória.

Uma abordagem simples (mas eficiente) para a persistência de dados consiste na manipulação de arquivos.

📄 O conceito de arquivo

Estamos acostumados, desde sempre, a trabalhar com diferentes arquivos no computador. Usualmente, diferenciamos esses arquivos por meio de sua extensão: um arquivo .xslx é gerado pelo Excel, enquanto um arquivo .jpg é usado para armazenar uma imagem, podendo ser lido por diversos programas.

Apesar dessa diferença, um arquivo é essencialmente um conjunto de bytes. A grande questão está em como esse conjunto de bytes será interpretado.

Como você já pode imaginar, o significado de um arquivo está relacionado com a estrutura de dados utilizada em sua concepção.

🛠️ Arquivos em C

Na linguagem de programação C, podemos manipular arquivos de variegadas formas. A linguagem já nos fornece funções prontas para executar tal operação.

Para facilitar a compreensão, vamos investigar a manipulação de arquivos usando duas estratégias diferentes. Cada uma delas será apresentada a seguir.

🅱 Arquivos de texto

Uma das estratégias que podemos usar na linguagem C é manipularmos os arquivos como textos. A principal vantagem dessa abordagem é que os dados são armazenados de forma compreensível por seres humanos, como devem ser exibidos na tela.

Para que isso aconteça, os dados são gravados como caracteres da tabela ASCII, ocupando 1 byte cada (8 bits). Ou seja: os dados são convertidos antes de serem armazenados no arquivo.

Essa abordagem implica em:

Além disso, é necessário armazenar a quebra de linha. Dependendo do sistema em que o programa está executando, um caracter de nova linha (\n) pode ser convertido para \r\n.

👾 Arquivos binários

Diferentemente de um arquivo de texto, um arquivo binário representa os dados seguindo fielmente a estrutura adotada pelo programa que o gerou. Embora a leiturabilidade dos dados por um ser humano diretamente do arquivo fique comprometida, essa estratégia utiliza menos espaço para armazenamento, assim como não implica numa conversão de representação.

Estrutura básica para manipulação de arquivos

Ponteiro para arquivo

Para trabalharmos com arquivos em C, precisamos fazer o uso de pelo menos um ponteiro, chamado de ponteiro para arquivo. Quando definimos um arquivo e o abrimos na linguagem de programação C, esse ponteiro indicará a posição do registro em que deve ser feita uma operação de leitura ou escrita.

A definição desse ponteiro é feita da seguinte forma:

FILE *arquivo;

🔓 Abrindo um arquivo: fopen

Após a definição de um ponteiro que representa o arquivo, precisamos abri-lo. Para tanto, usamos a função fopen, provida pela biblioteca stdlib.h. Confira a assinatura da função:

FILE *fopen(char *arquivo,char *modo);
  • *arquivo consiste na string que contém o caminho do arquivo a ser manipulado;
  • *modo consiste na string que representa o modo de abertura do arquivo.

Não apenas em C, mas em diversas linguagens de programação, um arquivo pode ser aberto para diferentes finalidades. A tabela a seguir sumariza os modos de abertura de um arquivo:

ModoDescrição
rAbre um arquivo para leitura. Se o arquivo não existir, ocorre um erro.
wCria um arquivo para escrita. Se o arquivo já existir, seu conteúdo é apagado. Se não existir, um novo arquivo é criado.
aAbre um arquivo para anexação (append). Os dados são adicionados ao final do arquivo. Se o arquivo não existir, um novo arquivo é criado.
r+Abre um arquivo para leitura e escrita. Se o arquivo não existir, ocorre um erro.
w+Cria um arquivo para leitura e escrita. Se o arquivo já existir, seu conteúdo é apagado. Se não existir, um novo arquivo é criado.
a+Abre um arquivo para leitura e anexação. Os dados são adicionados ao final do arquivo. Se o arquivo não existir, um novo arquivo é criado.
rbAbre um arquivo em modo binário para leitura.
wbCria um arquivo em modo binário para escrita.
abAbre um arquivo em modo binário para anexação.
rb+Abre um arquivo existente em modo binário para leitura e escrita. Não apaga o conteúdo do arquivo. Falha se o arquivo não existir.
wb+Cria um novo arquivo em modo binário para leitura e escrita. Apaga o conteúdo se o arquivo já existir; cria um novo arquivo se não existir.
ab+Abre um arquivo em modo binário para leitura e anexação. Cria um novo arquivo se não existir.

Se a função fopen for executada com sucesso, ela retornará um ponteiro para o arquivo. Caso contrário, retonará NULL.

🔒 Fechando um arquivo: fclose

Após efetuarmos as manipulações (leitura ou escrita) desejadas, precisamos fechar o arquivo. Para isso, usamos a função fclose, definida conforme a sintaxe a seguir:

int fclose(FILE *arquivo);

Se for executada com sucesso, a função retornará o valor 0. Caso contrário, retornará um valor diferente de 0.

Agora, veja como seria uma estrutura básica:

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

int main() {
    FILE *arquivo = fopen("arquivo.txt", "w");

    if(!file) {
      printf("Erro ao abrir o arquivo.\n");
    }

    // Operações de leitura e escrita virão aqui

    // Fechamento do arquivo
    fclose(file);

    return 0;
}

🤯 Aspectos importantes

Quando efetuamos operações de leitura e escrita num arquivo em C, o buffer é uma área de armazenamento temporário na memória usada para otimizar o acesso aos dados do arquivo. Quando um arquivo é aberto, o sistema operacional aloca um buffer que armazena temporariamente os dados lidos ou escritos.

  • Para Leitura: Quando você lê de um arquivo, os dados são primeiro carregados no buffer. Isso permite que o programa acesse os dados mais rapidamente do que se estivesse lendo diretamente do arquivo a cada vez. O buffer é preenchido com uma quantidade substancial de dados do arquivo, reduzindo o número de operações de leitura no disco.
  • Para Escrita: Da mesma forma, ao escrever em um arquivo, os dados são primeiro escritos no buffer. Eles só são gravados fisicamente no arquivo quando o buffer está cheio ou quando a função fclose ou fflush é chamada. Isso minimiza o número de operações de gravação no disco, que são mais lentas do que as operações na memória.

O uso de buffer em C melhora a eficiência e o desempenho da manipulação de arquivos, reduzindo a quantidade de operações de leitura e escrita diretamente no disco.

Ao fecharmos um arquivo, os dados que estavam no buffer são persistidos.

📖 Bibliografia

Livros

  • Backes, A. (2018). Linguagem C - Completa e Descomplicada.