Curso de Pascal
Arquivos
Você está em: MarMSX >> Cursos >> Pascal
A memória do computador é volátil, e todos os dados contidos nela é perdido após o desligamento do computador.
Se desejarmos manter os dados de uma aplicação vivos após o desligamento, como por exemplo, o resultado de um jogo ou os nomes de uma agenda telefônica, teremos que
salvá-los em disco.
Existem dois formatos de arquivos: o formato texto e o formato binário.
Em arquivos no formato texto, os códigos armazenados serão interpretados como valor de entrada na tabela ASCII, de maneira a formar um texto. Assim, por exemplo, o código 65 significa a letra 'A' maiúscula. Além disso, os arquivos no formato texto são divididos em linhas, onde um caracter com um código determinado indica o fim de uma linha.
Em arquivos binários, os códigos são a representação direta dos dados, em seu formato original. Por exemplo, um inteiro será armazenado em disco como é armazenado na memória, ou seja, com 2 bytes por número. Já o double, em seu formato de 8 bits.
Fica evidente que o programador deverá conhecer a disposição dos dados e seus respectivos tipos na hora de recuperar a informação no disco. Caso contrário, poderemos recuperar dados de um número double em um variável do tipo inteiro, por exemplo, causando inconsistência nos dados.
Veremos a seguir, todas as funções de Pascal, relativas à arquivos.
Identificador |
Tipo |
Descrição |
Append |
procedure |
Abre um arquivo texto já existente,
posicionando o ponteiro de arquivo no final do arquivo. |
Assign |
procedure |
Associa uma variável tipo arquivo com
arquivo em disco. |
BlockRead |
procedure |
Transferência de um arquivo sem tipo do
disco para a memória. |
BlockWrite |
procedure |
Transferência de um arquivo sem tipo da
memória para o disco. |
Chain |
procedure |
Carrega e executa um arquivo .CHN. |
Close |
procedure |
Grava informações do buffer e fecha um
arquivo externo. |
Eof |
function |
TRUE se o ponteiro do arquivo estiver no
fim do arquivo. |
EoLn |
function |
TRUE se o ponteiro de arquivo texto
estiver no fim da linha. |
Erase |
procedure |
Remove o arquivo do diretório do disco. |
Execute |
procedure |
Carrega e executa um arquivo tipo .COM. |
FilePos |
function |
Retorna a posição do ponteiro no arquivo
não texto. |
FileSize |
function |
Retorna o número de componentes de um
arquivo não texto. |
Flush |
procedure |
Força a saída dos dados contidos no
buffer para o disco, sem fechar o arquivo. |
Get |
procedure |
Entrada de dados do Pascal padrão - não
usada no Turbo. |
LongFilePos |
function |
Retorna a posição do ponteiro de um
arquivo maior que 32 K - não é válido para arquivos texto. |
LongFileSize |
function |
Retorna o número de componentes de um
arquivo não- texto, maior que 32 K. |
LongSeek |
procedure |
Posiciona o ponteiro de arquivo num dado
registro, em arquivos maiores que 32 K - não é válido para arquivos
texto. |
Put |
procedure |
Saída de dados do Pascal padrão - não
usada no Turbo. |
Read |
procedure |
Entrada de dados do teclado ou de um
arquivo externo de disco. |
ReadLn |
procedure |
Entrada de dados de um dispositivo ou
arquivo texto. |
Rename |
procedure |
Muda o nome do arquivo em disco. |
Reset |
procedure |
Abre o arquivo já existente em disco somente para leitura. |
Rewrite |
procedure |
Cria um novo arquivo no disco e abre somente para gravação. |
Seek |
procedure |
Posiciona ponteiro do arquivo no
componente de número var - não é válido para arquivos texto. |
SeekEof |
procedure |
Pula tabulação, espaço em branco e
marcas de fim de linha (CR/LF) antes do teste de fim de arquivo (Eof).
Retorna valor booleano. |
SeekEoln |
procedure |
Pula tabulação e espaços em branco
antes do teste de fim de linha (EoLn) e/ou fim de arquivo. Retorna valor
booleano. |
Truncate |
procedure |
Trunca um arquivo no registro indicado
pelo ponteiro de um arquivo. |
Write |
procedure |
Saída de dados para o dispositivo ou
arquivo em disco. |
WriteLn |
procedure |
Saída de dados para um dispositivo ou
arquivo de texto com uma seqüência de caracteres CR e LF após a última
variável. |
(Tabela retirada do livro Turbo Pascal - Guia do Usuário, STEVE WOOD, ed Mc Graw-Hill, pgs 59, 60 e 61).
Sintaxe básica
A sintaxe básica para ambos os formatos de arquivo é:
var t_var : file of tipo_de_dado / text;
begin
assign (t_var, nome_do_arquivo); { Associa varável a arquivo }
rewrite/reset/append (t_var); { Tipo de operação em disco }
read/write (t_var, var); { Lê ou grava dados da variável }
close (t_var); { Fecha arquivo aberto }
end.
O primeiro passo é criar uma variável do tipo arquivo "t_var", que definirá o tipo de dado que será utilizado na leitura e gravação de dados. Em seguida, associamos a variável t_var a um arquivo físico no disco (designado pelo nome). A partir daí, todos os comandos de acesso ao disco que mencionam a variável t_var estarão acessando ao arquivo associado.
Cursor de arquivo
Imaginando o arquivo em disco como um grande vetor de dados, o cursor é um índice que irá apontar para uma determinada posição desse vetor.
Para exemplificar, seja um arquivo no formato binário, composto de dados em valores inteiros (2 bytes ou 16 bits):
0 | 1 | 2 | 3 | 4 | 5 - Índice do vetor.
------+-------+-------+-------+-------+-------
00 00 | 01 00 | 02 00 | 0A 00 | 0B 00 | 0C 00 - Dados em hexadecimal (arquivo).
Ao comando de reset(), o cursor irá apontar para o início do arquivo. Assim, cursor := 0 e sua posição está assinalada em vermelho no diagrama abaixo.
0 | 1 | 2 | 3 | 4 | 5 - Índice do vetor.
------+-------+-------+-------+-------+-------
00 00 | 01 00 | 02 00 | 0A 00 | 0B 00 | 0C 00 - Dados em hexadecimal (arquivo).
  Quando uma leitura no arquivo é feita através do comando read(), o conteúdo apontado pelo cursor (assinalado em azul) é copiado para a variável i. Nessa leitura, i recebe o valor igual 0.
