Subsecções

24 Arquivos

O armazenamento de dados em variáveis e arrays é temporário. Arquivos são usados para armazenamento permanente de grandes quantidades de dados (e programas) em dispositivos de armazenamento secundário, como discos.

Ás vezes não é suficeinte para um programa usar somente a entrada e saída padrão. Há casos em que um programa deve acessar arquivos. Por exemplo, se nós guardamos uma base de dados com endereços de pessoas em um arquivo, e queremos escrever um programa que permita ao usuário interativamente buscar, imprimir e mudar dados nesta base, este programa deve ser capaz de ler dados do arquivo e também gravar dados no mesmo arquivo.

No restante desta seção discutiremos como arquivos de texto são manipulados em C . Como será visto, tudo ocorre de maneira análoga ao que acontece com entrada e saída padrão.

24.1 Acessando um arquivo: FILE *, fopen(), fclose()

C visualiza cada arquivo simplesmente como um stream seqüencial de bytes. Da mesma forma que uma string em C  termina com o caracter nulo, '\0', cada arquivo em C  termina com um marcador de final de arquivo (end-of-file), EOF.

As regras para acessar um arquivo são simples. Antes que um arquivo seja lido ou gravado, ele é aberto. Um arquivo é aberto em um modo que descreve como o arquivo será usado (por exemplo, para leitura, gravação ou ambos). Um arquivo aberto pode ser processado por funções da biblioteca padrão em C . Estas funções são similares às funções de biblioteca que lêem e escrevem de/para entrada/saída padrão. Quando um arquivo não é mais necessário ele deve ser fechado. Ao final da execução de um programa todos os arquivos abertos são automaticamente fechados. Existe um número máximo de arquivos que podem ser simultaneamente abertos de forma que você deve tentar fechar arquivos quando você não precisa mais deles.

Quando um arquivo está aberto, um stream é associado ao arquivo. Este stream fornece um canal de comunicação entre um arquivo e o programa. Três arquivos e seus respectivos streams são abertos automaticamente quando um programa inicia sua execução: a entrada padrão, a saída padrão e a saída padrão de erros.

A função da biblioteca padrão fopen() é usada para abrir um arquivo. fopen() toma dois argumentos do tipo string: o primeiro argumento é o nome do arquivo (por exemplo data.txt), o segundo argumento é a indicação do modo no qual o arquivo deve ser aberto. fopen() negocia com o sistema operacional e retorna um ponteiro para um tipo estrutura especial FILE. Este ponteiro é chamado file pointer, e aponta para uma estrutura que contém informações de sistema sobre o arquivo. O tipo FILE é predefinido em <stdio.h>. O file pointer é usado pelas funções de biblioteca que processam o arquivo aberto, e "representa" o arquivo do momento em que é aberto até o momento em que é fechado. A estrutura FILE é referida como file control block (FCB). Cada arquivo possui um FCB correspondente no disco. Quando um arquivo é aberto sua FCB é copiada para a memória e um ponteiro é definido para lá. O processamento do arquivo usa o ponteiro para o FCB para manipular arquivos, de forma que o tipo do ponteiro é FILE *. A saída padrão, a entrada padrão e a saída padrão de erros são manipulados usando ponteiros pré-definidos chamados stdout, stdin e stderr respectivamente.

O usuário não necessita saber detalhes de como a transferência de dados entre programa e arquivo é feita. As únicas sentenças necessárias no programa são a definição de uma variável do tipo FILE * (um file pointer) e uma atribuição de um valor para aquela variável por fopen(). As sentenças abaixo dão um exemplo de como abrir o arquivo data.txt para leitura.

FILE *fp;
fp = fopen("data.txt", "r");

O protótipo da função fopen() é:

FILE *fopen(char *name, char *mode);

fopen() recebe dois argumentos: o primeiro é uma string que é um nome de um arquivo a ser aberto, e o segundo é uma string que representa o modo de abertura do arquivo: ``r'' indica que o arquivo será aberto apenas para leitura, ``w'', para escrita apenas. Se o arquivo não existe e é aberto para escrita, fopen() cria o arquivo. Se um arquivo já existente é aberto para escrita, o seu conteúdo é descartado. Há outros modos, incluindo anexação a um arquivo, leitura e escrita simultânea; para mais detalhes, veja a documentação da função nos livros-texto ou no manual on-line.

Se um arquivo é aberto com sucesso, o endereço da estrutura FILE é retornado por fopen(). Se a tentativa de abertura resulta em erro, fopen() retorna um ponteiro nulo, NULL. Alguns dos error possíveis são: abrir um arquivo que não existe para leitura, abrir um arquivo para escrita quando não há mais espaço disponível em disco, ou abrir um arquivo para qualquer operação sendo que as permissões de acesso do arquivo não o permitem.

