Programar em C/Entrada e saída em arquivos

Trabalhando com arquivos

editar

Já vimos como podemos receber e enviar dados para usuário através do teclado e da tela; agora veremos também como ler e gravar dados em arquivos, o que é aliás muito importante ou até essencial em muitas aplicações.

Assim como as funções de entrada/saída padrão (teclado e tela), as funções de entrada/saída em arquivos estão declaradas no cabeçalho stdio.h que significa "STanDard Input-Output". Aliás, as funções para manipulação de arquivos são muito semelhantes às usadas para entrada/saída padrão. Como já dissemos na seção sobre a entrada e saída padrões, a manipulação de arquivos também se dá por meio de fluxos (streams).

Na manipulação de um arquivo, há basicamente três etapas que precisam ser realizadas:

  1. abrir o arquivo;
  2. ler e/ou gravar os dados desejados;
  3. fechar o arquivo.

Em C, todas as operações realizadas com arquivos envolvem seu identificador de fluxo, que é uma variável do tipo FILE * (sobre o qual não cabe agora falar). Para declarar um identificador de fluxo, faça como se fosse uma variável normal:

FILE *fp;        // não se esqueça do asterisco!

Abrindo e fechando um arquivo

editar

Não surpreendentemente, a primeira coisa que se deve fazer para manipular um arquivo é abri-lo. Para isso, usamos a função fopen(). Sua sintaxe é:

FILE *fopen (char *nome_do_arquivo, char *modo_de_acesso);
  • O nome do arquivo deve ser uma string ou com o caminho completo (por exemplo, /usr/share/appname/app.conf ou C:\Documentos\nomes.txt) ou o caminho em relação ao diretório atual (nomes.txt, ../app.conf) do arquivo que se deseja abrir ou criar.
  • O modo de acesso é uma string que contém uma seqüência de caracteres que dizem se o arquivo será aberto para gravação ou leitura. Depois de aberto o arquivo, você só poderá executar os tipos de ação previstos pelo modo de acesso: não poderá ler de um arquivo que foi aberto somente para escrita, por exemplo. Os modos de acesso estão descritos na tabela a seguir.
Modo Significado
r Abre o arquivo somente para leitura. O arquivo deve existir. (O r vem do inglês read, ler)
r+ Abre o arquivo para leitura e escrita. O arquivo deve existir.
w Abre o arquivo somente para escrita no início do arquivo. Apagará o conteúdo do arquivo se ele já existir, criará um arquivo novo se não existir. (O w vem do inglês write, escrever)
w+ Abre o arquivo para escrita e leitura, apagando o conteúdo pré-existente.
a Abre o arquivo para escrita no final do arquivo. Não apaga o conteúdo pré-existente. (O a vem do inglês append, adicionar, apender)
a+ Abre o arquivo para escrita no final do arquivo e leitura.

Em ambientes DOS/Windows, ao ler arquivos binários (por exemplo, programas executáveis ou certos tipos de arquivos de dados), deve-se adicionar o caractere "b" ao final da string de modo (por exemplo, "wb" ou "r+b") para que o arquivo seja lido/gravado corretamente.

Isso é necessário porque no modo texto (o padrão quando não é adicionado o b) ocorrem algumas traduções de caracteres (por exemplo, a terminação de linha "\r\n" é substituída apenas por "\n" na leitura) que poderiam afetar a leitura/gravação dos arquivos binários (indevidamente inserindo ou suprimindo caracteres).

  • O valor de retorno da função fopen() é muito importante! Ele é o identificador do fluxo que você abriu e é só com ele que você conseguirá ler e escrever no arquivo aberto.
  • Se houver um erro na abertura/criação do arquivo, a função retornará o valor NULL. O erro geralmente acontece por duas razões:
    • O arquivo não existe, caso tenha sido requisitado para leitura.
    • O usuário atual não tem permissão para abrir o arquivo com o modo de acesso pedido. Por exemplo, o arquivo é somente-leitura, ou está bloqueado para gravação por outro programa, ou pertence a outro usuário e não tem permissão para ser lido por outros.

Ao terminar de usar um arquivo, você deve fechá-lo. Isso é feito pela função fclose():

int fclose (FILE *fluxo);
  • O único argumento é o identificador do fluxo (retornado por fopen). O valor de retorno indica o sucesso da operação com o valor zero.
  • Fechar um arquivo faz com que qualquer caractere que tenha permanecido no "buffer" associado ao fluxo de saída seja gravado. Mas, o que é este "buffer"? Quando você envia caracteres para serem gravados em um arquivo, estes caracteres são armazenados temporariamente em uma área de memória (o "buffer") em vez de serem escritos em disco imediatamente. Quando o "buffer" estiver cheio, seu conteúdo é escrito no disco de uma vez. A razão para se fazer isto tem a ver com a eficiência nas leituras e gravações de arquivos. Se, para cada caractere que fôssemos gravar, tivéssemos que posicionar a cabeça de gravação em um ponto específico do disco, apenas para gravar aquele caractere, as gravações seriam muito lentas. Assim estas gravações só serão efetuadas quando houver um volume razoável de informações a serem gravadas ou quando o arquivo for fechado.
  • A função exit() fecha todos os arquivos que um programa tiver aberto.
  • A função fflush() força a gravação de todos os caracteres que estão no buffer para o arquivo.

