1 Funções básicas de implementação de arquivos 1.1 Definindo registros Depois de um objeto do mundo real ter sido modelado, ou seja, após seus atributos importantes (e relevantes) terem sido identificados, deve-se criar uma estrutura (um tipo de dado) que consiga representá-lo na memória do computador. Toda a linguagem de programação de alguma forma oferece um método (um comando) capaz de definir variáveis que dêem suporte ao objeto modelado. Nesta apostila utilizar-se-á o Pascal e o C para exemplificar a manipulação de arquivos. Definição de uma estrutura (tipo) de um registro (objeto) em Pascal: Type <identificador_do_tipo> = Record End; <identificador_do_primeiro_atributo> : <tipo_do_atributo>; <identificador_do_segundo_atributo> : <tipo_do_atributo>; : : <identificador_do_último_atributo> : <tipo_do_atributo>; Type Pessoa = Record End; nome : String[30]; idade : Integer; sexo : Char; No trecho de código acima define-se um novo tipo de variável chamada Pessoa, que servirá para armazenar na memória do computador informações sobre pessoas. Esta variável possui como atributos (sub-variáveis) o nome da pessoa, a idade da pessoa e o sexo da pessoa. Note que os atributos foram definidos através de tipos básicos do Pascal: String (cadeia de caracteres), Integer (números inteiros) e char (caracter). Porém, estes atributos (variáveis) poderiam ser definidos por qualquer outro tipo, inclusive tipos complexos (como o próprio tipo Pessoa) definidos pelo programador. Em C uma estrutura é definida do seguinte modo: struct <identificador_do_tipo> ; Struct Pessoa <tipo_do_atributo> <identificador_do_primeiro_atributo>; <tipo_do_atributo> <identificador_do_segundo_atributo>; : : <tipo_do_atributo> <identificador_do_último_atributo>; char nome[30]; int idade; char sexo;
End; Note que existem algumas diferenças interessantes. No C o tipo da variável (atributo) é colocado antes do identificador. Além disso, não há a palavra record nem a palavra End. O que indica o início e o fim da definição da estrutura são as chaves e. Os tipos de variáveis também são um pouco diferentes, sendo que a diferença mais significativa dá-se pelo fato de no C não existir o tipo String, mas sim seqüências (vetores) de caracteres. A partir deste momento podem ser criadas variáveis na memória do computador capazes de armazenar informações sobre o objeto modelado: Pascal: Var Funcionario : Pessoa; C: Pessoa Funcionario; No exemplo acima são criadas as variáveis funcionário que irão armazenar objetos do tipo Pessoa nas linguagens Pascal e C, respectivamente. Estas variáveis são manipuladas (acessadas) através da seguinte notação: <objeto>.<atributo> Ou seja, o ponto. é o caracter especial utilizado para indicar que deseja-se manipular um atributo do objeto. Coloca-se o identificador do objeto, o ponto e o identificador do atributo. Estes atributos podem ser utilizados normalmente, como se fossem variáveis comuns, inclusive por procedimentos e funções de manipulação de variáveis oferecidas pela linguagem. Exemplos em Pascal: Funcionario.nome := Márcia ; Exemplos em C: Funcionario.idade := 24; Funcionario.sexo := F ; Readln(Funcionario.nome); Writeln(Funcionario.idade); strcpy(funcionario.nome, Márcia ); Funcionario.idade = 24; Funcionario.sexo = F ; printf( %s, Funcionario.nome); scanf( %d, &Funcionario.idade); Estas variáveis funcionam como se fossem variáveis normais, inclusive são perdidas quando o programa é finalizado. Para que os conteúdos destas variáveis sejam salvos, para que possam ser manipulados em outra execução do programa, é necessário armazenar o conteúdo destas variáveis (estruturas) em um arquivo. 1.2 Criando referências (apelidos) para um arquivo Para que um objeto seja armazenado em um arquivo é necessário primeiro criar um novo arquivo ou abrir um arquivo já existente (caso objetos já tenham sido armazenados anteriormente). Porém, antes de criar (ou abrir) o arquivo fisicamente (no dispositivo) é preciso declarar uma variável de programa que referencie 1 este arquivo no dispositivo de armazenamento. Por exemplo, se você deseja criar um arquivo chamado funcionarios.dat para armazenar seus registros de funcionários vai precisar de uma variável de arquivo para utilizá-lo. Para declarar uma variável do tipo referência para arquivo em Pascal utiliza-se a seguinte notação: <identificador_do_arquivo> : File of <tipo_dos_registros >; 1 Uma referência é como se fosse um apelido para o arquivo.
Onde identificador_do_arquivo é o nome da variável, file of são palavras especiais que avisam ao Pascal que esta variável é do tipo referencia de arquivo e o tipo_dos_registros indica que objetos serão armazenados neste arquivo. Vamos supor que o nome (identificador) escolhido para esta variável seja Arquivo_de_funcionarios, o código em Pascal para declarar e criar esta variável seria: Var Arquivo_de_funcionarios : File of Pessoa; A partir deste momento você tem um arquivo preparado para armazenar e manipular informações (registros) do tipo Pessoa. Note que quando você declara um arquivo como sendo de determinado tipo o Pascal só consegue armazenar informações (registros) deste tipo. Neste arquivo não podem ser armazenados registros de outros tipos, mas sim, somente objetos do tipo Pessoa. Em C não é necessário (nem possível) indicar o tipo dos registros que o arquivo contém. Isso significa que qualquer informação pode ser armazenada no arquivo (e de qualquer forma). Apesar de ser uma vantagem para o programador o fato dele poder armazenar registros diferentes em um mesmo arquivo, há a desvantagem de ele ter que controlar como devem ser armazenados estes registros (você tem liberdade, mas também tem responsabilidade). O código em C para criar uma variável que referencie um arquivo é o seguinte: FILE *Arquivo_de_funcionarios; Ou seja, a palavra FILE (em letras maiúsculas) seguida de um ponteiro 2 para o arquivo. Assim, sempre que quisermos utilizar o arquivo funcionarios.dat devemos utilizar o apelido dele (a sua referência): Arquivo_de_funcionarios. Porém, ainda não foi dito que a variável Arquivo_de_funcionarios é um apelido (referência) para o arquivo funcionarios.dat. É preciso fazer esta ligação entre o apelido e o arquivo disposto fisicamente no dispositivo. Em Pascal essa ligação (mapeamento) é feita pelo comando Assign: Assign(Arquivo_de_funcionarios, c:\temp\funcionarios.dat ); O trecho de código acima liga (mapeia) a variável Arquivo_de_funcionarios ao arquivo funcionarios.dat. Note que é possível especificar também o drive e o caminho onde encontra-se o arquivo. No caso a variável Arquivo_de_funcionarios foi ligada com o arquivo de nome funcionarios.dat no diretório temp do drive C:. cria uma referência para um arquivo no dispositivo de armazenamento (no disco rígido). Em C não há um comando especial para ligar a variável com o nome do arquivo (o local físico dele) no dispositivo. Isso é feito automaticamente quando se utiliza o comando de criação ou abertura do arquivo, apresentado a seguir. 1.3 Criando e abrindo arquivos O comando utilizado em Pascal para criar um novo arquivo no disco é: Rewrite(<referência_ao_arquivo>); Continuando o exemplo do arquivo de funcionários, o comando seguinte criaria um novo arquivo, chamado funcionarios.dat, no dispositivo: Rewrite(Arquivo_de_funcionarios); Note que se já existir um arquivo com esse mesmo nome no local (drive e diretório) especificado, o arquivo será zerado (seu conteúdo apagado). Caso já exista um arquivo de 2 Se você não sabe o que é um ponteiro não se preocupe, continua valendo a idéia de que você está criando uma referência (apelido) para o arquivo. Se você tem interesse em aprender especificamente sobre ponteiros, consulte um livro de linguagem C.
funcionários e você queira abrir este arquivo a fim de consultar suas informações ou adicionar novas informações, você deve utilizar o comando: Reset(Arquivo_de_funcionarios); Este comando abre o arquivo solicitado sem que as informações contidas nele sejam perdidas. Porém, se o arquivo não existir ele não será criado e nada irá acontecer. Em C há uma função que serve tanto para criar quanto para abrir arquivos. É a função fopen, que manipula arquivos através de um buffer 3. A sintaxe desta função é a seguinte: <referência_ao_arquivo> = fopen(<nome_do_arquivo>, <modo_e_tipo>); Já que trata-se de uma função, a variável de referência ao arquivo é retornada. Caso a função não consiga criar ou abrir o arquivo ela retorna um valor nulo (zero ou NULL). Do mesmo modo que no Pascal, o nome_do_arquivo pode conter o drive e o diretório do arquivo (o caminho path. O modo indica o modo de acesso, e pode ser um dos seguintes valores: r w - abre um arquivo somente para leitura (não permite escrita) - cria um novo arquivo somente para escrita (não permite leitura) r+ - abre um arquivo para leitura e escrita. w+ - cria um novo arquivo para leitura e escrita O tipo de arquivo pode ser: t b - arquivo texto - arquivo binário O tipo e o modo podem ser combinados a fim de criar ou abrir arquivos, tanto em modo texto quanto em modo binário 4. O exemplo da utilização desta função para criar um novo arquivo de funcionários (utilizando o tipo binário e com permissão de leitura e escrita) seria o seguinte: Arquivo_de_funcionarios = fopen( c:\\temp\\funcionarios.dat, w+b ); Já o comando para abrir um arquivo sem apagar seu conteúdo seria: Arquivo_de_funcionarios = fopen( c:\\temp\\funcionarios.dat, r+b ); 1.4 Movimentando-se em um arquivo A partir do momento que um arquivo é criado ou aberto ele é posicionado no primeiro registro. Caso você queira mover-se para um outro registro basta utilizar o seguinte comando em Pascal: Seek(<referencia_ao_arquivo>, <número_do_registro>); Seek(Arquivo_de_funcionarios, 4); 3 Consulte a seção de dispositivos de armazenamento para compreender o funcionamento de um buffer. Com o buffer, mais de um registro é lido simultaneamente. Além disso, os dados são gravados somente quando o buffer fica cheio ou quando o arquivo é fechado. Neste caso, quedas de luz ou outros problemas podem impedir que seus dados sejam salvos... Todas as funções de arquivo que começam com a letra f são bufferizadas (utilizam buffer), caso você não queira utilizar um buffer, utilize os mesmos comandos sem a letra f. 4 Aqui não são apresentadas as diferenças entre o tipo binário e o tipo texto. Para o armazenamento de objetos o tipo mais indicado é o binário, de modo que somente este modo será exemplificado. Para obter maiores informações sobre as diferenças entre estes tipos ou para aprender o funcionamento do tipo texto consulte um livro de C.
O exemplo anterior vai para o quarto registro do arquivo, caso ele exista. Em C o comando é um pouco mais complicado, pois deve-se passar também o modo de procura. seek(<referência_ao_arquivo>, <n>, <modo>); O modo de procura pode ser um dos seguintes: SEEK_SET SEEK_END SEEK_CUR - Parte do início do arquivo e avança <n> bytes - Parte do final do arquivo e retrocede <n> bytes - Parte do local atual e avança <n> bytes Justamente pelo fato dos arquivos em C não serem criados para um tipo específico de objeto ele não tem como saber qual é a posição de cada registro no arquivo (já que o tamanho de cada registro pode variar com o tipo de objeto). Portanto, não é possível, como no Pascal, pedir para que ele posicione-se no segundo, terceiro ou último registro. Deste modo, o programador é que deve saber o tamanho em Bytes de cada registro, e posicionar-se de acordo. Isso significa que a função seek movimenta-se de byte em byte. O parâmetro <n> indica quantos bytes devem ser avançados ou retrocedidos... O exemplo seguinte posiciona-se no quarto registro do arquivo de funcionários. Note que é utilizada uma função auxiliar a função sizeof() que indica quantos bytes possui o objeto: fseek(arquivo_de_funcionarios, 4 * sizeof(pessoa), SEEK_SET); Neste caso o tipo Pessoa, que é o tipo do objeto (registro), foi utilizado para indicar o tamanho de cada registro. Multiplicando-se o valor retornado por quatro obtém-se o local do quarto registro do arquivo. Caso o local (o registro) solicitado não exista não será feito o posicionamento e o registro atual continuará sendo o mesmo. Existem outras funções que podem ser utilizadas em conjunto com a função seek a fim de facilitar sua utilização. São funções que indicam se a posição atual corresponde ao final do arquivo, o registro atual e a função que indica o número total de registros armazenados no arquivo. Em pascal elas são as seguintes: EOF(<referência>); - Indica se a posição atual é o final do arquivo Filepos(<referência>); - Retorna o registro atual Filesize(<referência>); - Retorna o número total de registros no arquivo Em C as funções correspondentes são: feof(<referência>); fpos(<referência>); - Indica se a posição atual é o final do arquivo - Retorna a posição atual (em bytes) ftell(<referência>); - Retorna o tamanho total (em bytes) do arquivo 1.5 Gravando um registro Para gravar um registro no arquivo você deve primeiro posicionar-se na região que você deseja gravar o registro e utilizar o comando de gravação. Lembre-se que o comando de gravação não insere o registro no arquivo. Isso significa que se você posicionar-se em um registro já existente e mandar gravar os dados do arquivo serão substituídos (alterados). O comando gravar só insere (cria) um novo registro se você estiver posicionado no final do arquivo. Importante: Quando você grava (ou lê) um registro do arquivo você é posicionado automaticamente no próximo registro. O comando em Pascal que grava um registro no arquivo é: Write(<referência_ao_arquivo>, <registro>);
Write(Arquivo_de_funcionarios, Funcionario); Em C o comando (função) é: fwrite(<endereço_do_registro>, <tamanho>, <quantidade>, <referência>); fwrite(&funcionario, sizeof(funcionario), 1, Arquivo_de_funcionarios); Note que em C, justamente pelo fato do arquivo poder armazenar qualquer tipo de objeto (registro), é necessário informar o tamanho do registro. Não se esqueça de utilizar o símbolo & na frente do objeto pois o C exige que seja passado um endereço (ponteiro) para o registro, e não o registro. O parâmetro <quantidade> indica quantas vezes o registro deve ser gravado, e quase sempre é 1 5. 1.6 Lendo um registro Do mesmo modo que o comando de gravação, para ler um registro você deve primeiro posicionar-se nele (usando a função seek). Importante: Quando você lê (ou grava) um registro do arquivo você é posicionado automaticamente no próximo registro. O comando de leitura de registros em Pascal é: Read(<referência_ao_arquivo>, <registro>); Read(Arquivo_de_funcionarios, Funcionario); Este comando lê o registro atual do arquivo e coloca-o na variável especificada (no caso, funcionario). Esta variável pode ser então manipulada (geralmente seu conteúdo é impresso na tela para fins de listagem). Lembre-se que esta variável é independente do arquivo. Caso você altere o conteúdo da variável funcionário, por algum motivo, o registro correspondente no arquivo não será alterado. Caso você deseje alterar o arquivo para corresponder às alterações realizadas em memória, você deve reposicionar-se e utilizar o comando write. Em C o comando (função) é: fread(<endereço_do_registro>, <tamanho>, <quantidade>, <referência>); fread(&funcionario, sizeof(funcionario), 1, Arquivo_de_funcionarios); 1.7 Fechando um arquivo Antes de finalizar (sair) um programa que utilize arquivos você deve fechar o arquivo. O processo é similar a uma gaveta: se você abriu, deve fechar... Os comandos são: Pascal: Close(<referência_ao_arquivo>); C: fclose(<referência_ao_arquivo>); Exemplos: Pascal: Close(Arquivo_de_funcionarios); C: fclose(arquivo_de_funcionarios); 1.8 Exemplo de implementação de uma estrutura de arquivo serial em C ********************************************************************** ** Arquivo Serial 5 Se você encontrar uma utilidade prática e boa para este parâmetro, me avise...
** ** Este programa apresenta as funções básicas de manipulação de ** arquivos do tipo serial ** ** Prof. Leandro Krug Wives ** #include <condefs.h> #include <stdio.h> #include <string.h> #include <conio.h> Estrutura dos registros do arquivo struct Registro char nome[25]; int idade; float salario; char excluido; ; Função que consulta um registro do arquivo Retorna a posição física onde o registro foi encontrado ou -1 caso o registro não tenha sido encontrado Consulta_registro(FILE *arquivo, Registro registro) Registro registro_lido; int posicao; if(arquivo!= NULL) fseek(arquivo, 0L, SEEK_SET); posicao = 0; repete enquanto nao chega ao final do arquivo while(fread(®istro_lido, sizeof(registro_lido), 1, arquivo)) if(strcmpi(registro_lido.nome, registro.nome)==0 && (registro_lido.excluido == 0)) return posicao; posicao++; ; return -1; Função que insere um registro no arquivo Retorna: 1 se o registro foi incluído com sucesso 0 se o registro não pode ser incluído Insere_registro(FILE *arquivo, Registro registro) Registro registro_lido; int posicao ; if(arquivo!= NULL) posicao = 0; if(consulta_registro(arquivo, registro)) fseek(arquivo, 0L, SEEK_SET); repete enquanto nao chega ao final do arquivo while(fread(®istro_lido, sizeof(registro_lido), 1, arquivo)) if(registro_lido.excluido == 1) ; return 0; posicao++; fseek(arquivo, posicao*sizeof(registro), SEEK_SET); registro.excluido = 0; if(fwrite(®istro, sizeof(registro), 1, arquivo)) grava registro return 1; Função Exclui um registro do arquivo Retorna: 1 se o registro foi excluído com sucesso 0 se o registro não pode ser excluído Exclui_registro(FILE *arquivo, Registro registro) int posicao; if(arquivo!= NULL) posicao = Consulta_registro(arquivo, registro); if(posicao!= -1) fseek(arquivo, posicao*sizeof(registro), SEEK_SET); registro.excluido = 1; if(fwrite(®istro, sizeof(registro), 1, arquivo)) return 1; return 0;
Função que altera um registro do arquivo Retorna: 1 se o registro foi alterado com sucesso 0 se o registro não pode ser alterado Altera_registro(FILE *arquivo, Registro registro_antigo, Registro registro_novo) int posicao; if(arquivo!= NULL) posicao = Consulta_registro(arquivo, registro_antigo); if(posicao!= -1) fseek(arquivo, posicao*sizeof(registro), SEEK_SET); fread(®istro_antigo, sizeof(registro_antigo), 1, arquivo); registro_antigo.salario = registro_novo.salario; fseek(arquivo, posicao*sizeof(registro), SEEK_SET); fwrite(®istro_antigo, sizeof(registro_novo), 1, arquivo); return 1; return 0; Programa Principal void main(void) FILE *Arquivo; Declara um arquivo Registro Funcionario; char opcao; Arquivo = fopen("c:\\temp\\func.dbf", "r+b"); Tenta abrir um arquivo if(arquivo == NULL) Se arquivo nao existe Arquivo = fopen("c:\\temp\\func.dbf", "w+b"); Tenta criar um novo if(arquivo!= NULL) Se o arquivo foi aberto ou criado do Desenha um menu de opções clrscr(); printf("arquivo Serial\n"); printf("==============\n\n"); printf("\t[1] Consulta registro\n"); printf("\t[2] Insere registro\n"); printf("\t[3] Exclui registro\n"); printf("\t[4] Altera registro\n"); printf("\t[5] Lista registros\n"); printf("\t[6] Sai do programa\n"); flushall(); opcao = getch(); Lê a opção do usuário Realiza a ação solicitada clrscr(); switch(opcao) case '1': int posicao; printf("consultar por:"); scanf("%s", Funcionario.nome); if((posicao = Consulta_registro(Arquivo, Funcionario))!= -1) fseek(arquivo, posicao*sizeof(funcionario), SEEK_SET); fread(&funcionario, sizeof(funcionario), 1, Arquivo); printf("funcionario: %25s %3d %6.2f", Funcionario.nome, Funcionario.idade, Funcionario.salario); getch(); case '2': printf("inserir (nome, idade, salario):"); scanf("%s %d %f", Funcionario.nome, &Funcionario.idade, &Funcionario.salario); Insere_registro(Arquivo, Funcionario); case '3': printf("excluir :"); scanf("%s", Funcionario.nome); Exclui_registro(Arquivo, Funcionario); case '4': printf("alterar :"); scanf("%s", Funcionario.nome); printf("novo salario:"); scanf("%f", &Funcionario.salario); Altera_registro(Arquivo, Funcionario, Funcionario); case '5': fseek(arquivo, 0L, SEEK_SET); printf("funcionarios:\n\n"); while(fread(&funcionario, sizeof(funcionario), 1, Arquivo)) if(!funcionario.excluido) printf("\t%-25s %3d %6.2f\n", Funcionario.nome, Funcionario.idade, Funcionario.salario); getch(); ; while(opcao!= '6'); Repete enquanto o usuário não solicitar para sair fclose(arquivo); Fecha o arquivo