É recomendável que você teste o valor de retorno de fopen() para verificar se houve erro de abertura. O trecho de programa abaixo ilustra como fazê-lo:

#include <stdio.h>
.... 
FILE *fp;
char fnome[13];
char fmodo[3];

printf("Entre um nome de arquivo para abrir:");
scanf("%s", fnome);
printf("Entre o modo de abertura do arquivo:");
scanf("%s", fmodo);

fp = fopen( fnome, fmodo );
if (fp == NULL) 
   {
   printf("Erro na abertura de %s no modo %s\n", fnome, fmodo);
   return ;
   }
else
   printf("Arquivo %s aberto com sucesso no modo %s\n", fnome, fmodo); 
...

No exemplo acima se o arquivo não puder ser aberto com sucesso, uma mensagem apropriada é exibida na saída padrão e o programa termina. Caso contrário uma mensagem indicando o sucesso na abertura do arquivo é exibida e o programa continua sua execução.

Cada arquivo aberto possui seu próprio file pointer. Por exemplo, se um programa vai manipular dois arquivos diferentes arq1 and arq2 simultaneamente (um para leitura e outro para escrita), dois file pointers devem ser usados:

FILE *fp1, *fp2;

fp1 = fopen("arq1", "r");
fp2 = fopen("arq2", "w");

Os valores de file pointer (FILE *) são chamados streams. Eles estabelecem conexão entre o programa e o arquivo aberto. A partir do momento de abertura, o nome do arquivo é irrelevante para o programa. Todas as funções que operam sobre o arquivo usam o file pointer associado.

Terminada a manipulação do arquivo o programa deve fechar o arquivo. A função padrão fclose() é usada com este propósito. Ela quebra a conexão entre o file pointer e o arquivo. Esta função toma como argumento o file pointer que representa o arquivo a ser fechado. file to be closed. O protótipo de fclose() é:

int fclose(FILE *);
fclose() retorna 0, se há sucesso ou EOF em caso contrário. Abaixo um exemplo de uso de fclose():
fclose(pf1);
fclose(pf2);

24.2 Processando arquivos de texto

Arquivos podem guardar duas categorias básicas de dados: texto (caracteres no universo ASCII) ou binário (como dados armazenados em memória ou dados que representam uma imagem JPEG).

Depois que um arquivo de texto é aberto, existem 3 formas diferentes de ler ou escrever sequencialmente os dados: (i) um caracter por vez, usando as funções da biblioteca padrão fgetc() e fputc(); (ii) uma linha (string) por vez, usando fgets() e fputs(); e (iii) em um formato específico, usando fscanf() e fprintf().

Arquivos binários podem ser lidos como registros de dados estruturados. Além disso, uma vez que todos os registros tem o mesmo tamanho, os dados podem ser acessados de forma não-sequencial (acesso aleatório). As funções usadas para isto são fwrite() e fread().

Outras funções de entrada e saída de mais baixo nível que podem ser usadas são as funções read() e write(). Estas funções não serão usadas no momento e geralmente somente programadores experientes as usam.

24.2.1 Entrada e saída de caracteres

As funções fgetc() e putc() são similares a getchar() e putchar(). Elas operam sobre um arquivo aberto cujo file pointer é passado como argumento.

Os protótipos de fgetc() e fputc() are

int fgetc(FILE *fp);
int fputc(char ch, FILE *fp);
getc() returns the next character read from the file represented by the stream fp, or EOF if error or end of file occurs. putc() writes the character ch in the file represented by the stream fp. It returns the character written or EOF.

Abaixo segue um exemplo de programa que lê um arquivo caracter a caracter e imprime o que foi lido na saída padrão (a tela do computador):

/************************************************************************
 * Lê um caracter por vez de um arquivo e
 * o imprime na saída padrão
 ************************************************************************/
#include <stdio.h> /* para funções padrão de E/S */

main()
{
   FILE *fp;                      
   char fnome[13];
   int ch;

   /* dialogo com usuário */
   printf("Entre um nome de arquivo: ");
   scanf("%s", fnome);

   fp = fopen( fnome, "r" ); /* abre arquivo*/
if (fp == NULL) 
      {
      printf("Erro ao abrir %s\n", fnome);
      return;
      }
   else
      {
      printf("Arquivo aberto com sucesso.\n");

   /* Lê o arquivo caracter a caracter e imprime em stdout (saída padrão) */
   while( (ch=fgetc(fp)) != EOF ) 
          printf("%c", ch);             

      fclose(fp); /* fecha arquivo */
}
}

24.2.2 Entrada e saída de strings

As funções fgets() and fputs() são similares a gets() e puts(). Elas operam sobre um arquivo aberto cujo file pointer é passado como argumento.