Exemplo

editar

Um pequeno exemplo apenas para ilustrar a abertura e fechamento de arquivos:

 #include <stdio.h>
 
 int main()
 {
    FILE *fp;
    fp = fopen ("README", "w");
    if (fp == NULL) {
       printf ("Houve um erro ao abrir o arquivo.\n");
       return 1;
    }
    printf ("Arquivo README criado com sucesso.\n");
    fclose (fp);
    return 0;
 }

Arquivos pré-definidos

editar

Na biblioteca padrão do C, existem alguns fluxos pré-definidos que não precisam (nem devem) ser abertos nem fechados:

  • stdin: dispositivo de entrada padrão (geralmente o teclado)
  • stdout: dispositivo de saída padrão (geralmente o vídeo)
  • stderr: dispositivo de saída de erro padrão (geralmente o vídeo)
  • stdaux: dispositivo de saída auxiliar (em muitos sistemas, associado à porta serial)
  • stdprn: dispositivo de impressão padrão (em muitos sistemas, associado à porta paralela)

Escrevendo em arquivos

editar

Para escrever em arquivos, há quatro funções, das quais três são análogas às usadas para saída padrão:

Saída padrão Arquivos Explicação
putchar fputc Imprime apenas um caractere.
puts fputs Imprime uma string diretamente, sem nenhuma formatação.
printf fprintf Imprime uma string formatada.
N/A fwrite Grava dados binários para um arquivo.

A seguir apresentamos os protótipos dessas funções:

void fputc (int caractere, FILE *fluxo);
void fputs (char *string, FILE *fluxo);
void fprintf (FILE *fluxo, char *formatação, ...);
int fwrite (void *dados, int tamanho_do_elemento, int num_elementos, FILE *fluxo);
  • Sintaxe quase igual à de printf(); só é necessário adicionar o identificador de fluxo no início.

fwrite

editar
  • Esta função envolve os conceitos de ponteiro e vetor, que só serão abordados mais tarde.

A função fwrite() funciona como a sua companheira fread(), porém escreve no arquivo. Seu protótipo é:

unsigned fwrite(void *buffer,int numero_de_bytes,int count,FILE *fp);

A função retorna o número de itens escritos. Este valor será igual a count a menos que ocorra algum erro. O exemplo abaixo ilustra o uso de fwrite e fread para gravar e posteriormente ler uma variável float em um arquivo binário.

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

int main()
{
      FILE *pf;
      float pi = 3.1415;
      float pilido;
      if((pf = fopen("arquivo.bin", "wb")) == NULL) /* Abre arquivo binário para escrita */
            {
                  printf("Erro na abertura do arquivo");
                  exit(1);    
            }
      if(fwrite(&pi, sizeof(float), 1,pf) != 1)     /* Escreve a variável pi */
               printf("Erro na escrita do arquivo");
      fclose(pf);                                    /* Fecha o arquivo */
      if((pf = fopen("arquivo.bin", "rb")) == NULL) /* Abre o arquivo novamente para leitura */
            {
               printf("Erro na abertura do arquivo");
               exit(1);
            }
      if(fread(&pilido, sizeof(float), 1,pf) != 1)  /* Le em pilido o valor da variável armazenada anteriormente */
            printf("Erro na leitura do arquivo");
      printf("\nO valor de PI, lido do arquivo e': %f", pilido);
      fclose(pf);
      return 0;
}

Nota-se o uso do operador sizeof, que retorna o tamanho em bytes da variável ou do tipo de dados.

A função fputc é a primeira função de escrita de arquivo que veremos. Seu protótipo é:

int fputc (int ch, FILE *fp);

Escreve um caractere no arquivo.O programa a seguir lê uma string do teclado e escreve-a, caractere por caractere em um arquivo em disco (o arquivo arquivo.txt, que será aberto no diretório corrente).

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

int main()
{
   FILE *fp;
   char string[100];
   int i;
   fp = fopen("arquivo.txt","w");   		/* Arquivo ASCII, para escrita */
   if(!fp)
    {
      printf( "Erro na abertura do arquivo");
      exit(0);
    }
   printf("Entre com a string a ser gravada no arquivo:");
   gets(string);
   for(i=0; string[i]; i++) fputc(string[i], fp); /* Grava a string, caractere a caractere */
   fclose(fp);
   return 0;
}

