UDESC - SBS Departamento de Sistemas de Informação LPG-I: Entrada & Saída de Dados - Arquivos Prof. Flavio Marcello Strelow [email protected] BD, Banco de Dados “conjunto de cadastros (tabelas) que se relacionam entre si”. DB, Database Definições e Conceitos Uma TABELA é uma estrutura de dados que agrupa informações em linhas e colunas. Cada linha de uma tabela, denominada REGISTRO, contém informações sobre um determinado item ou elemento, distribuídas em colunas. Cada coluna da tabela é denominada de CAMPO. O tipo de dado do campo é definido pela natureza da informação que ele armazena. Pode ser numérico (inteiro ou real), alfabético ou alfanumérico (combinação de alfabético e numérico). Glossário - Parte I • Banco de dados: conjunto de cadastros (tabelas) que se relacionam entre si (ex. estoque). • Tabela, ou arquivo: conjunto de registros (ex. relação dos clientes da empresa). • Registros: elementos, ou itens, do arquivo (ex. Restaurante Merion, Faculdade Mater Dei, ...). • Campos: armazenam as informações do cadastro (código, nome, endereço, telefone, ...) O que é uma Tabela ? colunas = campos linhas = registros indicador de posição ponteiro de registro (registro atual)- denota o registro com o qual o usuário está trabalhando ESTRUTURA INTERNA DA TABELA (OU ARQUIVO) ponteiro de registro (indicador de posição) R1 0 1 * tam 2 * tam ... (n – 1) * tam R2 R3 registros do arquivo ... Rn marca de final de arquivo (End Of File) posição física do registro onde: “tam” representa o tamanho em bytes do registro (sizeof) Definições e Conceitos De forma geral, o termo banco de dados pode ser utilizado para indicar um conjunto de tabelas. Uma tabela, por sua vez, é um conjunto de registros, ou linhas, subdivididos em muitas colunas, uma para cada campo do registro. Outro conceito destacável é o conceito de registro atual, que denota o registro com o qual o usuário está trabalhando, registro este que é indicado pelo ponteiro de registro (indicador de posição) disponível para cada tabela aberta no programa. Em Síntese: BD- Banco de Dados ou DB- Database Campo1 Campo2 Registro1 ... Tabela1 Registro2 ... Banco de Dados Tabela2 ... TabelaN RegistroN CampoN Definições e Conceitos Os conceitos de tabelas de memória através da utilização de vetores e o conceito de utilização do tipo definido registro (struct), formam a base para a utilização de um arquivo, pois um arquivo é na verdade uma tabela de informações gravada em um meio físico. Enquanto os vetores são tabelas manipuladas na memória RAM. Os vetores são manipulados através de um ou mais índices de controle enquanto os arquivos são manipulados por um ponteiro de registro. Vetores + Registro (struct) = Arquivo (FILE) Vantagens na Utilização de Arquivos • armazenamento em meios magnéticos – as informações são gravadas permanentemente em meio físico (memória secundária: disquete, HD, fitas magnéticas entre outros) • maior capacidade de armazenamento – armazena um número grande de registros, estando apenas limitado ao tamanho do meio físico utilizado para a sua gravação Arquivos A memória principal do microcomputador é volátil, não permitindo que as informações fiquem armazenadas permanentemente, pois se o micro for desligado, as informações armazenadas são apagadas e se perdem. O mesmo ocorre com as tabelas (vetores) criadas na memória do microcomputador: se acontecer algum problema com o micro durante a execução de um programa que cria ou modifica uma tabela, as informações contidas nesta tabela serão perdidas. A solução para este tipo de problema é utilizar uma estrutura de dados que permita a gravação de informações em dispositivos de memória secundária (disquete, HD, ...). Nas linguagens de programação a estrutura de dados que guarda informações em disco é chamada de ARQUIVO (FILE). Sistema de E/S: Programa escrito em Linguagem C (*.cpp) Entrada (input) camada, ou interface de “abstração” stream dispositivos reais: Arquivo em disco Teclado Sistema de E/S: Programa escrito em Linguagem C (*.cpp) Saída (output) camada, ou interface de “abstração” stream dispositivos reais: Arquivo em disco Console, ou terminal de vídeo Impressora Streams e Arquivos O sistema de E/S de C fornece uma “interface” consistente ao programador C, independente do dispositivo real que é acessado. Isto é, o sistema de E/S de C provê um nível de abstração entre o programador e o dispositivo utilizado. Essa abstração é chamada de stream e o dispositivo real é chamado de arquivo. Stream O sistema de arquivos de C é projetado para trabalhar com uma ampla variedade de dispositivos, incluindo terminais, acionadores de disco e acionadores de fitas. Embora cada um dos dispositivos seja muito diferente, o sistema de arquivo com buffer transforma-os em um dispositivo lógico chamado stream, ou seja, a mesma função pode escrever em um arquivo em disco ou em algum outro dispositivo, como o console. Stream Existem dois tipos de streams: texto e binária. Stream de Texto Uma stream de texto é um seqüência de caracteres organizadas em linhas terminadas por um caractere de nova linha (“\n”). Streams Binárias Uma stream binária é uma seqüência de bytes com uma correspondência de “um para um” com aqueles encontrados no dispositivo externo, ou seja, o número de bytes escritos (ou lidos) é o mesmo que o encontrado no dispositivo externo. Arquivos (1/2) Em C, um arquivo pode ser qualquer coisa, desde um arquivo em disco até um terminal ou uma impressora. Você associa uma stream com um arquivo específico realizando uma operação de abertura (fopen()). Uma vez o arquivo aberto, informações podem ser trocadas entre ele e o seu programa. Um arquivo é desassociado de uma stream específica por meio de uma operação de fechamento (fclose()). Se um arquivo aberto para saída for fechado, o conteúdo, se houver algum, de sua stream associada será escrito no dispositivo externo. Esse processo é geralmente referido como descarga (flushing) da stream e garante que nenhuma informação seja acidentalmente deixada no “buffer de disco”. Todos os arquivos são fechados automaticamente quando o programa termina normalmente, com main() retornando ao sistema operacional ou uma chamada exit(). Os arquivos “não” são fechados quando um programa quebra (crash) ou quando ele chama abort(). Arquivos (2/2) Cada stream associada a um arquivo tem uma estrutura de controle de arquivo do tipo FILE. Essa estrutura é definida no arquivo cabeçalho “stdio.h”. typedef struct { short level; unsigned flags; char fd; unsigned char hold; short bsize; unsigned char *buffer, *curp; unsigned istemp; short token; } FILE; A separação que C faz entre streams e arquivos pode parecer desnecessária ou estranha, mas a sua principal finalidade é a consistência da interface. Em C, você precisa pensar em termos de stream e usar apenas um sistema de arquivos para realizar todas as operações de E/S. O compilador C converte a entrada ou saída em linha em uma stream facilmente gerenciada. Fundamentos do Sistema de Arquivos O sistema de arquivos C é composto de diversas funções inter-relacionadas. Essas funções exigem que o cabeçalho “stdio.h” seja incluído em qualquer programa em que são utilizadas. Nome Função fopen() Abre um arquivo fclose() Fecha um arquivo fputc() e fputs() Escreve um caractere e uma string no arquivo fgetc() e fgets() Lê um caractere e uma string de um arquivo fseek() Posiciona o arquivo em um byte específico fprintf() É para um arquivo o que printf() é para o console fscanf() É para um arquivo o que scanf() é para o console feof() Verdadeiro se o fim de arquivo for encontrado ferror() Verdadeiro se ocorreu um erro rewind() Recoloca o ponteiro de registro (indicador de posição) no início do arquivo remove() Apaga um arquivo fflush() Descarrega um arquivo O Ponteiro de Arquivo Um ponteiro de arquivo é um ponteiro para informações que definem várias coisas sobre o arquivo, incluindo seu nome, status e a posição atual do arquivo. Basicamente, o ponteiro de arquivo identifica um arquivo específico em disco e é usado pela stream associada para direcionar as operações das funções de E/S. Um ponteiro de arquivo é uma variável ponteiro do tipo FILE. Para ler ou escrever arquivos, seu programa precisa usar ponteiros de arquivos. Os ponteiros de arquivo devem declarados obedecendo a seguinte sintaxe: FILE *fp; desta forma, indicamos fp como sendo um ponteiro para um arquivo. Abrindo um Arquivo A função fopen() abre uma stream para uso e associa um arquivo em disco a ela. A função fopen() tem o seguinte protótipo: FILE *fp; fp = fopen(const char *nomearq, const char *modo); onde: nomearq modo uma cadeia de caracteres que forma um nome válido de arquivo (pode incluir uma especificação de caminho), define qual arquivo deverá aberto. determina como o arquivo será aberto (para leitura, para escrita, para leitura/escrita, etc.), define que tipo de uso pode ser feito como o arquivo aberto. nota: A função fopen() devolve um ponteiro de arquivo. Seu arquivo nunca deve alterar o valor desse ponteiro. Se ocorrer um erro quando estiver tentando abrir um arquivo, fopen() devolve um ponteiro nulo (NULL). “Modo” de abertura válidos (1/2) Modo Significado "r" Abre um arquivo texto para leitura. O arquivo deve existir antes de ser aberto. "w" Abrir um arquivo texto para gravação. Se o arquivo não existir, ele será criado. Se já existir, o conteúdo anterior será destruído. "a" Abrir um arquivo texto para gravação. Os dados serão adicionados no fim do arquivo ("append"), se ele já existir. Se o arquivo não existir, ele será criado. "rb" Abre um arquivo binário para leitura. Igual ao modo "r" anterior, só que o arquivo é binário. "wb" Cria um arquivo binário para escrita, como no modo "w" anterior, só que o arquivo é binário. "ab" Acrescenta dados binários no fim do arquivo, como no modo "a" anterior, só que o arquivo é binário. “Modo” de abertura válidos (2/2) Modo Significado "r+" Abre um arquivo texto para leitura e gravação. O arquivo deve existir e pode ser modificado. "w+" Cria um arquivo texto para leitura e gravação. Se o arquivo existir, o conteúdo anterior será destruído. Se não existir, ele será criado. "a+" Abre um arquivo texto para gravação e leitura. Os dados serão adicionados no fim do arquivo se ele já existir. Se o arquivo não existir, ele será criado. "r+b" Abre um arquivo “binário” para leitura e escrita. "w+b" Cria um arquivo “binário” para leitura e escrita. "a+b" Acrescenta dados ou cria uma arquivo “binário” para leitura e escrita. Observações: Abrindo um Arquivo Em muitas implementações, no modo texto, seqüências de retorno de carro/alimentação de linha são traduzidas para caracteres de nova linha (“\n”) na entrada. Na saída, ocorre o inverso: novas linhas são traduzidas para retorno de carro/alimentação de linha. Abrindo um arquivo no modo “leitura” ou no modo “escrita” o ponteiro de registro (ou indicador de posição) é colocado no início do arquivo, ou seja, no primeiro registro ou byte do arquivo. Abrindo o arquivo no modo “adição”, ou append o ponteiro de registro é colocado no fim do arquivo. Verificando a existência de um arquivo: FILE *fp fp = fopen("saida.txt", "r"); if (fp == NULL) printf("ERRO: arquivo não pode ser aberto."); Fechando um Arquivo A função fclose() fecha uma stream que foi aberta por meio de uma chamada a função fopen(). Ela escreve no meio físico qualquer dado que ainda permanece no buffer de disco no arquivo e, então, fecha normalmente o arquivo em nível de sistema operacional. Uma falha ao fechar a stream atrai todo tipo de problema, incluindo perda de dados, arquivos destruídos e possíveis erros intermitentes em seu programa. Uma fclose() também libera o bloco de controle de arquivo associado a stream, deixando-o disponível para reutilização. A função fclose() tem o seguinte protótipo: int fclose(FILE *fp); Onde fp é o ponteiro de arquivo devolvido pela chamada a fopen(). Um valor de retorno zero significa uma operação de fechamento bem-sucedida. Qualquer outro valor indica erro. Verificando o “Final de Arquivo” A função feof() determina quando o ponteiro de registro (indicador de posição) atingiu a marca de final de arquivo (eof, end of file). A função feof() tem o seguinte protótipo: int feof(FILE *fp); Onde fp é o ponteiro de arquivo devolvido pela chamada a fopen(). O valor de retorno igual 1 (true) significa que o “final de arquivo” foi atingido e 0 (false) caso contrário. por exemplo: while (feof(fp) == 0) ou while (!feof(fp)) deve ser lido, ou interpretado como: repete o processo enquanto “não” for final de arquivo fread() e fwrite() Para ler e escrever tipos de dados maiores que um byte (stream binário), o sistema de arquivo C fornece duas funções: fread() e fwrite(). Essas funções permitem a leitura e a escrita de blocos de qualquer tipo de dados. Seus protótipos são: size_t fread(void *rg, size_t numBytes, size_t n, FILE *fp); size_t fwrite(const void *rg, size_t numBytes, size_t n, FILE *fp); Para fread(), rg é um ponteiro para uma região de memória que receberá os dados lidos do arquivo. Para fwrite(), rg é um ponteiro para as informações que serão escritas no arquivo. O número de bytes a ler ou escrever é especificado por numBytes. O argumento n determina quantos itens (cada um contendo numBytes) serão lidos ou escritos. Finalmente, fp é um ponteiro para uma stream aberta anteriormente. Estas funções retornam o número de itens lidos ou escritos. Esse valor será igual a n a menos que ocorra um erro. Roteiro para Trabalhar com Arquivos Binários: 1. declarar a estrutura e a variável registro (struct); 2. declarar e inicializar os contadores e somatórios; 3. declarar o ponteiro de arquivo (FILE *fp); 4. abrir (fopen) o arquivo no modo desejado; 5. ler o “primeiro” registro do arquivo; 6. ler o arquivo até o final: while (!feof(fp)) { ... }; 7. processar as informações, ou campos, do registro lido; 8. ler o “próximo” registro do arquivo; 9. fechar (fclose) o arquivo; 10. exibir os resultados obtidos. // Este trecho de programa lê um arquivo do primeiro registro // até o último (marca de final de arquivo- EOF). ... struct RgFunc { 1 Lê um bloco com sizeof(varRgFunc) bytes e char nome[35]; armazena dados lidos na variável varRgFunc. char sexo; float salario; Obs. Este processo de leitura avança o indicador } varRgFunc; de posição para o primeiro byte do próximo registro. void main() { int ctF = 0, ctM = 0; // declaração dos contadores FILE *fp; 3 fp = fopen("func.dat", "rb"); 2 4 printf("Processando os dados armazenados no arquivo...\n\n"); 5 fread(&varRgFunc, sizeof(varRgFunc), 1, fp); while (!feof(fp)) { 6 if (varRgFunc.sexo == ‘M’) 7 ctM = ctM + 1; else ctF = ctF + 1; fread(&varRgFunc, sizeof(varRgFunc), 1, fp); } 9 fclose(fp); 8 printf("Processamento concluído !\n\n"); printf("%2d funcionários do sexo feminino.\n", ctF); printf("%2d funcionários do sexo masculino.\n", ctM); } 1 0 MEMÓRIA SECUNDÁRIA (disquete, HD, ...) MEMÓRIA PRINCIPAL ponteiro de registro (indicador de posição) varRgFunc R1 R2 R3 ... RN fread(&varRgFunc, sizeof(varRgFunc), 1, fp); R1 registros do arquivo “func.dat” marca de final de arquivo (End Of File) // Esta função realiza o cadastro de dados em um arquivo binário struct RgAluno { char sit; // situação do registro: X- ocupado e *- apagado char nome[35]; float nota1; Grava o bloco de sizeof(varRgFunc) bytes armazenados float nota2; na variável varRgFunc no arquivo “Alunos.dat”. } varRgAluno; Obs. Concluído o processo de gravação eof é atingido. void Cadastrar(void) { FILE *fp; fp = fopen("Alunos.dat", "a+b"); char confirma; while (1) { clrscr(); printf("Informe o nome do aluno, (FIM) para encerrar:\n"); fflush(stdin); // limpa o buffer do teclado gets(varRgAluno.nome); if (strcmp(varRgAluno.nome, "FIM") == 0) break; printf("Informe a nota do 1o. Bimestre: "); scanf("%f", &varRgAluno.nota1); printf("Informe a nota do 2o. Bimestre: "); scanf("%f", &varRgAluno.nota2); do { printf("\nConfirma os dados (s/n): "); confirma = toupper(getche()); } while ((confirma != 'S') && (confirma != 'N')); if (confirma == 'S') { varRgAluno.sit = 'X'; // indica que a posição foi ocupada por um registro fwrite(&varRgAluno, sizeof(varRgAluno), 1, fp); } } fclose(fp); } fseek() Operações de leitura (fread()) e escrita (fwrite()) aleatórias podem ser executadas utilizando a ajuda da função fseek(), que modifica o indicador de posição de arquivo. Seu protótipo é mostrado aqui: int fseek(FILE *fp, id *rg, long numBytes, int origem); Aqui, fp é um ponteiro de arquivo devolvido por uma chamada fopen(). numBytes, um inteiro longo, é o número de bytes a partir origem, que se tornará a nova posição corrente, e origem é uma das seguintes macros: SEEK_SET início do arquivo (primeiro byte) SEEK_CUR posição atual SEEK_END final do arquivo (último byte) Portanto, para mover numBytes a partir do íncio do arquivo, origem deve ser SEEK_SET. Para mover da posição atual, deve-se utilizar SEEK_CUR e para mover a partir do final do arquivo, deve-se utilizar SEEK_END. A função fseek() devolve 0 quando bem-sucedida e um valor diferente de zero se ocorrer um erro. ftell() A função ftell() devolve o valor atual do indicador de posição de arquivo para a stream especificada. Para arquivos binários, o valor é o número de bytes cujo indicador está a partir do início do arquivo.Seu protótipo é mostrado aqui: long ftell(FILE *fp); // Este trecho de programa calcula o número do registro // dividindo o valor do indicador de posição pela // quantidade de bytes do registro printf("Posição Nome do Aluno.........................\n"); long pos; // lê o primeiro registro fread(&varRgAluno, sizeof(varRgAluno), 1, fp); while (!feof(fp)) { // não exibe os registro apagados if (varRgAluno.sit == 'X') { pos = ftell(fp) / sizeof(varRgAluno); printf("%4ld %s", pos, varRgAluno.nome); } // lê o próximo registro fread(&varRgAluno, sizeof(varRgAluno), 1, fp); } // Esta função lê os registros do arquivo do final para o início. void UltimoAoPrimeiro(void) { FILE *fp; fp = fopen("Alunos.dat", "rb"); printf("Posição Nome do Aluno..........................\n"); printf("===============================================\n"); // posiciona no primeiro byte do último registro fseek(fp, 0L, SEEK_END); long pos = ftell(fp) / sizeof(Aluno); fseek(fp, (pos-1) * sizeof(Aluno), SEEK_SET); // lê o último registro fread(&varRgAluno, sizeof(varRgAluno), 1, fp); while (pos != 0) { // não exibe os registro apagados if (varRgAluno.sit == 'X') { printf("%4ld %s", pos, varRgAluno.nome); } // posiciona no registro anterior pos = pos - 1; fseek(fp, (pos-1) * sizeof(varRgAluno), SEEK_SET); // lê o registro anterior fread(&varRgAluno, sizeof(varRgAluno), 1, fp); } fclose(fp); } fgetpos() int fgetpos(FILE *fp, const fpos_t *posição); A função fgetpos() armazena o valor atual do indicador de posição de arquivo no objeto posição. fsetpos() int fsetpos(FILE *fp, const fpos_t *posição); A função fsetpos() move o indicador de posição de arquivo para o ponto espeficado pelo objeto apontado por posição. Esse valor deve ser previamente obtido por meio de uma chamada a função fgetpos(). por exemplo: FILE *fp fpos_t posicaoSalva; fgetpos(fp, &posicaoSalva); // posição original // operações que deslocam o indicador de posição // ... fsetpos(fp, &posicaoSalva); // retorna a posição original rewind() A função rewind() reposiciona o ponteiro de registro, ou indicador de posição de arquivo no início do arquivo especificado como seu argumento. Isto é, ela “rebobina” o arquivo. A função rewind() tem o seguinte protótipo: void rewind(FILE *fp); Onde fp é o ponteiro de arquivo devolvido pela chamada a fopen(). Nota: 1) o início de uma “stream de texto” é o primeiro caractere do arquivo texto 2) o início de uma “stream binário” é o primeiro byte do arquivo Apagando Arquivos A função remove() apaga do diretório do disco (meio externo) o arquivo especificado como seu argumento. Seu protótipo é: int remove(const char *nomeArq); Onde nomeArq representa o nome do arquivo que deverá ser apagado. A função remove() retorna “zero”, para indicar que a operação foi bem-sucedida, e um valor diferente de zero, caso contrário. por exemplo, para apagar o arquivo “saida.txt”: if (remove("saida.txt") != 0) printf("O arquivo não pode ser apagado."); Esvaziando uma Stream Para esvaziar o conteúdo de uma stream de saída, deve-se utilizar a função fflush(). A função fflush() tem o seguinte protótipo: int fflush(FILE *fp); Essa função escreve o conteúdo de qualquer dado existente no buffer para o arquivo associado a fp. A função fflush() retorna “zero” para indicar sucesso; caso contrário, devolve EOF. Nota: Esta função é muito útil em um processo de atualização quando se faz leituras e gravações alternadamente. Operações sobre Tabelas e/ou Arquivos (1/6) Pesquisa Esta é talvez a operação mais comum que se realiza sobre tabelas e consiste basicamente em determinar em que posição da tabela se encontra um determinado valor. Há três formas básicas para localização de registros: (1) pesquisa seqüencial (um registro após o outro), (2) pesquisa binária (com o conjunto ordenado, divide-se o conjunto em duas partes) (3) pesquisa indexada ou direta (posiciona diretamente no registro correspondente ao índice- usando o método SEEK). Operações sobre Tabelas e/ou Arquivos (2/6) Inclusão (ou Inserção) Inserir um novo elemento (registro) ao conjunto. Alteração (ou Modificação) Operação que corresponde a necessidade de alterar uma informação presente em uma tabela, seja por atualização de informação (mudança de endereço, aumento de salário), seja por correção de uma informação errada. Operações sobre Tabelas e/ou Arquivos (3/6) Exclusão (ou Remoção) A remoção de um elemento de uma tabela quase sempre implica refazer toda a tabela- deslocando os registros subseqüentes para ocuparem a posição liberada pelo registro excluído. Essa operação denomina-se REMOÇÃO FÍSICA. Outra maneira de se remover registros da tabela é definir um critério que os elimine. Uma forma é "marcar" o elemento a ser removido, realizando uma operação denominada REMOÇÃO LÓGICA. A marca pode ser feita, por exemplo, alterando o conteúdo de um campo extra no registro com o caracter "*". Operações sobre Tabelas e/ou Arquivos (4/6) Seleção (ou Separação) Pode ocorrer a necessidade de se montar uma tabela com apenas alguns elementos de uma outra tabela. Esta separação pode objetivar um conjunto menor de informações relevantes em uma determinada situação. Por exemplo, montar uma tabela com os funcionários que recebem salários acima de R$ 5.000,00. Esta operação é também conhecida como filtro. Operações sobre Tabelas e/ou Arquivos (5/6) Junção (ou Justaposição) Ocorre quando deseja-se inserir os registros de uma tabela ao final de outra tabela, criando então uma terceira tabela. Isto pode ocorrer, por exemplo, pela fusão de dois ou mais departamentos em um único, sendo necessário unificar as tabelas de funcionários de cada departamento em uma única tabela. Operações sobre Tabelas e/ou Arquivos (6/6) Intercalação Quando ocorre a junção de duas ou mais tabelas ordenadas segundo o mesmo campo, é desejável que a nova tabela resultante também esteja ordenada. Para isto, nem sempre podemos simplesmente justapor as tabelas, sendo necessário intercalar os registros das tabelas segundo os valores presentes no campo pelo qual as tabelas estão ordenadas. Formas de Acesso em um Arquivo (1/3) Os arquivos criados em meio magnético poderão ser acessados para leitura e/ou escrita na forma seqüencial, direta ou indexada. Arquivo de Acesso Seqüencial O acesso seqüencial ocorre quando o processo de gravação e leitura é feito de forma contínua, um após o outro. Desta forma, para se gravar um novo registro é necessário percorrer todo o arquivo a partir do primeiro registro, registro a registro, até localizar a primeira posição vazia após o último registro. O processo de leitura também ocorre de forma seqüencial. Se o registro a ser lido é o último, primeiro será necessário ler todos os registros que o antecedem. Formas de Acesso em um Arquivo (2/3) Arquivo de Acesso Direto O acesso direto (conhecido também por acesso randômico) ocorre através de um campo chave previamente definido. Desta forma, passa-se a possuir um vínculo existente entre um dos campos do registro e sua posição de armazenamento, através da chave. Assim sendo, o acesso a um registro tanto para leitura como para escrita poderá ser feito de forma instantânea, utilizando a instrução FSEEK. Isto implica no fato de que os registros de um arquivo direto possuem um lugar (chave) previamente "reservado" para serem armazenados. O termo chave é utilizado para estabelecer o campo de um registro que será utilizado no processo de localização do registro no arquivo. No acesso direto, será possível acessar um determinado registro diretamente, sem se preocupar com os registros que o antecedem. Formas de Acesso em um Arquivo (3/3) Arquivo de Acesso Indexado O acesso indexado ocorre quando se acessa de forma direta um arquivo seqüencial. Na sua maioria, todo arquivo criado armazena os registros de forma seqüencial. A forma seqüencial de acesso torna-se inconveniente, pois à medida que o arquivo aumenta de tamanho, aumenta também o tempo de acesso ao mesmo. Para se trabalhar com esta técnica, basta criar um arquivo direto que será o índice de consulta do arquivo seqüencial, passando este a ser o arquivo indexado. Assim sendo, existirão dois arquivos: o arquivo índice (arquivo direto) e o arquivo indexado (arquivo seqüencial). Os acessos serão feitos como em um livro, primeiro se consulta o arquivo índice, o qual possui a chave de pesquisa, no caso seria o número da página, depois basta se posicionar de forma direta na página identificada no índice, ou seja, no registro do arquivo indexado. Glossário - Parte II • Chave: campo ou combinação de campos utilizados para alguma operação de pesquisa. • Chave Primária: campo que apresenta um valor diferente para cada registro da tabela (código) • Chave Secundária: chave auxiliar de pesquisa devido a probabilidade de repetição de valores (nome) • Pesquisa: localizar um determinado registro utilizando como argumento de pesquisa o campo chave. Operações sobre Arquivos Texto (1/2) fgets(), lendo “uma string de caracteres” de um arquivo aberto no modo leitura. char *fgets(char *str, int length, FILE *fp); a função fgets() retorna EOF quando o final do arquivo for alcançado. fputs(), escrevendo “uma string de caracteres” em um arquivo aberto no modo escrita. int fputs(const char *str, FILE *fp); onde: fp é um ponteiro de arquivo do tipo FILE devolvido por fopen(); str é uma variável do tipo cadeia de caracteres (char *str) length quantidade de caracteres que serão lidos Usando fopen(), fputs() e fclose(): Esta função cria um arquivo texto denominado “saida.txt” gravando as cadeias de caracteres digitadas pelo usuário final. void GravarTxt(void) { FILE *fp; fp = fopen("saida.txt", "w"); // cria o arquivo “saida.txt” char str[80]; clrscr(); printf("Digite um texto mensagem, (FIM) para encerrar:\n"); fflush(stdin); // limpa o buffer do teclado while (1) { gets(str); if (strcmp(str, "FIM") == 0) break; strcat(str, "\n"); // acrescenta o fim de linha fputs(str, fp); // grava uma linha no arquivo texto } fclose(fp); } Usando fopen(), fgets(), feof() e fclose(): Esta função lê um arquivo texto denominado “saida.txt” exibindo na tela as cadeias com no máximo 80 caracteres lidas. void LerTxt(void) { FILE *fp; fp = fopen("saida.txt", "r"); char str[80]; clrscr(); printf("Texto lido......\n"); // lê uma linha de 80 caracteres do arquivo texto fgets(str, 80, fp); while (!feof(fp)) { printf("%s", str); fgets(str, 80, fp); } fclose(fp); gotoxy(01, 24); printf("Pressione [algo] para prosseguir."); getch(); } Operações sobre Arquivos Texto (2/2) fgetc() ou getc(), lendo “um” caractere de um arquivo aberto no modo leitura. int fgetc(FILE *fp); a função fgetc() retorna EOF quando o final do arquivo for alcançado. fputc() ou putc(), escrevendo “um” caractere em um arquivo aberto no modo escrita. int fputc(int ch, FILE *fp); onde: fp é um ponteiro de arquivo do tipo FILE devolvido por fopen(). Por razões históricas estas funções retornam um inteiro (que automaticamente é convertido para sua representação em caractere). Usando fopen(), fgetc(), feof() e fclose(): Esta função lê os caracteres do arquivo indicado pela variável “nomeArq” enquanto o final de arquivo não for atingido. void LerTxt(char nomeArq[80]) { clrscr(); FILE *fp; fp = fopen(nomeArq, "r"); if (fp == NULL) printf("O arquivo \"%s\" não pode ser aberto.", nomeArq); else { char ch; clrscr(); ch = fgetc(fp); // lê um caractere do arquivo texto while (!feof(fp)) { putchar(ch); ch = fgetc(fp); // lê um caractere do arquivo texto } fclose(fp); } gotoxy(01, 24); printf("Pressione [algo] para prosseguir."); getch(); } Finalidade dos Sistemas de Gerenciamento BD (1/5) Eliminar / minimizar problemas de um sistema de processamento de arquivos, por exemplo, arquivos criados por aplicações desenvolvidas em Linguagem C: FILE *fp; Sistemas de processamento de arquivos = registros permanentes são armazenados em vários arquivos e diversos programas de aplicação são escritos para extrair e gravar registros nos arquivos apropriados. a) Redundância: vários arquivos com as mesmas informações. b) Inconsistência: dados que são alterados em um arquivo e não o são em outros. Finalidade dos Sistemas de Gerenciamento BD (2/5) c) Dificuldades de Acesso: são necessários programas específicos para recuperar informações (é necessário conhecer a estrutura, ou a relação de campos, do registro). d) Isolamento dos Dados: os dados estão dispersos em vários arquivos, e estes arquivos podem apresentar diferentes formatos (dificuldade em desenvolver novas aplicações). e) Problemas de Integridade: programas devem garantir a manutenção de restrições de integridade (consistência). Restrições de Integridade = regras que estabelecem quando uma base de dados está correta. Finalidade dos Sistemas de Gerenciamento BD (3/5) f) Problemas de atomicidade: um sistema de processamento esta sujeito a falhas. Mecanismos simples como cópias não são suficientes. Após falhas o banco de dados deve ser recuperado rapidamente, em seu último estado consistente. Por exemplo, um programa deve transferir R$ 50,00 da conta A para uma conta B. Se ocorrer falha no sistema durante a execução, é possível que os R$ 50,00 sejam debitados da conta A sem serem creditados na conta B, criando um estado inconsistente no banco de dados. Operação Atômica: deve ocorrer por completo ou não deve ocorrer. Finalidade dos Sistemas de Gerenciamento BD (4/5) g) Problemas de Concorrência: acesso por múltiplos usuários. Os programas devem implementar controle de acesso concorrente (programação complexa). Dois clientes retiram fundos da conta A “simultaneamente” conta A Saldo Inicial = R$ 500,00 saque de R$ 100,00 saque de R$ 50,00 Qual será o Saldo Final ? R$ 400,00 - R$ 450,00 - R$ 350,00 Finalidade dos Sistemas de Gerenciamento BD (5/5) h) Problemas de Segurança: nem todos os usuários de banco de dados estão autorizados ao acesso a todos os dados. i) Outros: integridade de armazenamento no caso de quedas do sistema. O principal objetivo de um “Sistema de Gerenciamento de Banco de Dados” é proporcionar um ambiente, conveniente e eficiente, para a recuperação e armazenamento das informações do banco de dados. E/S – Exemplos (01) /* Cada caracter digitado ser gravado no arquivo */ c = getchar(); while (!feof(stdin)) { // Quando digitado CTRL+Z fputc(c, pa); c = getchar(); } rewind(pa); /* volta ao inicio do arquivo */ printf("\nTerminei de escrever, agora vou ler.\n"); c = fgetc(pa); while (!feof(pa)) { putchar(c); c = fgetc(pa); } fclose(pa); getchar(); /* Espera o usuario digitar alguma coisa */ } E/S – Exemplos (02) /*Programa descrição: Escreve e em seguida lê cadeias de um arquivo. */ #include<stdio.h> #include<stdlib.h> #include<string.h> #define MAX 80 int main (void ) { char linha[MAX]; FILE *pa; char *nome = "texto.txt"; /* Abre o arquivo para leitura e escrita */ if (( pa = fopen(nome, "w+")) == NULL) { printf("\n\nNao foi possivel abrir o arquivo.\n"); exit(1); } E/S – Exemplos (02) /* Cada linha digitada sera gravada no arquivo */ gets(linha); while (!feof(stdin)) { strcat(linha, "\n"); fputs(linha, pa); gets(linha); } rewind(pa); /* volta ao inicio do arquivo */ printf("\nTerminei de escrever, agora vou ler.\n"); fgets(linha, MAX, pa); while (!feof(pa)) { printf("%s",linha); fgets(linha, MAX, pa); } fclose(pa); getchar(); } E/S – Exemplos (03) //O programa abaixo ilustra como podemos escrever e ler estruturas. #include<stdio.h> #include<stdlib.h> #define MAX 4 int main () { FILE *pa; char nome[40]; struct pessoa { char nome[40]; int ano; } turma [MAX], back[MAX]; int i; for (i=0; i<MAX; i++) { puts("Nome ? "); gets(turma[i].nome); fflush (stdin); puts("Ano ? "); scanf("%d", &turma[i].ano); fflush (stdin); } E/S – Exemplos (03) puts ("\nImprimindo\n"); for (i=0; i<MAX; i++) { printf("Nome = %s\n", turma[i].nome); printf("Ano = %d\n\n", turma[i].ano); } puts("\nGravando\n"); puts("Qual o nome do arquivo?"); gets(nome); if (( pa = fopen(nome, "w+b")) == NULL ) { puts("Arquivo nao pode ser aberto"); exit(1); } for (i=0; i<MAX; i++) { if (fwrite( &turma[i], sizeof (struct pessoa), 1, pa) != 1) puts("Erro na escrita."); } E/S – Exemplos (03) rewind(pa); for (i=0; i<MAX; i++) { if (fread( &back[i], sizeof (struct pessoa), 1, pa) != 1) { puts("Erro na escrita."); if (feof(pa)) break; puts("Erro na leitura."); } } puts("Imprimindo o vetor lido."); for (i=0; i<MAX; i++) { printf("Nome = %s\n", back[i].nome); printf("Ano = %d\n\n", back[i].ano); } exit(0); } Referência • C, Completo e Total – Herbert Schildt – E/S com Arquivo, páginas 219..252.