Os protótipos de fgets() e fputs() são:

char *fgets(char *str, int n, FILE *fp);
int fputs(char *str, FILE *fp);
A função fgets() lê do arquivo conectado ao stream fp no máximo (n - 1) caracteres para o array str, parando a leitura se o caracter '$\backslash$n' (uma mudança de linha) é encontrado; O caracter '$\backslash$n' é incluído no array e ao elemento do array seguinte é atribuído '\0' (final de string). A função fgets() retorna str ou o ponteiro nulo, NULL, se um erro ou final de arquivo ocorre.

A função fputs() escreve no arquivo conectado ao stream fp a string str, retornando um número não-negativo, ou EOF em caso de erro.

No exemplo a seguir é usada a função fgets() (e não gets(). Você pode dizer por quê?) para salvar em um arquivo um texto digitado através da entrada padrão (stdin). Para sinalizar pelo teclado que você terminou de entrar o texto, deve-se teclar ^D (^Z Turbo C ) e então a tecla ENTER.

/************************************************************************
 * Escreve texto digitado em stdin em um arquivo
 ***********************************************************************/
#include <stdio.h> /* funções padrão de E/S */

main()
{
   FILE *fp;                      
   char fnome[13];
   char linha[81];

   /* dialogo com usuario */
   printf("Entre um nome de arquivo: ");
   scanf("%s", fnome);

   fp = fopen( fnome, "w" ); /* abre arquivo. Conteúdo anterior é perdido.*/
 if (fp == NULL) 
      {
      printf("Erro ao abrir %s\n", fnome);
      return;
      }
   else
      {
      printf("Arquivo aberto com sucesso");

      /* lê linha do teclado, armazena em uma string,
       * salva string em arquivo */

      while( fgets(linha, 80, stdin ) != NULL)  
         fputs(linha, fp);              
      fclose(fp); /* fecha arquivo */
}
}

24.2.3 Entrada e saída formatada: fscanf(), fprintf()

Para entrada e saída formatada as funções padrão fscanf() e fprintf() podem ser usadas. Elas são idênticas às funções scanf() e printf(), exceto que elas têm um argumento adicional (o primeiro em sua lista de argumentos) que é o stream conectado ao arquivo a ser lido ou escrito. Informalmente, seus protótipos podem ser escritos como:
int fscanf( FILE *fp, char *format, arg1, arg2, ... );
int fprintf(FILE *fp, char *format, arg1, arg2, ... );
A função fscanf() lê do arquivo representado pelo stream fp sob controle de um string de formato format. O string de formato geralmente contém conversões (como %d, %s, %f) que dirigem a interpretação da entrada. Os valores convertidos são atribuídos para os argumentos subsequentes, cada qual devendo ser um ponteiro. A função fscanf() retorna quando o string de formato foi totalmente interpretado. O valor retornado por fscanf() é EOF se o final do arquivo foi atingido ou um erro ocorre, caso contrário retorna a quantidade de itens convertidos e atribuídos.

A função fprintf() escreve no arquivo conectado ao stream fp sob controle de um string de formato format. O string de format contém dois tipos de objetos: caracteres ordinários que são copiados do jeito que são, e especificadores de conversão que causam a conversão e impressão dos argumentos seguintes de fprintf(). O valor de retorno é o número de caracteres escritos, ou negativo em caso de ocorrência de erros.

Abaixo segue um exemplo simples de base de dados. Os dados são armazenados permanentemente em um arquivo, agentes.txt. A base de dados contém registros de agentes secretos famosos. Para cada agente um apelido e um número de código são armazenados. Uma vez que será usada e/s formatada, deve-se conhecer o formato no qual os dados estão armazenados no arquivo da base de dados. Este formato é: os dados de diferentes agentes estão em linhas separadas; para cada agente em uma linha, tem-se primeiro o apelido e então o código numérico, separados por espaço.

O programa orientado a menu abaixo lista todos os registros e adiciona novos itens.

/****************************************************************
 * programa com menu para operar uma base de dados de no máximo 50
 * agentes secretos; a base de dados é guardada permanentemente em
 * um arquivo em disco.
 ****************************************************************/

#include <stdio.h>                    /* funções padrão de E/S */
#define FNOME "agentes.txt"             /* nome do arquivo de dados */
#define NUM 50                        /* numero de registros na base de dados */
#define NOMELEN 30                    /* tamanho de um nome */

/*** declara estrutura de dados ***/
struct pessoal               
   {
   char nome [NOMELEN];         /* nome codigo(sem espaços em branco) */
   int agnum ;                  /* numero codigo */
   };