Depois de executar este programa, verifique o conteúdo do arquivo arquivo.txt (você pode usar qualquer editor de textos). Você verá que a string que você digitou está armazenada nele.

Leitura de arquivos

editar

Novamente, há quatro funções, das quais três se assemelham às usadas para a saída padrão:

Saída padrão Arquivos Explicação
getchar fgetc Recebe apenas um caractere.
gets fgets Lê uma string (geralmente uma linha inteira).
scanf fscanf Recebe uma string formatada.
N/A fread Lê dados binários de um arquivo.
int fgetc (FILE *fluxo);
void fgets (char *string, int tamanho, FILE *fluxo);
void fscanf (FILE *fluxo, char *formatação, ...);
int fread (void *dados, int tamanho_do_elemento, int num_elementos, FILE *fluxo);


  Este módulo tem a seguinte tarefa pendente: criar exemplos de uso das funções
  • Está função requer como parâmetro o indicador de fluxo do arquivo, retorna um caractere do arquivo ou EOF, caso ocorra um erro ou o final do arquivo seja atingido, podendo ser verificado respectivamente por ferror e feof.

Exemplo:

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

int main()
{
    FILE *fl;
    int c;

    if((fl = fopen("caminho/do/arquivo", "r")) == NULL)
    {
        perror("Erro: fopen");
        exit(EXIT_FAILURE);
    }

    while((c = fgetc(fl)) != EOF)
        printf("Caractere lido: %c\n", c);

    if((c == EOF) && (feof(fl) == 0) && (ferror(fl) != 0))
        perror("Erro: fgetc");
        

    fclose(fl);
    return EXIT_SUCCESS;
}
  • Ao chamar a função fgets(), você deve fornecer o ponteiro para a string onde os dados lidos devem ser guardados, além do tamanho máximo dos dados a serem lidos (para que a memória reservada à string não seja ultrapassada).

Para se ler uma string num arquivo podemos usar fgets() cujo protótipo é:

char *fgets (char *str, int tamanho,FILE *fp);

A função recebe 3 argumentos: a string a ser lida, o limite máximo de caracteres a serem lidos e o ponteiro para FILE, que está associado ao arquivo de onde a string será lida. A função lê a string até que um caracter de nova linha seja lido ou tamanho-1 caracteres tenham sido lidos. Se o caracter de nova linha ('\n') for lido, ele fará parte da string, o que não acontecia com gets.

A função fgets é semelhante à função gets(), porém, além dela poder fazer a leitura a partir de um arquivo de dados e incluir o caracter de nova linha na string, ela ainda especifica o tamanho máximo da string de entrada. Como vimos, a função gets não tinha este controle, o que poderia acarretar erros de "estouro de buffer". Portanto, levando em conta que o ponteiro fp pode ser substituído por stdin, como vimos acima, uma alternativa ao uso de gets é usar a seguinte construção:

fgets (str, tamanho, stdin);

fscanf

editar
  • Sintaxe quase igual à de scanf(); só é necessário adicionar o identificador de fluxo no início.

fscanf

editar

A função fscanf() funciona como a função scanf(). A diferença é que fscanf() lê de um arquivo e não do teclado do computador. Protótipo:

int fscanf (FILE *fp,char *str,...);
#include <stdio.h>
#include <stdlib.h>
int main()
{
   FILE *p;
   char str[80],c;
   printf("\n\n Entre com um nome para o arquivo:\n");       /* Le um nome para o arquivo a ser aberto: */
   gets(str);
   if (!(p = fopen(str,"w")))  		                     /* Caso ocorra algum erro na abertura do arquivo..*/ 
     {                           		             /* o programa aborta automaticamente */
        printf("Erro! Impossivel abrir o arquivo!\n");
        exit(1);
     }
   fprintf(p,"Este e um arquivo chamado:\n%s\n", str);
   fclose(p);                                                /* Se nao houve erro, imprime no arquivo, fecha ...*/
   p = fopen(str,"r");                                 /* abre novamente para a leitura  */
   while (!feof(p))
    {
       fscanf(p,"%c",&c);
       printf("%c",c);
    } 
   fclose(p);
   return 0;
}
  • Essa função envolve os conceitos de ponteiro e vetor, que só serão abordados mais tarde.

Podemos escrever e ler blocos de dados. Para tanto, temos as funções fread() e fwrite(). O protótipo de fread() é:

 unsigned fread (void *buffer, int numero_de_bytes, int count, FILE *fp);

O buffer é a região de memória na qual serão armazenados os dados lidos. O número de bytes é o tamanho da unidade a ser lida. count indica quantas unidades devem ser lidas. Isto significa que o número total de bytes lidos é:

