Registos em Ficheiros - Estruturas Pedro Barahona DI/FCT/UNL Introdução aos Computadores e à Programação 2º Semestre 2009/2010 4 Maio 2011 Registos em Ficheiros - Estruturas 1
Leitura de Ficheiros Texto Muitos ficheiros apresentam informação na base de texto, pelo que a sua leitura pode ser feita quer através do formato caracter (%) quer através do formato string (%s). O formato caracter lê todos os caracteres do ficheiro, nomeadamente os caracteres de controle como os tabs e mudanças de linha. A função ler_texto lê o texto do ficheiro file_in e retorna-o na string txt: function txt = ler_chars(fname); [fid,msg] = fopen(fname,"r"); txt= ""; while!feof(fid) [wrd,count] = fscanf(fid,"%c","c"); txt = [txt,wrd]; endwhile; endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 2
Leitura de Ficheiros Texto O formato string permite separar todas as palavras, nomeadamente criando uma matriz cujas linhas correspondem às palavras do texto (completadas por espaços). De notar que os separadores de palavras correspondem aos espaços e aos caracteres de controle, não incluindo os caracteres de pontuação habitualmente considerados numa linguagem natural (pontos, vírgulas,...). Notar ainda que os caracteres de mudança de linha não são retornados A função ler_palavras lê o texto do ficheiro fname e retorna-o na matriz txt: function txt = ler_palavras(fname); [fid,msg] = fopen(fname,"r"); txt= ""; while!feof(fid) [wrd,count] = fscanf(fid,"%s","c"); txt = [txt;wrd]; endwhile; fclose(fid); endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 3
Leitura de Ficheiros Texto Finalmente, os ficheiros podem ser lidos linha a linha, com a instrução fgetl. Esta instrução lê uma string de um ficheiro até ao caracter mudança de linha ( \n ), não se detendo em espaços ou outros caracteres de controle como os tabs. De notar o tratamento de linhas brancas : linhas apenas com espaços são retornadas, embora as linhas sem caracteres o sejam. A função ler_linhas lê o texto do ficheiro fname e retorna-o na matriz txt, linha a linha: function txt = ler_linhas(fname); [fid,msg] = fopen(fname,"r"); txt= ""; while!feof(fid) line = fgetl(fid); txt = [txt;line]; endwhile; endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 4
Registos em Ficheiros Muita informação alfanumérica está registada em ficheiros, na forma de registos. Por exemplo, numa base de dados da empresa, são mantidos ficheiros com informação sobre os empregados da empresa. Muitas aplicações (de gestão) consistem em ler ficheiros e criar outros com a informação devidamente processada. Por exemplo: ler um ficheiro de empregados e escrever outro, apenas com os empregados com vencimento superior a 1000. cod nome vencimento data 610 Paulo Fernandes Lopes 2341.36 15/04/1996 825 Pedro Vieira 989.24 25/06/1999 316 Marta Costa Martins 1389.17 05/01/1992 34 Rui Vasco Pereira 5310.32 15/04/1996 723 Jorge Barata 767.26 03/09/2002 4 Maio 2011 Registos em Ficheiros - Estruturas 5
Estruturas Embora Vectores e Matrizes sejam muito úteis quando os dados são todos do mesmo tipo (no Octave, de qualquer tipo numérico), nestes casos, a informação que se pretende agrupar com um só identificador não é do mesmo tipo. Neste caso, a informação referente a cada empregado da empresa deve conter os seguintes itens: Um código (tipicamente um número inteiro) Um nome (uma cadeia de caracteres) Um vencimento (um decimal) Uma data de entrada (uma cadeia, ou 3 campos numéricos, para o dia, mês e ano) cod nome venc data 610 Paulo Fernandes Lopes 2341.36 15/04/1996 4 Maio 2011 Registos em Ficheiros - Estruturas 6
Estruturas As várias linguagens de programação permitem o agrupamento destes dados heterogéneos, com um mesmo identificador de uma forma variada (records no Pascal, Struct em C,...) O Octave adopta uma designação semelhante à do C, denominando estes agrupamentos como estruturas. Consideremos pois o caso do empregado abaixo, em que gostaríamos de agregar toda a informação numa única variável, do tipo estrutura, que denotaremos como emp. cod nome venc data 610 Paulo Fernandes Lopes 2341.36 15/04/1996 4 Maio 2011 Registos em Ficheiros - Estruturas 7
Estruturas Uma vez definidos os nomes dos campos da estrutura, podemos atribuir-lhe os valores pretendidos. O acesso a um campo da estrutura é feito fazendo suceder ao nome da estrutura o nome do campo pretendido, separado por um ponto (. ). Por exemplo, a atribuição dos 4 valores dos campos pode ser feita pelas seguintes atribuições: emp.cod = 610; emp.nome = Paulo Fernandes Lopes ; emp.venc = 2341.36; emp.data= 15/04/1996 ; emp = cod nome venc data 610 Paulo Fernandes Lopes 2341.36 15/04/1996 4 Maio 2011 Registos em Ficheiros - Estruturas 8
Estruturas Uma vez agrupados os vários items de informação numa só variável do tipo estrutura, podemos referir alguns campos depois de verificar outros. Por exemplo, dados vários empregados com o tipo referido, indicar qual o nome dos que ganham mais de 1000 euros. Na sintaxe do Octave, tal poderia ser feito através da instrução condicional if emp.venc > 1000 then disp(emp.nome) endif No entanto este tipo de processamento só é verdadeiramente útil se tivermos a possibilidade de aceder a todos os empregados de uma forma genérica. 4 Maio 2011 Registos em Ficheiros - Estruturas 9
Por exemplo, se tivessemos uma tabela com várias linhas, com códigos na primeira coluna e vencimentos na 2ª coluna, poderíamos apresentar os códigos dos empregados com vencimento superior a 1000 euros através da seguinte instrução iterativa: for i = 1:n if tabela(i,2) > 1000 disp(tabela(i,1)) endif endfor; Por analogia, o que é necessário é poder aceder a uma sequência de (1 a n) estruturas do tipo da do empregado. Tabelas 1 2 1 610 2341.36 2 825 989.24 3 316 1389.17 4 34 5310.32 5 723 767.26......... Na maioria das linguagens de programação, essa analogia é imediata, já que se podem especificar vectores de estruturas. Tal é o caso do Octave, mas apenas a partir da versão 3.X (as anteriores usavam o conceito de lista, entretanto descontinuado). 4 Maio 2011 Registos em Ficheiros - Estruturas 10
Nestas linguagens, podemos representar o conjunto de empregados através de um vector, emps, em que cada elemento é uma estrutura (de empregado) com os campos definidos como anteriormente. Vectores de Estruturas ind cod nome vencimento data 1 610 Paulo Fernandes Lopes 2341.36 15/04/1996 2 825 Pedro Vieira 989.24 25/06/1999 3 316 Marta Costa Martins 1389.17 05/01/1992 4 34 Rui Vasco Pereira 5310.32 15/04/1996 5 723 Jorge Barata 767.26 03/09/2002 Agora, para obter os códigos dos empregados com vencimento superior a 1000 euros bastará usar uma seguinte instrução iterativa, análoga à anterior for i = 1:n if emps(i).vencimento > 1000 disp(emps(i).nome) endif endfor; for i = 1:n if tabela(i,2) > 1000 disp(tabela(i,1)) endif endfor; 4 Maio 2011 Registos em Ficheiros - Estruturas 11
Registos em Ficheiros Sendo os registo geralmente guardados em ficheiros texto, há que saber ler esta informação, para as estruturas adequadas.. Apesar de, tipicamente, existirem (pelo menos) duas formas de armazenamento dessas sequências, a leitura de ficheiros e empregados pode ser feito de uma forma genérica. Na função genérica abaixo, o parâmetro mode é passado para a função ler_emp/ 2, que depende do formato de armazenamento utilizado. function Emps = ler_emps(fname, mode); [fid, msg] = fopen(fname, "r"); i = 0; [emp, ok] = ler_emp(fid, mode); while ok i = i+1; Emps(i) = emp; [emp, ok] = ler_emp(fid, mode); endwhile; fclose(fid); endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 12
Registos em Ficheiros Para ler cada registo, há que saber a forma como são codificadas essas sequências. Tipicamente, existem duas formas de armazenamento dessas sequências: Comprimento Fixo: As sequências têm sempre o mesmo número de caracteres (sendo usados espaços se necessário); Comprimento Variável: As sequências têm o número de caracteres necessários, sendo necessários caracteres separadores, tipicamente tabs (horizontais). cod nome vencimento data 610 Paulo Fernandes Lopes 2341.36 15/04/1996 825 Pedro Vieira 989.24 25/06/1999 316 Marta Costa Martins 1389.17 05/01/1992 34 Rui Vasco Pereira 5310.32 15/04/1996 723 Jorge Barata 767.26 03/09/2002 4 Maio 2011 Registos em Ficheiros - Estruturas 13
Registos em Ficheiros Por exemplo, o nome Pedro Vieira, no ficheiro abaixo, pode ser codificado Comprimento Fixo: Com 5+1+6 = 12 caracteres (incluindo o espaço), mais 13 espaços, para permitir sequências de comprimento 25, que podem armazenar nomes com até 25 caracteres (incluindo espaços); Comprimento Variável: Apenas com os 12 caracteres necessários, sendo separado do vencimento por um tab horizontal ( \t ). cod nome vencimento data 610 Paulo Fernandes Lopes 2341.36 15/04/1996 825 Pedro Vieira 989.24 25/06/1999 316 Marta Costa Martins 1389.17 05/01/1992 34 Rui Vasco Pereira 5310.32 15/04/1996 723 Jorge Barata 767.26 03/09/2002 4 Maio 2011 Registos em Ficheiros - Estruturas 14
Registos em Ficheiros Em Octave (e em C) os dois tipos de codificação requerem instruções de leitura padronizada (com templates) diferentes. Comprimento Fixo: Utiliza-se um template %nc em que n é o número de caracteres a ler; Comprimento Variável: Utiliza-se um template %s ; Neste último caso, levanta-se um problema: o template %s, não lê espaços nem caracteres brancos. Assim, o nome Pedro Vieira seria lido não como 1, mas sim como 2 sequências, mas em geral, o número de nomes (próprios e apelidos) não é conhecido. Isto pode ser evitado com o uso de espaços especiais ( non break spaces - ASCII 160), que são considerados como quaisquer outros caracteres, mas impressos como espaços. Mas esta solução tem outros inconvenientes, nomeadamente obrigando a um tratamento especial na separação de palavras. 4 Maio 2011 Registos em Ficheiros - Estruturas 15
Leitura de Registos em Ficheiros Comprimento Variável: Neste caso, aconselha-se a leitura de um registo de cada vez (através da instrução fgetl/1) e obter os vários campos das linhas assim obtidas. A separação dos vários campos pode fazer-se pela instrução split/2, assumindose o caracter tab ( \t ) como separador dos campos. Obtem-se assim uma matriz de texto, em que cada campo aparece numa linha própria. Comprimento Fixo: Agora, todos os registos do ficheiro podem ser lidos com instruções de leitura padronizada, fscanf, usando-se os padrões apropriados. A função de leitura de um registo é apresentada de seguida em ambos os formatos de codificação, sendo de registar a diferente forma de identificar o fim do ficheiro (e um registo vazio). 4 Maio 2011 Registos em Ficheiros - Estruturas 16
Leitura de Registos em Ficheiros A função abaixo prevê os dois métodos de leitura, distinguindo-os pelo valor do parâmetro modo: 1/variável e 2/ fixo. De notar booleano ok para sinalizar se uma estrutura emp é de facto lida (e retornada) A inicialização de emp, para o caso de a leitura não se fazer (esta inicialização é opcional, mas evita mensagens de aviso que o parâmetro de saída emp não existe). function [emp,ok] = ler_emp(fid, mode); emp = ""; if mode == 1 % nomes com comprimento variável... else % nomes com comprimento fixo... endif endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 17
No modo variável, a função Leitura de Registos em Ficheiros 1. lê uma linha de cada vez (caso já não exista, a função fgetl/1 retorna o valor -1); 2. converte-a numa matriz de caracteres L, partindo-a nos caracteres tab ( \t ) com a instrução split; 3. Atribui as linhsa da matriz aos respectivos campos, convertendo-as em números (instrução str2num) ou retirando os espaços fnais (instrução deblank).... if mode == 1 % nomes com comprimento variável line = fgetl(fid); ok = (line!= -1); if ok M = split(line,"\t"); emp.cod = str2num(m(1,:)); emp.nome = deblank(m(2,:)); emp.venc = str2num(m(3,:)); emp.data = deblank(m(4,:)); endif; endif;... 4 Maio 2011 Registos em Ficheiros - Estruturas 18
Leitura de Registos em Ficheiros No modo fixo, a função lê os quatro campos de uma só instrução fscanf. A separação dos campos é feita por tabs, excepto entre o nome e o vencimento. Neste caso, a separação é implícita, ao serem lidos exactamente 25 caracteres para o nome. De notar, que o booleano ok é verdadeiro sse os 4 campos forem lidos. function [emp,ok] = ler_emp(fid, mode); emp = ""; if mode == 1 % nomes com comprimento variável... else % nomes com comprimento fixo [emp.cod, emp.nome, emp.venc, emp.data, count] = fscanf(fid,"%i%25c%f%s","c"); ok = (count == 4); endif endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 19
Eis a função completa: Leitura de Registos em Ficheiros function [emp,ok] = ler_emp(fid, mode); emp = ""; if mode == 1 % nomes com comprimento variável line = fgetl(fid); ok = (line!= -1); if ok M = split(line,"\t"); emp.cod = str2num(m(1,:)); emp.nome = deblank(m(2,:)); emp.venc = str2num(m(3,:)); emp.data = deblank(m(4,:)); endif; else % nomes com comprimento fixo [emp.cod, emp.nome, emp.venc, emp.data, count] = fscanf(fid,"%i%25c%f%s","c"); ok = (count == 4); endif endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 20
Comprimento fixo: Escrita de Registos em Ficheiros A escrita de ficheiros depende igualmente do formato utilizado para as strings. Em comprimento fixo, um registo pode ser escrito como fprintf(fid, "%3i", cod); fprintf(fid, "%-25s", nome); fprintf(fid, "%7.2f", venc); fprintf(fid, "%10s\n", data); ou numa só instrução, como anteriormente. Notar ainda que 1. O sinal (em %-25s) justifica, à esquerda, o campo nome. 2. Após o último campo deve ser escrito um caracter ( \n ), para mudança de linha 610Paulo Fernandes Lopes 2341.3615/04/1996 4 Maio 2011 Registos em Ficheiros - Estruturas 21
Comprimento variável: Escrita de Registos em Ficheiros Neste caso o registo de um empregado pode ser escrito como fprintf(fid, "%i\t", cod); fprintf(fid, "%s\t", nome); fprintf(fid, %7.2f\t", venc); fprintf(fid, "%s\n", data); ou numa só instrução, como anteriormente. Notar agora que 1. Após cada campo, deve ser escrito o tab ( \t ) de separação, excepto no último campo, após o que se escreve o caracter ( \n ) 2. Alguns templates podem ser fixos, (por exemplo, "%7.2f ) para evitar as convenções por omissão do Octave. 610 Paulo Fernandes Lopes 2341.36 15/04/1996 4 Maio 2011 Registos em Ficheiros - Estruturas 22
Eis a função completa: Escrita de Registos em Ficheiros function [] = escreve_emps(emps,fname, mode); [fid, msg] = fopen(fname, "w"); if mode == 1 for i = 1:length(Emps) fprintf(fid, "%i\t", Emps(i).cod); fprintf(fid, "%s\t", Emps(i).nome); fprintf(fid, "%7.2f\t", Emps(i).venc); fprintf(fid, "%s\n", Emps(i).data); endfor; else for i = 1:length(Emps) fprintf(fid, %6i", Emps(i).cod); fprintf(fid, "%-25s", Emps(i).nome); fprintf(fid, %8.3f", Emps(i).venc); fprintf(fid, "%11s\n", Emps(i).data); endfor; endif; fclose(fid); endfunction; 4 Maio 2011 Registos em Ficheiros - Estruturas 23