/*** prototipos ***/
int  cargadb(struct pessoal []);
int  novonome(struct pessoal [], int);
void listatudo(struct pessoal [], int);
void salvadb(struct pessoal [], int);
/****** MAIN *********/
main()
{
   struct pessoal agentes[50];     /* array de 50 estruturas      */
   int n;                          /* indice para o ultimo registro ativo */
   char ch;
   
   /*** carrega a base de dados em agentes[], n é o tamanho da base de dados     */
   n = loaddb(agentes);              

   /* seleciona uma opção do menu e processa os dados em memória  */
   do {
      printf("\nDigite 'e' para entrar novo agente,"); 
      printf("\n     'l' para listar todos os agentes,");    
      printf("\n     'q' para terminar: ");
      ch = getchar();                            
      switch (ch)
         {
         case 'e':
            n = novonome(agentes, n);   /* adiciona um novo agente no indice n  */
            break;
         case 'l':                      /* lista todos os registros */
            listatudo(agentes, n);
            break;
         case 'q':                      /* salva todos os registros */
            salvadb(agentes, n); 
            break;
         default:                       /* Engano do usuario  */
            printf("\nEntre somente as opções listadas.\n");
         }  
         while (fgetc(stdin) != '\n') ;
      } while (ch != 'q');
}
Uma amostra de uma execução do programa segue abaixo. Inicialmente o conteúdo do arquivo agentes.txt é:
Klara 89
Edward 888
ZipZap 109
Uma amostra de execução:
Digite 'e' para entrar novo agente,
       'l' para listar todos os agentes,
       'q' para terminar: l
Klara 89
Edward 888
ZipZap 109

Digite 'e' para entrar novo agente,
       'l' para listar todos os agentes,
       'q' para terminar: e
Digite nome e código: TipTop 999

Digite 'e' para entrar novo agente,
       'l' para listar todos os agentes,
       'q' para terminar: l
Klara 89
Edward 888
ZipZap 109
TipTop 999

Digite 'e' para entrar novo agente,
       'l' para listar todos os agentes,
       'q' para terminar: q
Salvar? ('s' para salvar)
y
Salvando...Feito
A seguir, apresenta-se a implementação das quatro funções cargadb(), novonome(), listatudo(), and salvadb().
/***********************************************************************
 * lê a base de dados do arquivo (até EOF) no array em memória
 * ENTRADA: um array de do tipo struct pessoal
 * RETORNO: número de elementos lidos
 * SUPOSIÇÕES: o tamanho da base de dados deve ter no máximo 50 registros
 ***********************************************************************/
int cargadb(struct pessoal pessoa[])
{
   int i = 0;
   FILE *fp;                     /* define ptr to FILE          */

   fp = fopen(FNOME, "r");    

   while ( fscanf(fp, "%s %d", pessoa[i].nome, &pessoa[i].agnum) != EOF )
      i++;
   fclose(fp);
   return i;
}
/*************************************************************************
 * adiciona novo elemento ao indice n no array pessoa[],
 * o valo da estrutura é obtido da entrada padrão
 * ENTRADA: array pessoa[] -- to store the structure value
 *                       n -- indice do elemento, incrementado a 
 *                            cada novo elemento
 *************************************************************************/
int novonome(struct pessoal pessoa[], int n)
   {
      if (n < NUM)
      {
         printf("Digite nome e código: ");
         scanf("%s %d", pessoa[n].nome, &pessoa[n].agnum);
         n++;
      }
      else
         printf("Não há mais espaço\n");
      return n;
   }

/*************************************************************************
 * imprime a base de dados na tela
 * ENTRADA: array pessoa[] a imprimir
 *                       n número de registros para imprimir
 *************************************************************************/
void listatudo(struct pessoal pessoa[], int n)
   {
   int j;

   for (j = 0; j < n; j++)
       {
       printf("%s %d\n", pessoa[j].nome, pessoa[j].agnum);
       }
   }

/**************************************************************************
 * Pergunta ao usuário se quer salvar a base de dados. Se a resposta é SIM (s)
 * abre o arquivo para escrita e grava o array no arquivo
 * ENTRADA: array pessoa[] a ser salvo
 *                       n número de registros a ser salvo
 **************************************************************************/
void salvadb(struct pessoal pessoa[], int n)
{
  int i;
  FILE *fp;

  while (fgetc(stdin) != '\n') ;
  printf("Salvar? ('s' para salvar)\n");
  if ( getchar() == 's')
     {
     fp = fopen(FNOME, "w");    
     printf("Salvando...");
     for (i = 0; i < n; i++) 
       {
       fprintf(fp, "%s %d\n", pessoa[i].nome, pessoa[i].agnum);
       }
     fclose(fp);
     printf("Feito.\n");
     }
  else
     printf("Alterações não foram salvas.\n");
}

Armando Luiz Nicolini Delgado
2013-10-21