Persistência de s Universidade do Estado de Santa Catarina - Udesc Centro de Ciências Tecnológicas - CCT Departamento de Ciência da Computação Tecnologia de Sistemas de Informação Estrutura de s II - DAD II Prof.: Denio Duarte Os dados dos programas necessitam, cada vez mais, ser armazenados em algum meio que permita que os mesmos possam ser recuperados futuramente. O ato de fazer com que os dados sejam armazenados permanentemente é chamado persistir os dados. Os dados são persistidos em memórias secundárias (e.g., discos). Existem várias formas de persistir os dados. Uma delas é se apoiar nos sistemas gerenciadores de banco de dados (SGBD). Neste caso, as tarefas de decidir a melhor forma de organizar os dados na memória secundária e a melhor forma de trazer esses dados para a aplicação, ficam transperente para o usuário programador. Por exemplo para criar um arquivo para armazenar dados de um cliente podemos emitir o comando: create table cliente (codigo int, nome varchar(20), ender varchar(40)) Para inserir dados neste arquivo (na terminologia de banco de dados relacional, um arquivo é chamado de tabela), simplesmente escreveríamos: insert into cliente values (1002, Jacques Langue, R. Blois, 10 ) Para recuperar os dados armazenados no arquivo (tabela) cliente, o comando seria: select codigo, nome, ender from cliente Podemos verificar que os SGBD s facilitam bastante a tarefa do programador quando trata-se de armazenar e recuperar dados. Porém, estamos interessados em outra forma de tratar o armazenamento e a recuperação de dados: de baixo nível. O que isso significa? O programador deve se preocupar com todos os detalhes de como o dado será armazenado (e.g., tamanho em bytes de cada campo, tamanho em bytes do registro, localização exata no arquivo em disco, entre outros) bem como formas de trazer esses dados do disco para a memória principal. Para tal, utilizaremos a linguagem C. Terminologias No nosso contexto, um arquivo será um arquivo de dados, ou seja, conterá informações estruturadas de tal forma que a organização nos permite recuperar os dados de forma organizada. Outros tipos de arquivos podem ser: Arquivos textos: os dados não tem estruturas e são armazenados, geralmente, em forma de cadeia de caracteres. As melhores formas de ler/gravar este tipo de arquivo é byte a byte ou linha a linha. Arquivos executáveis: são arquivos compreensíveis pelo sistema operacional e podem ser executados a partir do mesmo. Não são de interesse deste texto. Outros: imagem, filmes, etc. Os arquivos de dados são organizados por registros, que por sua vez são organizados através de campos que contém os dados. Se voltarmos para o arquivo (tabela) cliente acima, temos a seguinte organização:
Codigo : 1002 Nome : Jacques Langue Registro Ender : R. Blois, 10 Em C, um registro e seus componentes são criados através de struct. Para o nosso exemplo, poderíamos criar um tipo de registro com o seguinte comando: struct tpreg int codigo; char nome[20], ender[40]; Com isso, criamos um tipo de registro que pode armazenar o código, o nome e o endereço de um cliente no nosso arquivo de dados. Perceba que essa declaração contém todas as partes citadas anteriormente: o registro, o conjunto de campos e os dados, que nesse caso, estão representados pelo seus tipos. Quando tivermos uma instância desse registro, materializaremos a estrutura apresentada. O programa abaixo apresenta uma função que cria um registro com os seus campos e dados (a função poderia ser void, recebendo como parâmetro o endereço do tipo struct tpreg): struct tpreg criareg() printf("\ncodigo : "); scanf("%d",&mreg.codigo); printf("\nnome : "); gets(mreg.nome); printf("\nendereço: "); gets(mreg.ender); return MReg; O registro retornado pela função criareg está armazenado na memória principal, ou seja, quando o programa finalizar, os dados serão perdidos. Armazenando dados em C Neste texto quando dizemos armazenar os dados, estamos nos referindo a todas as atividades que são envolvidas com o tratamento dos dados armazenados: abrir ou criar o arquivo e ler/gravar/alterar/excluir os dados do arquivo aberto. Apesar de ter sido dito que trabalharíamos em baixo nível com os arquivos de dados, o C vai nos abstrair de alguns detalhes mais próximos do sistema operacional. Inicialmente, a linguagem possui um tipo chamado F ILE que é utilizado para criar um ponteiro para o arquivo na memória secundária. Assim, todas as operações com um determinado arquivo são feitas utilizando o ponteiro do tipo F ILE criado. Por exemplo, F ILE arq;, cria o ponteiro arq para uma estrutura que será associada a um arquivo na memória secundária. Esse arquivo pode ser existente ou criado pela aplicação. O primeiro passo para se trabalhar com armazenamento de dados em C é, então, associar um arquivo do disco com o ponteiro criado. A função C responsável por essa associação é a f open. Sua sintaxe é: ponteirof ILE = f open(arquivodisco, tipoarbertura), onde: ponteirof ILE é o ponteiro criado do tipo F ILE. Se a abertura ocorrer sem problemas, ponteirof ILE apontará para um endereço qualquer que, para o nosso caso, não nos interessrá. O que é necessário saber é que ponteirof ILE é a nossa ponte entre a aplicação (o programa) e o arquivo no disco. Se a função fopen retornar NULL indicará que o arquivo não foi aberto/criado com sucesso. arquivodisco é o nome do arquivo no disco, com o seu caminho caso não esteja na mesma pasta do programa. Pode ser uma variável do tipo char que contenha o caminho ou uma cadeia de caracteres em C. Por exemplo, arq = f open( //P rograma//s//cliente.dat,...), tenta abrir o arquivo Cliente.dat que está localizado no mesmo disco onde o programa está sendo executado, no caminho a partir da raiz /P rograma/s/. Se arq tiver um valor diferente de NULL, a operação teve sucesso,
caso contrário algum problema ocorreu: o caminho é inválido, o arquivo não existe (caso for aberto para leitura), o disco está cheio, o usuário não tem privilégio para acessar o arquivo, entre outros. tipoabertura é uma cadeia de caracteres que informa como o arquivo será aberto. Podemos abrir um arquivo no formato binário (nosso caso) ou no formato texto, pode ser apenas para escrita (o arquivo será criado), para leitura (o arquivo deve existir), para acrescentar dados (o arquivo deve existir), entre outros. Aqui, vamos focar apenas na seguinte combinação (leiam a ajuda da função f open para obter mais detalhes do tipo de abertura): arq = fopen( T este.dat, r + b ), que vai abrir um arquivo T este.dat do tipo binário (b) para leitura e escrita (r+). Se o arquivo T este.dat não existir, o ponteiro arq receberá o valor NULL. Devemos fazer o seguinte para garantirmos que, caso o arquivo não exista, o mesmo seja criado e, caso exista, seja simplesmente aberto: : // existe codigo acima arq=fopen("teste.dat","r+b"); if (arq==null) arq=fopen("teste.dat","w+b"); // cria o arquivo if (arq==null) return erro; // erro generico na abertura (encerra o programa) : // mais codigo abaixo Após fazer as operações necessárias no arquivo através do ponteiro, devemos fechar o arquivo antes de finalizar o programa. A função que fecha o arquivo é f close(ponteirof ILE). Se f close retornar 0, o arquivo apontado por ponteirof ILE foi fechado com sucesso, caso contrário, houve algum erro. Por exemplo, imagine que o arquivo foi aberto em uma mídia removível (e.g., pen drive) e antes do programa encerrar a mídia foi removida. No nosso programa acima, devemos emitir um f close(arq) antes do fim do programa. Existem vários comandos em C para escrever e ler de arquivos. Vamos considerar apenas as funções fread e fwrite que lêem e escrevem, respectivamente, dados em arquivos apontados por um ponteiro do tipo F ILE. A sintaxe (e a semântica) do fwrite(&buffer, qtbytes, qtv ezes, ponteirof ILE) é a seguinte: buffer é o nome da variável que contém os dados a serem armazenados. Geralmente, utilizamos & para indicar o endereço deste local, caso buffer não seja um ponteiro. qtbytes é a quantidade de bytes a serem gravadas a partir de buffer. Se buffer conter vários bytes mas colocarmos 1 neste parâmetro, apenas o primeiro byte será armazenado. Neste caso temos um problema: como saber quantos bytes buf f er tem? Utilizaremos a função sizeof(tipobuf f er) para resolver este problema. No arquivo de dados existe um ponteiro que aponta para os bytes dentro do arquivo. Se esse ponteiro estiver apontado para uma posição no meio do arquivo, os novos dados sobreporão dados existentes na posição que o ponteiro se encontrava. Assim, certifique-se que o ponteiro dos bytes esteja apontando para o último byte + 1, garantindo, assim, que os novos dados não sobreporão dados existentes. Veremos mais tarde como garantir essa restrição. qtv ezes indica quantas vezes o buf f er com qtbytes será armazenado. Normalmente, colocamos 1 neste parâmetro. Finalmente, ponteirof ILE é o ponteiro para o arquivo que armazenará os dados de buffer. A função fwrite retorna o número de itens escritos no arquivo (parâmetro qtv ezes), caso ocorra um erro, o valor retornado será diferente de qtv ezes. O trecho do programa abaixo escreve dados no arquivo aberto anteriormente: : MReg=criaReg(); : // faz o ponteiro do arquivo de dados ir para o fim
if (fwrite(&mreg,sizeof(struct tpreg),1,arq)!=1) printf("\nerro na escrita. O programa sera finalizado"); return erro; : A função fread(&buffer, qtbytes, qtv ezes, ponteirof ILE) é semelhante à função fwrite, inclusive os parâmetros. A diferença é que qtbytes serão lidos qtv ezes de ponteirof ILE e serão armazenados em buffer. O mesmo cuidado quanto a posição do ponteiro do arquivo de dados deve ser tomada no fread. Se o ponteiro não estiver apontado para o primeiro byte do registro a ser lido, a leitura preencherá o buffer com bytes deslocados, fazendo com que buffer contenha lixo. Após a leitura, o ponteiro do arquivo será avançado qtbytes qtv ezes bytes. Ao abrirmos o arquivo conforme apresentado na função f open, o ponteiro do arquivo de dados apontará para o primeiro byte, posição 0. Abaixo um exemplo de leitura de dados baseado nos exemplos anteriores: : // o arquivo foi recém aberto if (fread(&mreg,sizeof(struct tpreg),1,arq)!=1) printf("\nerro na leitura. O programa sera finalizado"); return erro; printf("\ncodigo : ",MReg.codigo); printf("\nnome : ",MReg.nome); printf("\nendereco: ",MReg.ender); : // programa continua A função feof(ponteirof ILE) retorna falso enquanto o fim do arquivo não foi atingido. O trecho abaixo exemplifica a leitura de todos os dados armazenados no arquivo aberto. int main() arq=fopen("teste.dat","r+b"); if (arq==null) arq=fopen("teste.dat","w+b"); // cria o arquivo if (arq==null) return erro; // finaliza o programa fread(&mreg,sizeof(struct tpreg),1,arq) while (!feof(arq)) printf("\ncodigo : ",MReg.codigo); printf("\nnome : ",MReg.nome); printf("\nendereco: ",MReg.ender); fread(&mreg,sizeof(struct tpreg),1,arq) fclose(arq); return sucesso; Para movimentar o ponteiro do arquivo de dados, utilizamos a função f seek(ponteirof ILE, qtbytes, ref erencia), onde: qtbytes é o número de bytes que o ponteiro do arquivo de dados será deslocado.
referencia é a referência para fazer o deslocamento do ponteiro: 0 indica que é do início do arquivo, 1 a partir da posição atual do ponteiro e 2 a partir do fim do arquivo. Existem as constantes SEEK SET, SEEK CUR e SEEK END que representam os três valores, respectivamente. Para posicionar o ponteiro no início do arquivo apontado por arq basta executar o comando f seek(arq, 0, 1) ou f seek(arq, 0, SEEK SET ). Para apontar para a próxima posição livre do arquivo de dados para gravação, o comando é fseek(arq, 0, 2) ou fseek(arq, 0, SEEK END). Todas as operações descritas aqui estão na biblioteca stdio.h, assim não esqueçam de incluí-la no programa. Trabalho para 22/04 Baseado no descrito acima e pesquisas faça o seguinte trabalho para ser entregue no dia 22/04: desenvolva um programa que tenha duas opções, inserir dados no arquivo e listar todos os registros do arquivo. O registro será composto pelos campos: nome, idade, endereço de e-mail. Uma sugestão de interface: 1 - Insere dados no arquivo 2 - Lista todos os registros 3 - Finaliza Quando usuário digitar 1, serão pedidos os dados do registro que em seguida serão gravados. Para o opção 2, todos os registros gravados anteriormente serão apresentados. Para sair do programa, o usuário digitará 3. O programa deverá ser entregue impresso. Dúvidas deverão ser enviadas para denio@joinville.udesc.br.