numero_de_bytes*count

A função retorna o número de unidades efetivamente lidas. Este número pode ser menor que count quando o fim do arquivo for encontrado ou ocorrer algum erro.

Quando o arquivo for aberto para dados binários, fread pode ler qualquer tipo de dados.

Movendo pelo arquivo

editar

Para se fazer procuras e acessos randômicos em arquivos usa-se a função fseek(). Esta move a posição corrente de leitura ou escrita no arquivo de um valor especificado, a partir de um ponto especificado. Seu protótipo é:

int fseek (FILE *fp, long numbytes, int origem);

O parâmetro origem determina a partir de onde os numbytes de movimentação serão contados. Os valores possíveis são definidos por macros em stdio.h e são:

Nome	        Valor	Significado
SEEK_SET	0	Início do arquivo
SEEK_CUR	1	Ponto corrente no arquivo
SEEK_END	2	Fim do arquivo

Tendo-se definido a partir de onde irá se contar, numbytes determina quantos bytes de deslocamento serão dados na posição atual.

rewind

editar

Volta para o começo do arquivo de um fluxo

EOF ("End of file") indica o fim de um arquivo. Às vezes, é necessário verificar se um arquivo chegou ao fim. Para isto podemos usar a função feof(). Ela retorna não-zero se o arquivo chegou ao EOF, caso contrário retorna zero. Seu protótipo é:

int feof (FILE *fp);

Outra forma de se verificar se o final do arquivo foi atingido é comparar o caractere lido por getc com EOF. O programa a seguir abre um arquivo já existente e o lê, caracter por caracter, até que o final do arquivo seja atingido. Os caracteres lidos são apresentados na tela:

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

int main()
{
   FILE *fp;
   char c;
   fp = fopen("arquivo.txt","r");   /* Arquivo ASCII, para leitura */
   if(!fp)
    {
     printf( "Erro na abertura do arquivo");
     exit(0);
    }
   while((c = getc(fp) ) != EOF)      /* Enquanto não chegar ao final do arquivo */
     printf("%c", c);                 /* imprime o caracter lido */
   fclose(fp);
   return 0;
  }

Verifique o exemplo.

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

int main()
{
   FILE *p;
   char c, str[30], frase[80] = "Este e um arquivo chamado: ";
   int i;
   printf("\n\n Entre com um nome para o arquivo:\n");
   gets(str); 					/* Le um nome para o arquivo a ser aberto: */
   if (!(p = fopen(str,"w")))  	/* Caso ocorra algum erro na abertura do arquivo..*/
    {                           
      printf("Erro! Impossivel abrir o arquivo!\n");
      exit(1); 			/* o programa aborta automaticamente */
    }
   strcat(frase, str);
   for (i=0; frase[i]; i++)
     putc(frase[i],p);
   fclose(p); 				/* Se nao houve erro,imprime no arquivo e o fecha ...*/
   p = fopen(str,"r");			/* Abre novamente para  leitura  */
   c = getc(p);				/* Le o primeiro caracter */
   while (!feof(p))        		/* Enquanto não se chegar no final do arquivo */
    {    
     printf("%c",c); 		/*   Imprime o caracter na tela */
     c = getc(p);    		/* Le um novo caracter no arquivo */
    }
   fclose(p);              		/* Fecha o arquivo */
}

Outras funções

editar
Função Explicação
remove Remove um arquivo especificado

ferror e perror

editar

Protótipo de ferror:

int ferror (FILE *fp);

A função retorna zero, se nenhum erro ocorreu e um número diferente de zero se algum erro ocorreu durante o acesso ao arquivo. se torna muito útil quando queremos verificar se cada acesso a um arquivo teve sucesso, de modo que consigamos garantir a integridade dos nossos dados. Na maioria dos casos, se um arquivo pode ser aberto, ele pode ser lido ou gravado.

Porém, existem situações em que isto não ocorre. Por exemplo, pode acabar o espaço em disco enquanto gravamos, ou o disco pode estar com problemas e não conseguimos ler, etc. Uma função que pode ser usada em conjunto com ferror() é a função perror() (print error), cujo argumento é uma string que normalmente indica em que parte do programa o problema ocorreu.

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

int main()
{
   FILE *pf;
   char string[100];
   if((pf = fopen("arquivo.txt","w")) ==NULL) 
    {
      printf("\nNao consigo abrir o arquivo ! ");
      exit(1);
    }
   do 
    {
      printf("\nDigite uma nova string. Para terminar, digite <enter>: ");
      gets(string);
      fputs(string, pf);
      putc('\n', pf);
      if(ferror(pf))
       {
         perror("Erro na gravacao");
         fclose(pf);
         exit(1);
       }
    }while (strlen(string) > 0);
    fclose(pf);
}