Programar em C/Entrada e saída em arquivos
Trabalhando com arquivos
editarJá 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:
- abrir o arquivo;
- ler e/ou gravar os dados desejados;
- 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
editarNão surpreendentemente, a primeira coisa que se deve fazer para manipular um arquivo é abri-lo. Para isso, usamos a função fopen(). Sua sintaxe é:
- 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():
- 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
editarUm 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
editarNa 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
editarPara 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:
- 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.
fputc
editarA 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
editarNovamente, 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. |
Este módulo tem a seguinte tarefa pendente: criar exemplos de uso das funções |
fgetc
editar- 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;
}
fgets
editar- 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
editarA 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;
}
fread
editar- 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
editarfseek
editarPara 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
editarVolta para o começo do arquivo de um fluxo
feof
editarEOF ("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
editarFunção | Explicação |
---|---|
remove | Remove um arquivo especificado |
ferror e perror
editarProtó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);
}