 |
Aula
do dia 19 de Maio de 2003 |
|
Estrutura e Arquivos
Lendo e Gravando Registros
Nós já vimos como números podem ser gravados usando as funções de leitura e gravação
formatada fscanf() e fprintf(). Entretanto, guardar números nos formatos oferecidos
por estas funções pode tomar um grande espaço em disco, pois cada dígito é armazenado
como um caractere.
Estas funções apresentam ainda um outro problema: não é a maneira direta de ler e escrever
tipos de dados complexos como matrizes e estruturas. Matrizes e estruturas são manuseadas
ineficientemente escrevendo cada elemento da matriz um por vez.
O modo registro grava números em arquivo em disco no formato binário; assim, inteiros são
gravados em dois bytes, números em ponto flutuante em quatro bytes etc.
O modo registro também permite gravar qualquer quantidade de dados de uma única vez; o
processo não é limitado a um único caracter ou string ou aos poucos valores que podem ser
colocados em fprintf() fscanf().
Matrizes, estruturas, matrizes de estruturas e outras construções de dados podem ser
gravadas com uma simples instrução.
Gravando Estruturas com fwrite()
Tomemos novamente a estrutura usada para os dados de livros para criar um arquivo em disco
que consiste de um número de registro de livros.
//grava registros de livros no arquivo
#include "stdio.h"
#include "stdlib.h"
main()
{
struct //declarando tipo SEM nome
{
char titulo[30];
int regnum;
double preco;
}livro; //declarando variavel livro
char numstr[81], resp;
FILE *fptr;
clrscr();
if((fptr=fopen("livros.rec","wb"))==NULL) //abre arquivo
{
printf("\nerro na abertura do arquivo");
getch();
exit(1);
}
do
{
printf("\n\nDigite titulo: ");
gets(livro.titulo);
printf("\nDigite registro: ");
gets(numstr);
livro.regnum = atoi(numstr);
printf("\nDigite preco: ");
gets(numstr);
livro.preco = atof(numstr);
fwrite(&livro,sizeof(livro),1,fptr); //gravando
printf("\nAdiciona outro livro (s/n) ?");
fflush(stdin);resp=getche();
}while(toupper(resp)=='S');
fclose(fptr); //fecha arquivo
}
Observe que o arquivo livros.rec é aberto em modo binário.
A informação obtida do usuário é colocada na estrutura livro. O ponto relevante deste
programa é a instrução:
fwrite(&livro,sizeof(livro),1,fptr);
A função fwrite() toma 4 argumentos. O primeiro é um ponteiro do tipo void que aponta para
a localização na memória do dado a ser gravado. No nosso exemplo, é o endereço da estrutura
livro.
O segundo argumento é um número inteiro que indica o tamanho do tipo de dado a ser gravado.
No nosso exemplo, usamos o operador sizeof() que retorna o tamanho da estrutura livro.
O terceiro argumento é um número inteiro que informa a fwrite() quantos itens do mesmo
tipo serão gravados. No nosso exemplo, a cada chamada a fwrite() será gravada uma estrutura
livro. O quarto argumento é um ponteiro para a estrutura FILE do arquivo onde queremos
gravar.
Gravando Matrizes com fwrite()
A função fwrite() não está restrita à gravação de estruturas em disco. Podemos usá-la
(e a sua função complementar fread() ) para trabalhar com outros dados também.
Por exemplo, suponhamos que queremos guardar uma matriz de inteiros com dez elementos no
disco.
Poderíamos gravar os dez itens, um por vez, usando fprintf(), o que não seria ineficiente.
O uso de fwrite() para este propósito se adapta perfeitamente.
/// grava matriz no disco
#include "stdio.h"
int tabela[10] = {1,2,3,4,5,6,7,8,9,10};
main()
{
FILE *fptr;
clrscr();
if((fptr=fopen("tabela.rec","wb"))== NULL)
{
printf("\nErro na abertura do arquivo");
getch();
}
else
{
fwrite(tabela,sizeof(tabela),1,fptr); //gravando
printf("dados gravados com sucesso");
getch();
fclose(fptr);
}
}
Lendo Estruturas com fread()
Para ler as estruturas gravadas com fwrite(), vamos usar a função fread().
//le os registros de livros do arquivo
#include "stdio.h"
main()
{
struct
{
char titulo[30];
int regnum;
double preco;
} livro;
FILE *fptr;
clrscr();
if((fptr=fopen("livros.rec","rb"))==NULL)
{
printf("\nErro na abertura do arquivo");
getch();
exit();
}
while(fread(&livro,sizeof(livro),1,fptr)==1) //lendo dados
{
printf("\n\nTitulo: %s\n",livro.titulo);
printf("\nRegistro: %03d\n",livro.regnum);
printf("\nPreco: %.2f\n",livro.preco);
getch();
}
printf("\n*** final do arquivo encontrado ***");
getch();
fclose(fptr);
}
O ponto essencial deste programa é a expressão:
fread(&livro,sizeof(livro),1,fptr)
A função fread() toma 4 argumentos. O primeiro é um ponteiro void para a localização da
memória onde serão armazenados os dados lidos. O segundo indica a quantidade de bytes do
tipo de dados a ser lido. O terceiro argumento informa a quantidade de itens a serem lidos
a cada chamada, e o quarto argumento é um ponteiro para a estrutura FILE do arquivo a ser
lido.
A função fread() retorna o número de itens lidos. Normalmente este número deve ser igual
ao terceiro argumento, neste caso 1. Se for encontrado o fim do arquivo, o número será
menor, neste caso 0.
No modo registro, dados numéricos são guardados em modo binário. Então um inteiro sempre
ocupará dois bytes, um número em ponto flutuante quatro bytes etc. Como as funções de
leitura e gravação no modo registro usam formato binário, elas são mais eficientes para
guardar dados numéricos do que funções que usam formato texto, como fprintf().
Visto que os dados são guardados em formato binário, é importante abrir o arquivo em modo
binário. Se ele for aberto em modo texto e o número 26 decimal for usado no arquivo, o
programa irá interpretá-lo como EOF e parará a leitura neste ponto.
Um Exemplo de Banco de Dados
Um programa somente poderá ser chamado de banco de dados, se no mesmo habilitar leitura e
gravação de arquivos.
Uma nova versão para o controle dos livros será descrita a seguir. O programa resultante
tomará os dados de até 50 livros e os colocará numa matriz de estruturas na memória do
computador.
Uma simples escolha do usuário fará com que a matriz toda seja gravada num arquivo em disco.
A interação com o usuário lhe permitirá ainda ler a matriz do arquivo novamente para a
memória do computador.
// manutencao da lista de livros do arquivo
#include "stdio.h"
#include "stdlib.h"
void novonome();
void listatudo();
void garq();
void larq();
struct livro
{
char titulo[30];
int regnum;
double preco;
};
struct livro livros[50];
int n=0;
char numstr[40];
main()
{
char ch;
do
{
clrscr();
printf("\nDigite A para adicionar um livro");
printf("\n I para imprimir todos os livros");
printf("\n G para gravar arquivo");
printf("\n L para ler arquivo");
printf("\n ESC para sair\n");
ch=getch();
switch(toupper(ch))
{
case 'A': novonome(); break;
case 'I': listatudo(); break;
case 'G': garq(); break;
case 'L': larq(); break;
}
}while(ch!=27);
}
//novonome - adiciona um novo livro
void novonome()
{
printf("\nRegistro %d.\nDigite titulo: ",n+1);
gets(livros[n].titulo);
printf("Digite o numero do registro (3 digitos): ");
gets(numstr);
livros[n].regnum=atoi(numstr);
printf("Digite preco: ");
gets (numstr);
livros[n].preco=atof(numstr);
n++;
}
// listatudo - lista os dados
void listatudo()
{
int j;
if(n<1)
{
printf("\nLista vazia");
getch();
return;
}
for(j=0;j<n;j++)
{
printf("\nRegistro numero: %d\n",j+1);
printf("\n Titulo: %s\n",livros[j].titulo);
printf("\nNumero do reg. : %03.d\n",livros[j].regnum);
printf("\n Preco : %4.2f\n",livros[j].preco);
getch();
}
}
// garq - grava matriz de estruturas em arquivo
void garq()
{
FILE *fptr;
if(n<1)
{
printf("\nNao posso gravar lista vazia\n");
getch();
return;
}
if((fptr=fopen("livros.rec","wb"))==NULL)
{
printf("\nErro na abertura do arquivo\n");
getch();
}
else
{
fwrite(livros,sizeof(livros[0]),n,fptr);
fclose(fptr);
printf("\n%d registros gravados.\n",n);
getch();
}
}
// larq - le registros do arquivo para a matriz
void larq()
{
FILE *fptr;
if((fptr=fopen("livros.rec","rb"))==NULL)
{
printf("\nErro na abertura do arquivo");
getch();
}
else
{
if(n<1)
{
printf("arquivo vazio");
getch();
}
else
{
while(fread(&livros[n],sizeof(livros[n]),1,fptr)==1)
n++;
}
fclose(fptr);
printf("\nArquivo lido !\n");
printf("\nO total de livros agora e: %d\n",n);
getch();
}
}
Na nova versão foram acrescentadas as funções garq() e larq().
A função garq() grava a matriz de n estruturas do tipo livro de uma única vez no arquivo
livros.rec em disco. A função de biblioteca C fwrite() é chamada para este fim.
Certamente não podemos projetar a função larq() para que trabalhe de modo similar a garq().
A função larq() deve ler os dados do arquivo um por vez, visto que ela não conhece de
antemão quantos livros estão presentes em livros.rec. Sua tarefa é executada pela chamada à
função de biblioteca C fread().
É importante lembrar que as funções fread() e fwrite() trabalham com qualquer tipo de dados,
incluindo matrizes e estruturas, e armazenam números em formato binário.
Acesso Aleatório
Nos exemplos anteriores, os nossos programas lêem e gravam em disco de modo seqüencial.
Isto é, quando gravamos o arquivo tomamos um grupo de itens (caracteres, strings ou
estruturas mais complexas) e os colocamos no disco um seguido ao outro. Igualmente,
quando lemos o arquivo, começamos no início e vamos até encontrar o fim.
É também possível acessar arquivos de modo aleatório. A vantagem dos arquivos de acesso
aleatório é que qualquer que seja a posição em que você está no arquivo pode acessar
qualquer outra sem precisar antes acessar todas as posições intermediárias.
O programa seguinte permite o acesso aleatório ao arquivo livros.rec.
// le um arquivo de livros, selecionado pelo usuario
#include "stdio.h"
main()
{
struct livro
{
char titulo[30];
int regnum;
double preco;
} livros;
FILE *fptr;
int numreg;
long int offset; /* deve ser long */
clrscr();
if((fptr = fopen("livros.rec","rb")) == NULL)
{
printf("\nErro na abertura do arquivo\n");
getch();
exit();
}
printf("\nDigite o numero do registro: ");
scanf("%d" ,&numreg);
offset=numreg * sizeof(livros);
if(fseek(fptr,offset,0)!=0)
{
printf("\nPosicao para o ponteiro invalida");
getch();
exit();
}
fread(&livros,sizeof(livros),1,fptr);
printf("\n Titulo: %s\n",livros.titulo);
printf("\nNumero do reg. : %03d\n",livros.regnum);
printf("\n Preco: %4.2f\n",livros.preco);
getch();
fclose(fptr);
}
Ponteiro de Arquivos
Para que você possa compreender como este programa trabalha, é necessário familiarizar-se
com o conceito de ponteiros para arquivos.
Um ponteiro para um arquivo aponta para um byte particular chamado posição atual. Algumas
funções, precisam conhecer o ponteiro para o arquivo que desejamos manipular. A cada tempo
em que gravamos ou lemos qualquer coisa no arquivo, o ponteiro é movido para o fim desta
coisa e a próxima leitura ou gravação começara neste ponto.
Quando o arquivo é aberto, o ponteiro é fixado no seu primeiro byte, então se quisermos
ler ou gravar nele estaremos no seu início.
Se abrirmos o arquivo usando a opção "a" (append), o seu ponteiro será posicionado no
fim do arquivo.
O ponteiro para o arquivo aponta para o byte do próximo acesso. A função fseek() permite a
movimentação deste ponteiro.
A Função fseek()
No programa anterior o ponteiro é fixado na posição desejada pela expressão:
if(fseek(fptr,offset,0)!= 0)
A função fseek() aceita três argumentos. O primeiro argumento é o ponteiro para a estrutura
FILE do arquivo. Após a chamada a fseek(), este ponteiro será movimentado para aposição que
desejarmos. O segundo argumento é chamado offset e consiste no número de bytes, a partir
da posição especificada pelo terceiro argumento, de deslocamento para ponteiro.
No nosso programa, offset é calculado através do produto do tamanho de um registro pelo
número de registros que desejamos acessar (lembre-se que o primeiro registro é o de número
0). É essencial que offset seja do tipo long int.
O último argumento é chamado modo. Existem três possibilidades para este número que
determinam de onde offset começará a ser medido.
Modo
|
Medido a partir de:
|
0
|
começo do arquivo
|
1
|
posição corrente do ponteiro
|
2
|
fim do arquivo
|
Uma vez fixado conteúdo do registro, o conteúdo do registro, a partir deste ponto é
armazenado na estrutura livros e finalmente impresso.
A Função ftell()
Em seus programas você pode ainda usar a função ftell() que retorna a posição do ponteiro
de um arquivo binário em relação ao seu começo. Esta função aceita um único argumento, que
é o ponteiro para a estrutura FILE do seu arquivo e retorna um valor do tipo long, que
representa o número de bytes do começo do arquivo até a posição atual.
A função ftell() pode não retornar o número exato de bytes se for usada com arquivos em
modo texto.
Um dos erros comuns no uso das funções fseek() e ftell() é o de não declarar o segundo
argumento de fseek() ou a variável que receberá o valor retornado por ftell() como sendo do
tipo long. Cuidado! Se fizer isto, o seu programa não trabalhará de acordo.
Condições de Erro
Na maioria dos casos, se um arquivo pode ser aberto, ele pode ser lido ou gravado. Existem
situações, entretanto, em que isto não acontece.
Um problema de hardware pode ocorrer enquanto a leitura ou gravação está sendo executada,
o término do espaço em disco enquanto gravamos, ou qualquer outro problema.
Nos exemplo anteriores, assumimos que semelhantes erros de leitura e gravação não ocorrem.
Mas em muitas situações, como programas em que a integridade dos dados é crítica, pode ser
desejável a verificação explícita de erros.
Várias funções de E/S não retornam um erro explícito. Por exemplo, se putc() retornar um
EOF, isto pode indicar tanto um EOF como um erro.
Para determinar se um erro ocorreu, podemos usar a função ferror(). O argumento que esta
função aceita é um ponteiro para FILE. Ela retorna 0, se nenhum erro ocorreu e não zero
(TRUE), se houver erro.
Uma outra função útil em conjunto com ferror() é perror(). O argumento de perror() é uma
"string" fornecida pelo programa que normalmente é uma mensagem de erro que indica em que
parte do programa ocorreu erro.
// grava dados formatados no arquivo e
// usa funcoes de verificacao de erros
#include "stdio.h"
main()
{
FILE *fptr;
char titulo[30];
int regnum;
double preco;
clrscr();
if((fptr=fopen("arqtext.txt","w"))==NULL)
{
printf("\nErro na abertura de arqtext.txt");
getch();
exit();
}
do
{
clrscr();
printf("\nDigite titulo");
fflush(stdin);
gets(titulo);
if (strlen(titulo)>1) //condicao de saida do loop
{
printf("\nDigite registro");
scanf("%d",®num);
printf("\nDigite preco");
scanf("%f",&preco);
if(ferror(fptr))
{
perror("Erro de gravacao: ");
getch();
fclose(fptr);
exit();
}
fprintf(fptr,"%s %d %f",titulo,regnum,preco);
}
else
{
break;
}
}while(1); //loop infinito
fclose(fptr);
}
Se, por exemplo, for detectado um erro de disco, ferror() retornará um valor verdadeiro
(não zero) e perror() imprimirá a seguinte mensagem:
Erro de gravaçao: Bad data
A primeira parte da mensagem é fornecida pelo programa, e a segunda parte, pelo sistema
operacional.
Mensagens de erro explícitas podem ser informativas para o usuário e para o programador
durante o desenvolvimento do programa.
|