O código básico de leitura de dados no formato inteiro (integer) é esse:
var t_var : file of integer;
i : integer;
...
read(t_var, i);
...
Imediatamente após a leitura em disco, o cursor sobe uma posição. Assim, cursor := 1.
0 | 1 | 2 | 3 | 4 | 5 - Índice do vetor.
------+-------+-------+-------+-------+-------
00 00 | 01 00 | 02 00 | 0A 00 | 0B 00 | 0C 00 - Dados em hexadecimal (arquivo).
Na próxima leitura (read), i receberá o valor 1. O cursor sobe novamente uma posição (sempre quando o read/write é acionado).
0 | 1 | 2 | 3 | 4 | 5 - Índice do vetor.
------+-------+-------+-------+-------+-------
00 00 | 01 00 | 02 00 | 0A 00 | 0B 00 | 0C 00 - Dados em hexadecimal (arquivo).
É importante lembrar que a cada leitura, o conteúdo da variável i é modificado e a informação antiga é perdida. Assim, você deve imprimir o valor na tela ou armazená-los em algum lugar.
Quando o cursor atinge a posição 6 desse exemplo, chega ao fim do arquivo. Ao tentar ler essa posição, o programa irá gerar um erro. Dessa forma, o comando EOF verifica se o cursor atingiu o fim do arquivo, antes de fazer a leitura de dados.
A seguir, serão apresentados os códigos completos para a geração do arquivo binário utilizado, bem como a recuperação dos dados desse arquivo.
Código para gravar dados em um arquivo |
Código para ler dados de um arquivo |
var t_var : file of integer;
i : integer;
v : array[0..5] of integer = ( $0, $1, $2, $A, $B, $C );
begin
assign (t_var, 'dados.dat');
rewrite (t_var);
for i:= 0 to 5 do
begin
write(t_var, v[i]);
end;
close (t_var);
end.
|
var t_var : file of integer;
x, i : integer;
begin
assign (t_var, 'dados.dat');
reset (t_var);
for x:= 0 to 5 do
begin
read(t_var, i);
writeln(i);
end;
close (t_var);
end.
|
Saída para o arquivo de leitura:
0
1
2
10
11
12
O comando SEEK
É possível posicionar diretamente o cursor para ler uma posição desejada do arquivo, em vez de percorrer todo o arquivo. O comando "seek" faz isso. Basta indicar o valor da posição do cursor, que varia de 0 a N-1.
O programa a seguir mostra como ler diretamente a posição 4 do arquivo utilizado até aqui como exemplo.
var t_var : file of integer;
i : integer;
begin
assign (t_var, 'dados.dat');
reset (t_var);
seek (t_var, 4);
read(t_var, i);
writeln('Valor da posicao 4: ', i);
close (t_var);
end.
Saída:
Valor da posicao 4: 11
Obs: O comando reset é necessário uma vez, para sinalizar que o arquivo será utilizado como leitura. Novos comandos seek podem ser acrescentados, seguidos do comando read para a leitura de outras posições do arquivo.
Arquivos no formato texto
Há duas maneiras de ler ou gravar arquivos de texto:
- Caractere a caractere
- Linha a linha
A operação de acesso a disco é extremamente demorada, se comparada à velocidade de acesso à memória principal do computador. Assim, quanto menos operações de entrada e saída fizermos para uma mesma quantidade de dados, melhor. Por esse ângulo, percebemos que é muito mais vantajoso levar/trazer um bloco inteiro de dados para a o disco, do que de caractere a caractere. A opção 2 é a melhor.
No primeiro exemplo, vamos escrever um texto linha a linha em um arquivo. Iremos utilizar a sintaxe básica, com pequenas adaptações.
var arq_texto : text;
linha : string[255];
begin
assign(arq_texto, 'texto.txt');
rewrite(arq_texto);
linha := 'O MSX foi trazido para o Brasil em 1985.';
writeln(arq_texto, linha);
linha := 'Foi fabricado pela Gradiente e Sharp.';
writeln(arq_texto, linha);
close(arq_texto);
end.
Agora, vamos abrir o texto:
var arq_texto : text;
linha : string[255];
begin
assign(arq_texto, 'texto.txt');
reset(arq_texto);
while (not eof(arq_texto)) do
begin
readln(arq_texto, linha);
writeln(linha);
end;
close(arq_texto);
end.
Saída:
O MSX foi trazido para o Brasil em 1985.
Foi fabricado pela Gradiente e Sharp.
No exemplo a seguir, o programa escreve caractere a caractere em disco e depois abre o arquivo para leitura:
program Ilustra_Tipo_Text;
const RETURN = #13; {retorno do carro de impressao; retorno do cursor}
LINE_FEED = #10; {avanco do papel; mudanca de linha}
var carac : Char;
arq_texto : text;
str_leitura : String[255];
begin
ClrScr;
{ Gravação }
WriteLn('Digite um texto. Pressione para finalizar.');
WriteLn;
Assign(arq_texto,'TEXTO.DAT');
Rewrite(arq_texto);
repeat
read(Kbd,carac);
if (carac <> ^Z) then
Write(carac);
if (carac = RETURN) then { Apague este comando se seu sistema pular }
Write(LINE_FEED); { duas linhas ao ecoar o texto na tela. }
Write(arq_texto,carac);
until(carac = ^Z);
Close(arq_texto);
{ Leitura }
WriteLn; WriteLn;
Write('Pressione qualquer tecla para rever o texto. ==> ');
Read(Kbd,carac); WriteLn;
Reset(arq_texto);
WriteLn;
While (not Eof(arq_texto)) do
begin
ReadLn(arq_texto,str_leitura);
WriteLn(str_leitura);
end;
Close(arq_texto);
end.
(Retirado do livro Turbo Pascal - Guia do Usuário, STEVE WOOD, ed Mc Graw-Hill, pg 62 e 63)
Arquivos no formato binário
Há quatro maneiras de ler ou gravar arquivos de texto:
- Byte a Byte
- Dado a dado (definido pelo tipo de dado)
- Bloco de bytes (buffer)
- Bloco de dados
Conforme dito na seção de arquivos de texto, a operação de acesso a disco é extremamente demorada. Assim, optamos sempre por ler um bloco de informações.
No primeiro exemplo, veremos um programa que utilizará um bloco de bytes (opção 3) para ler arquivos binários e trazer seus dados à tela.
type buf = array[1..1023] of byte;
var arq_buff : file of buf;
buffer : buf;
x : integer;
begin
Assign(arq_buff,'agenda.dat');
Reset(arq_buff);
while (not Eof(arq_buff)) do
begin
writeln;
Read(arq_buff, buffer); { Lê dados do disco no vetor }
for x:=0 to 1023 do
write(buffer[x],' '); { Imprime dados na tela }
end;
close(arq_buff);
end.
Obs: A diferença entre o arquivo de texto e o arquivo binário está no tipo de variável associada (assign) com o arquivo. Aqui, a sintaxe é:
var nome : file of tipo_de_dado;
Quando o programa lê dados do disco, armazena exatamente 1024 bytes no vetor do tipo "buf". O cursor do arquivo (posição na área de dados) inicialmente está na posição 0. Após ler 1024 bytes, irá para a posição 1024 no arquivo. A leitura de dados é sequencial e limitada ao tamanho do buffer. Portanto, se o arquivo tiver tamanho igual a 2000 bytes, terá que fazer 2 leituras no disco com copia no buffer para ler todo o arquivo, no exemplo acima.
No segundo exemplo, será utilizada a leitura/gravação de dado a dado (opção 2). Para isso, vamos utilizar um tipo de dado composto no programa que irá gravar informações em disco.
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
var ficha_arq : file of ficha;
ficha_1 : ficha;
begin
assign(ficha_arq, 'ficha.dat');
rewrite(ficha_arq);
ficha_1.nome := 'Kleber';
ficha_1.idade := 20;
ficha_1.peso := 65.0;
write(ficha_arq, ficha_1);
close(ficha_arq);
end.
Se abrirmos o arquivo 'ficha.dat' em um editor hexadecimal, temos:
End. | Dados em Hexadecimal | ASCII
-----+--------------------------------------------------+--------------------
0000 | 06 4B 6C 65 62 65 72 00 00 00 00 00 00 00 00 00 | .Kleber.........
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0020 | 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 | ................
0030 | 00 00 00 00 00 40 50 40 | .............@P@
Separando os dados, temos:
End. | Dados em Hexadecimal | ASCII
-----+--------------------------------------------------+--------------------
0000 | 06 4B 6C 65 62 65 72 00 00 00 00 00 00 00 00 00 | .Kleber.........
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0020 | 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 | ................
0030 | 00 00 00 00 00 40 50 40 | .............@P@
A string de tamanho igual a 40 é assinalada em verde (o primeiro byte é o comprimento da sring armazenada).
A variável inteira (tem dois bytes) está assinalada em azul. Note que o valor decimal 20 em hexadecimal vale 14.
A variável real (tem oito bytes - no Free Pascal para PC, onde foi testado) está assinalada em vermelho.
O programa a seguir recupera os dados do arquivo "ficha.dat" e os imprime na tela.
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
var ficha_arq : file of ficha;
ficha_1 : ficha;
begin
assign(ficha_arq, 'ficha.dat');
reset(ficha_arq);
read(ficha_arq, ficha_1);
writeln('Nome: ', ficha_1.nome);
writeln('Idade: ', ficha_1.idade);
writeln('Peso: ', ficha_1.peso);
close(ficha_arq);
end.
Saída:
Nome: Kleber
Idade: 20
Peso: 65.0
Agora, será apresentado um programa que lê três fichas em um vetor e depois salva em disco.
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
var ficha_arq : file of ficha;
fichario : array[1..3] of ficha;
i : integer;
procedure preenche_ficha(pos : integer);
begin
writeln('Ficha ', pos);
write('Nome: ');
readln(fichario[pos].nome); { No MSX utilizar read(kbd, fichario[pos].nome) }
write('Idade: ');
readln(fichario[pos].idade);
write('Peso: ');
readln(fichario[pos].peso);
end;
procedure salva_arquivo;
var i : integer;
begin
assign(ficha_arq, 'ficha_2.dat');
rewrite(ficha_arq);
for i:=1 to 3 do
begin
write(ficha_arq, fichario[i]);
end;
close(ficha_arq);
end;
begin
for i:=1 to 3 do
begin
preenche_ficha(i);
end;
salva_arquivo;
end.
Saída (dados que eu coloquei):
Ficha 1
Nome: Beatriz
Idade: 26
Peso: 58
Ficha 2
Nome: Carla
Idade: 18
Peso: 55
Ficha 3
Nome: Fabiana
Idade: 22
Peso: 56
Ao abrirmos o arquivo "ficha_2.dat" em um editor hexadecimal, o resultado é o seguinte:
0000:0000 | 07 42 65 61 74 72 69 7A 00 00 00 00 00 00 00 00 | .Beatriz........
0000:0010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0000:0020 | 00 00 00 00 00 00 00 00 00 00 1A 00 00 00 00 00 | ................
0000:0030 | 00 00 00 00 00 00 4D 40 05 43 61 72 6C 61 00 00 | ......M@.Carla..
0000:0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0000:0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0000:0060 | 00 00 12 00 00 00 00 00 00 00 00 00 00 80 4B 40 | ..............K@
0000:0070 | 07 46 61 62 69 61 6E 61 00 00 00 00 00 00 00 00 | .Fabiana........
0000:0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0000:0090 | 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 | ................
0000:00A0 | 00 00 00 00 00 00 4C 40 | ......L@
O programa que irá ler a "ficha_2.dat" e imprimir os dados na tela é o seguinte:
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
var ficha_arq : file of ficha;
fichario : array[1..3] of ficha;
i : integer;
procedure imprime_ficha(pos : integer);
begin
writeln('Ficha ', pos);
writeln('Nome: ', fichario[pos].nome);
writeln('Idade: ', fichario[pos].idade);
writeln('Peso: ', fichario[pos].peso);
writeln;
end;
procedure le_arquivo;
var i : integer;
begin
assign(ficha_arq, 'ficha_2.dat');
reset(ficha_arq);
for i:=1 to 3 do
begin
read(ficha_arq, fichario[i]);
end;
close(ficha_arq);
end;
begin
le_arquivo;
for i:=1 to 3 do
begin
imprime_ficha(i);
end;
end.
Saída:
Ficha 1
Nome: Beatriz
Idade: 26
Peso: 5.80000000000000E+001
Ficha 2
Nome: Carla
Idade: 18
Peso: 5.50000000000000E+001
Ficha 3
Nome: Fabiana
Idade: 22
Peso: 5.60000000000000E+001
Você também pode ir direto para um registro do arquivo através da instrução seek(arq_var, n), onde n é o número do registro. Entretanto, n varia de 0 a total_registros - 1.
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
var ficha_arq : file of ficha;
fichario : array[1..3] of ficha;
i : integer;
procedure imprime_ficha(pos : integer);
begin
writeln('Ficha ', pos);
writeln('Nome: ', fichario[pos].nome);
writeln('Idade: ', fichario[pos].idade);
writeln('Peso: ', fichario[pos].peso);
writeln;
end;
procedure le_e_imprime_ficha(pos : integer);
begin
assign(ficha_arq, 'ficha_2.dat');
reset(ficha_arq);
seek(ficha_arq, pos-1); { Varia de 0 a n-1 }
read(ficha_arq, fichario[pos]); { Varia de 1 a n }
close(ficha_arq);
imprime_ficha(pos);
end;
begin
le_e_imprime_ficha(2);
end.
Saída:
Ficha 2
Nome: Carla
Idade: 18
Peso: 5.50000000000000E+001
Podemos também ler/gravar um vetor de tipo, todo de uma vez (opção 4).
O que fizemos nos exemplos anteriores, foi criar uma variável "ficha_arq" do modelo "arquivo de tipo ficha". Dessa forma, cada leitura/escrita no disco irá gravar dados do tipo ficha (no caso, é apenas uma ficha).
Agora, iremos criar um tipo novo de dado, chamado "ficha_vet", que será um vetor de tamanho igual a três, do tipo ficha (modela o tipo que vamos precisar).
Iremos modificar a variável "ficha_arq" para ser do tipo "arquivo de ficha_vet". Assim, a cada leitura/gravação, todo o vetor é gravado de uma só vez.
Modificamos os programas anteriores:
Gravação |
Leitura |
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
ficha_vet = array[1..3] of ficha;
var ficha_arq : file of ficha_vet;
fichario : array[1..3] of ficha;
i : integer;
procedure preenche_ficha(pos : integer);
begin
writeln('Ficha ', pos);
write('Nome: ');
readln(fichario[pos].nome);
write('Idade: ');
readln(fichario[pos].idade);
write('Peso: ');
readln(fichario[pos].peso);
end;
procedure salva_arquivo;
var i : integer;
begin
assign(ficha_arq, 'ficha_2.dat');
rewrite(ficha_arq);
write(ficha_arq, fichario);
close(ficha_arq);
end;
begin
for i:=1 to 3 do
begin
preenche_ficha(i);
end;
salva_arquivo;
end.
|
type
ficha = record
nome : string[40];
idade : integer;
peso : real;
end;
ficha_vet = array[1..3] of ficha;
var ficha_arq : file of ficha_vet;
fichario : array[1..3] of ficha;
i : integer;
procedure imprime_ficha(pos : integer);
begin
writeln('Ficha ', pos);
writeln('Nome: ', fichario[pos].nome);
writeln('Idade: ', fichario[pos].idade);
writeln('Peso: ', fichario[pos].peso);
writeln;
end;
procedure le_arquivo;
var i : integer;
begin
assign(ficha_arq, 'ficha_2.dat');
reset(ficha_arq);
read(ficha_arq, fichario);
close(ficha_arq);
end;
begin
le_arquivo;
for i:=1 to 3 do
begin
imprime_ficha(i);
end;
end.
|
Obs: Os comandos read/write não fazem mais o acesso a uma posição do vetor (fichario[pos]), mas sim a todo o vetor (fichario).