Programação Orientada à Procedimentos II Programação Orientada à Procedimentos I Processamento Paralelo Lógica de Programação Introdução à Computação Informática Básica

 

 

Informática Básica
Introdução à Computação
Lógica de Programação
Programação Orientada à Procedimentos I
Programação Orientada à Procedimentos II

Processamento Paralelo

Aula do dia 12 de Maio

Aula do dia 19 de Maio de 2003

Aula do dia 22 de Maio



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.