Curso de Basic
Arquivos
Você está em: MarMSX >> Cursos >> BASIC
A memória RAM do MSX é volátil, ou seja, quando desligamos o micro, os dados contidos nessa memória são perdidos. Para preservarmos os dados de uma aplicação nossa, devemos salvar os dados em disco ou fita cassete.
O comando utilizado para gerenciar a entrada e saída (E/S) de dados em disco é o OPEN.
Sintaxe:
OPEN nome_do_arquivo FOR INPUT/OUTPUT/APPEND AS#n
Onde:
- nome_do_arquivo - Nome do arquivo de dados a ser lido ou gravado. Acrescente "CAS:" antes do nome do arquivo para ler ou gravar em fitas cassete.
- INPUT - Abre arquivo como modo leitura.
- OUTPUT - Abre arquivo como modo gravação, gerando um arquivo novo.
- APPEND - Abre arquivo como modo gravação, continuando um arquivo existente.
- n - Número do arquivo. Número máximo é definido pelo comando MAXFILES (1 a 15).
O funcionamento do OPEN do MSX é semelhante ao fopen do C ou o assign do Pascal:
OPEN
' Operações de leitura e gravação
CLOSE
Modo Texto x Modo Binário
Na realidade, todo arquivo é binário. Entretanto, o que muda é como vamos interpretar dos dados contidos nesse arquivo.
No modo texto, cada byte ou caractere é um código ASCII que representa uma letra, número ou símbolo.
Vejamos o seguinte arquivo de exemplo chamado "texto.txt" em um editor hexadecimal:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000 | 4D 61 72 4D 53 58 | MarMSX
Até mesmo quando temos números no formato texto, sua representação em bytes é um código ASCII. Por exemplo, o número 25 no formato texto ficaria:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000 | 32 35 | 25
A informação relevante está na parte do "Texto", ou seja, o valor 25 é encontrado nessa parte e não na parte dos "Bytes".
No modo binário, os bytes representam o valor diretamente. Por exemplo, o número 25 (&H19) no formato binário ficaria:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000 | 19 | .
A quantidade de bytes demandada para representar um valor irá depender do tipo de variável que foi definida. No MSX temos:
- Inteiro - 2 bytes
- Precisão simples - 4 bytes
- Precisão dupla - 8 bytes
Por exemplo, o valor 25 (&H19) armazenado em uma variável do tipo inteiro seria representado em um arquivo assim:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000 | 19 00 | ..
Observe que agora a informação relevante está na parte dos "Bytes", ou seja, encontramos o valor 25 nessa parte.
Os Comandos PRINT#n, INPUT#n e LINE INPUT#n
Os três comandos funcionam a partir de um arquivo aberto por OPEN e operam no modo texto.
PRINT#n
O comando PRINT#n converte uma string, um valor ou uma expressão para o formato texto e grava no arquivo n. O funcionamento é igual ao do comando PRINT, onde:
- O comando puro salva uma string e salta para a próxima linha. Ex: PRINT#1,"Teste"
- Ao crescentar o ponto e vírgula ";" ao final, continua na mesma linha. Ex: PRINT#1,"Teste";
- Ao crescentar a vírgula "," ao final, dá 13 espaços em branco e continua na mesma linha. Ex: PRINT#1,"Teste","MSX"
INPUT#n
O comando INPUT#n lê um texto do arquivo até encontrar uma vírgula ou o return, ou seja, eles servem de caracteres separadores de dados. Para o texto:
Rua das Flores, 123,Moca
Ao lermos:
INPUT #1,A$
INPUT #1,B$
INPUT #1,C$
Teríamos:
A$ = "Rua das Flores"
B$ = " 123"
C$ = "Moca"
LINE INPUT#n
O comando LINE INPUT#n lê uma linha inteira (máximo 254 caracteres) de um arquivo no formato texto.
Usando o mesmo exemplo anterior, teríamos:
Rua das Flores, 123,Moca
Ao lermos:
LINE INPUT #1,A$
Teríamos:
A$ = "Rua das Flores, 123,Moca"
Lendo e gravando dados
O exemplo a seguir cria um arquivo de texto, escreve duas mensagens e grava.
10 OPEN"MENSAGEM.TXT" FOR OUTPUT AS#1
20 PRINT#1, "O MSX possui 16 cores"
30 PRINT#1, "O MSX possui 3 canais de som"
40 CLOSE 1
Ao abrirmos o arquivo gerado "mensagem.txt", veremos o seguinte formato:
O MSX possui 16 cores
O MSX possui 3 canais de som
Substituíndo a linha 20 por
20 PRINT#1, "O MSX possui 16 cores";
O texto resultante seria:
O MSX possui 16 coresO MSX possui 3 canais de som
Agora, vamos ler o primeiro arquivo "mensagem.txt" gerado e imprimir as linhas.
10 OPEN"MENSAGEM.TXT" FOR INPUT AS#1
20 LINE INPUT#1, A$
30 PRINT A$
40 IF NOT EOF(1) THEN 20
50 CLOSE 1
Saída:
O MSX possui 16 cores
O MSX possui 3 canais de som
O comando EOF testa se o fim do arquivo foi atingido. O arquivo de texto do MSX possui o caractere de código ASCII igual a &H1A ao final do texto, indicando o fim do arquivo.
Nesse próximo exemplo, iremos ler a informação de 3 alunos e salvar em disco.
10 OPEN"ALUNOS.TXT" FOR OUTPUT AS#1
20 FOR F=1 TO 3
30 PRINT"Aluno "+STR$(F)+":"
40 INPUT"Nome ";N$
50 INPUT"Idade ";I
60 INPUT"Curso ";C$
70 PRINT #1, N$ + "," + STR$(I) + "," + C$
80 NEXT F
90 CLOSE 1
Um exemplo de arquivo "alunos.txt" gerado para esse programa é:
Patrícia, 21,Direito
Camila, 22,Direito
Daniel, 20,Geografia
O programa para recuperar os dados dos alunos no disco e imprimir na tela é:
10 OPEN"ALUNOS.TXT" FOR INPUT AS#1
20 F=1
30 PRINT"Aluno "+STR$(F)+":"
40 INPUT #1,N$
50 INPUT #1,I$
60 INPUT #1,C$
70 PRINT "Nome: ";N$
80 PRINT "Idade: ";I$
90 PRINT "Curso: ";C$
100 F=F+1:PRINT
110 IF NOT EOF(1) THEN 30
120 CLOSE 1
Como a idade está armazenada no formato texto, a variável "I" para recuperar o dado será string. No caso desse programa, como só desejamos imprimir o valor, nenhuma conversão é necessária.
A saída correspondente ao arquivo "alunos.txt" para esse programa é:
Aluno 1:
Nome: Patrícia
Idade: 21
Curso: Direito
Aluno 2:
Nome: Camila
Idade: 22
Curso: Direito
Aluno 3:
Nome: Daniel
Idade: 20
Curso: Geografia
Quando quisermos gravar os dados de uma tabela criada através de matrizes, podemos utilizar o caractere vírgula "," para delimitar as colunas no texto do arquivo. Ex:
10 DIM T(3,2)
20 ' Popula a tabela
30 T(1,1)=1 : T(1,2)=2
40 T(2,1)=3 : T(2,2)=4
50 T(3,1)=5 : T(3,2)=6
60 ' Salva a tabela
70 OPEN"tabela.txt" FOR OUTPUT AS#1
80 FOR I=1 TO 3
90 FOR J=1 TO 2
100 PRINT#1, T(I,J);
110 IF J<2 THEN PRINT#1,",";
120 NEXT J
130 PRINT #1,
140 NEXTI
150 CLOSE 1
Arquivo texto gerado:
1 , 2
3 , 4
5 , 6
O programa que recupera os dados da tabela salva é o seguinte:
10 DIM T(3,2)
20 OPEN"tabela.txt" FOR INPUT AS#1
30 FOR I=1 TO 3
40 FOR J=1 TO 2
50 INPUT #1, A$
60 T(I,J) = VAL(A$)
70 PRINT "T(";I;",";J;")=";T(I,J)
80 NEXT J,I
Saída:
T( 1 , 1 )= 1
T( 1 , 2 )= 2
T( 2 , 1 )= 3
T( 2 , 2 )= 4
T( 3 , 1 )= 5
T( 3 , 2 )= 6
O Comando INPUT$(c, #n)
Este comando permite determinar o número de caracteres a ser lido por vez, onde c é a quantidade de caracteres a ser lido e n o número do arquivo aberto.
Com este comando, podemos ler um arquivo caractere a caractere. Veja o exemplo a seguir, que lê os 7 primeiros bytes de um arquivo binário:
10 OPEN "arquivo.dat" FOR INPUT AS#1
20 FOR I=1 TO 7
30 V = ASC(INPUT$(1,#1))
40 PRINT HEX$(V);" ";
50 NEXT I
60 CLOSE 1
Um exemplo de saída para o "arquivo.dat":
FE 6 80 7 80 6 80
Os arquivos binários não possuem o caractere de terminação "&H1A" como os arquivos de texto. Nesse caso, não podemos utilizar o comando EOF para testar o fim de arquivo.
Para resolver este problema, usamos o comando LOF(n) para obter o tamanho do arquivo em bytes, e poder determinar seu fim. O valor n corresponde ao número do arquivo aberto.
Utilizando o artigo dessa página sobre funções, vamos formatar a saída dos bytes no exemplo anterior:
10 DEF FN FRMT$(N$) = RIGHT$("0"+N$,2)
20 OPEN "arquivo.dat" FOR INPUT AS#1
30 FOR I=1 TO 7
40 V = ASC(INPUT$(1,#1))
50 PRINT FN FRMT$(HEX$(V));" ";
60 NEXT I
70 CLOSE 1
Saída:
FE 06 80 07 80 06 80
Arquivos Híbridos - MKI$, MKS$, MKD$ / CVI, CVS, CVD
Um arquivo híbrido é aquele que poderá ter ao mesmo tempo dados no formato texto e no formato binário.
Foi visto anteriormente, que há duas maneiras de armazenar um dado numérico: como string ou como bytes diretamente.
O armazenamento de um número como string em um arquivo texto é simples. Basta utilizar a função STR$ para converter de número para string. Ex:
PRINT #1, STR$(I)
Já o armazenamento de número como binário em um arquivo texto requer um truque, que é criar uma string com os códigos ASCII iguais ao valor desse número (vide a seção de formato binário).
O Disk-Basic (novos comandos do Basic para disquetes) implementa 6 funções que realizam esse truque para nós. São elas: MKI$, MKS$, MKD$, CVI, CVS e CVD.
As seguintes funções convertem de número para string:
- MKI$(valor) - converte de inteiro para uma string de 2 bytes.
- MKS$(valor) - converte de precisão simples para uma string de 4 bytes.
- MKD$(valor) - converte de precisão dupla para uma string de 8 bytes.
Exemplo:
PRINT #1, MKI$(I)
As seguintes funções convertem de string para número:
- CVI(string) - converte de string de 2 bytes para inteiro.
- CVS(string) - converte de string de 4 bytes para precisão simples.
- CVD(string) - converte de string de 8 bytes para precisão dupla.
Exemplo:
I = CVI(I$)
O exemplo a seguir irá ilustrar como funciona um arquivo híbrido.
10 OPEN "estoque.dat" FOR OUTPUT AS#1
20 INPUT"Peça";P$
30 INPUT"Quantidade";Q
40 PRINT #1, P$; ","; MKI$(Q)
50 CLOSE 1
Saída:
Peça? Parafuso
Quantidade? 4
O nome da peça foi salvo como string, já a quantidade de peças foi salvo como um número inteiro de 2 bytes. Observe o arquivo "estoque.dat" no editor hexadecimal, onde o nome da peça está destacado em verde e a quantidade em azul:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------------+--------------
0000 | 50 61 72 61 66 75 73 6F 2C | Parafuso,
0009 | 04 00 0D 0A 1A | .....
É evidente que temos que usar a conversão no sentido inverso para obter a quantidade de peças:
INPUT #1, Q$
Q = CVI(Q$)
Nota importante
O comando INPUT# irá indicar fim de arquivo, sempre que ele encontrar o caractere com o valor ASCII igual a &H1A ou 26. Dessa forma, a busca pára, mesmo se estivermos no inicio do arquivo.
No modo texto, o caractere 26 geralmente não é utilizado. Assim, quando ele aparecer na leitura dos dados, indica claramente que é o fim de arquivo. No modo binário, todos os valores entre 0 e 255 são perfeitamente possíveis de aparecer, inclusive o 26. Assim, não é seguro utilizar o comando INPUT para a leitura de dados binários. Em vez disso, deve-se utilizar o comando GET, que não é afetado pelo fim de arquivo - EOF.
Os Comandos FIELD, PUT e GET
Esse modelo de gravação permite formatar os dados de saída, baseado na idéia de registros de um banco de dados. Além disso, o acesso a cada registro é aleatório, ou seja, eu posso acessar e modificar qualquer registro, bastando para isto informar o número do registro.
Há uma pequena diferença no modo de abertura de um arquivo aqui, em relação aos outros modos que utilizam o comando OPEN. Para esse tipo, um arquivo é aberto genericamente, ou seja, para leitura e gravação ao mesmo tempo. Isto é feito através da omissão dos termos "FOR INPUT/OUTPUT/APPEND". Ex:
OPEN "arquivo.txt" AS #1 LEN=20
Outra novidade é a presença da opção LEN, que irá indicar o comprimento total de cada registro.
FIELD
O comando FIELD tem como objetivo modelar o registro da tabela de dados, indicando quais os campos existentes e o tamanho de cada um. Ele cria na memória o registro que irá receber ou enviar dados ao disco, através dos comandos GET ou PUT.
Sintaxe:
FIELD #n, c1 AS variável_string_1, c2 AS variável_string_2, etc
Onde "c" é o comprimento de cada campo criado no registro e "variável" é a variável de acesso a um determinado campo do registro.
Exemplo: vamos criar um registro para gravar informações sobre um aluno:
FIELD #1, 20 AS N$, 2 AS I$
O comando FIELD acima cria um registro com 2 campos: o primeiro para inserir o nome do aluno (N$), com comprimento igual a 20 caracteres, e o segundo para inserir a idade (I$), com comprimento igual a 2 caracteres. Veja o diagrama abaixo:
+--------------------+--+
| N$ |I$|
+--------------------+--+
| | |
+--------------------+--+
Quando o registro for preenchido, a informação estará pronta para ser salva de uma só vez no disco, através da instrução PUT.
Acesso ao registro - LSET e RSET
O registro criado pelo comando FIELD deve ser modificado através dos comandos LSET e RSET. A diferença entre ambos, é que o LSET alinha o texto à esquerda do campo, enquanto que o RSET alinha o texto à direita.
Seguindo o exemplo anterior, o comando:
LSET N$="MarMSX"
irá preencher o registro da seguinte maneira:
+--------------------+--+
| N$ |I$|
+--------------------+--+
|MarMSX | |
+--------------------+--+
Já o comando:
RSET N$="MarMSX"
irá preencher o registro da seguinte maneira:
+--------------------+--+
| N$ |I$|
+--------------------+--+
| MarMSX| |
+--------------------+--+
Para ler um campo do registro, basta acessar diretamente a variável relativa a esse campo. Ex:
PRINT N$
Saída:
MarMSX
PUT #n, r
Este comando grava em disco o conteúdo de um registro "r", criado através do comando FIELD e preenchido por LSET ou RSET.
Vejamos o seguinte exemplo de como cadastrar alunos.
10 OPEN "alunos.dat" AS #1 LEN=22
20 FIELD #1, 20 AS N$, 2 AS I$
30 R=1
40 INPUT"Nome";NO$
50 IF NO$ = "" THEN CLOSE 1: END
60 INPUT"Idade";ID$
70 LSET N$ = NO$
80 LSET I$ = ID$
90 PUT #1,R
100 R=R+1
110 NO$="":GOTO 40
Obs: Tecle "enter" sem digitar qualquer nome, para terminar o cadastro de alunos.
Saída:
Nome? Fabiana
Idade? 20
Nome? Diego
Idade? 19
Nome? Valentina
Idade? 20
Nome?
Ok
O arquivo "alunos.dat" gerado é:
Fabiana 20Diego 19Valentina 20
Os registros estão divididos da seguinte maneira:
Fabiana 20Diego 19Valentina 20
Azul: Registro 1
Verde: Registro 2
Amarelo: Registro 3
Obs: Uma vez que o acesso é aleatório, podemos, por exemplo, alterar somente o registro 2 sem que os registros 1 e 3 sejam afetados.
GET #n, r
Este comando lê do disco o conteúdo de um registro "r", criado através do comando FIELD.
O número de registros pode ser calculado, dividindo-se o tamanho do arquivo pelo tamanho de um registro.
Vamos criar um programa para ler os dados dos alunos:
10 OPEN "alunos.dat" AS #1 LEN=22
20 FIELD #1, 20 AS N$, 2 AS I$
30 TR = LOF(1) / 22
40 PRINT"Total de registros:";TR
50 FOR R=1 TO TR
60 GET #1,R
70 PRINT
80 PRINT"Aluno";R
90 PRINT"Nome: " + N$
100 PRINT"Idade: " + I$
110 NEXT R
120 CLOSE 1
Saída:
Total de registros: 3
Aluno 1
Nome: Fabiana
Idade: 20
Aluno 2
Nome: Diego
Idade: 19
Aluno 3
Nome: Valentina
Idade: 20
Lendo dados binários com o GET
Na seção de arquivos híbridos foi discutido que o comando INPUT parava sempre que encontrasse o caractere &H1A e que o o comando GET seria a solução para esse problema. Isto acontece porque o comando GET não é afetado pelo caractere de fim de arquivo.
De modo a testar os dois comandos na leitura de um arquivo binário, vamos criar o arquivo "data.dat".
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------------+--------------
0000 | 01 02 03 1A 04 05 06 1A 07 | .........
Observe que ele possui dois caracteres de fim de arquivo.
O programa a seguir utilizará o comando INPUT para ler os dados no modo binário do arquivo "data.dat".
10 OPEN"data.dat" FOR INPUT AS #1
20 T = LOF(1)
30 PRINT"Tamanho do arquivo:";T
40 PRINT"Dados: ";
50 C$ = INPUT$(1,#1)
60 PRINT RIGHT$("00"+HEX$(ASC(C$)),2);" ";
70 GOTO 50
Saída:
Tamanho do arquivo: 9
Dados: 01 02 03
Input past end in 50
Um erro de tentativa de leitura após o fim de arquivo é lançado.
Ao darmos o comando "PRINT EOF(1)", o valor -1 surge indicando que o fim de arquivo foi encontrado.
O programa a seguir faz a mesma coisa que o programa anterior, só que com a leitura realizada pelo GET em vez do INPUT.
10 OPEN"data.dat" AS #1 LEN=1
20 FIELD #1, 1 AS C$
30 T = LOF(1)
40 PRINT"Tamanho do arquivo:";T
50 PRINT"Dados: ";
60 P=1
70 GET #1,P
80 PRINT RIGHT$("00"+HEX$(ASC(C$)),2);" ";
90 P=P+1
100 IF P<=T THEN 70
Saída:
Tamanho do arquivo: 9
Dados: 01 02 03 1A 04 05 06 1A 07
Obs: o número máximo de registro é 4.294.967.295 [1].
Acelerando a leitura de dados com buffer
A leitura de dados byte a byte pelo método GET é mais lenta que pelo método INPUT. De modo a comprovar isso, vamos realizar dois testes: um utilizando o GET, e outro o INPUT.
Primeiramente, vamos criar um arquivo maior, chamado de "bigdata.dat", para auxiliar nos testes.
10 OPEN "BIGDATA.DAT" FOR OUTPUT AS#1
20 FOR I=1 TO 100
30 PRINT #1, "A";
40 NEXT
50 CLOSE 1
O programa a seguir abre o arquivo "bigdata.dat" e lê os dados caractere a caractere, utilizando o método GET.
10 OPEN"bigdata.dat" AS #1 LEN=1
20 FIELD #1, 1 AS C$
30 T = LOF(1)
40 PRINT"Tamanho do arquivo:";T
50 PRINT"Lendo dados ..."
60 P=1 : TIME=0
70 GET #1,P
80 P=P+1
90 IF P<=T THEN 70
100 TT=TIME
110 PRINT "Tempo:";TT;"segundos."
Saída:
Tamanho do arquivo: 101
Lendo dados ...
Tempo: 204 segundos.
O programa a seguir abre o arquivo "bigdata.dat" e lê os dados caractere a caractere, utilizando o método INPUT.
10 OPEN"bigdata.dat" FOR INPUT AS #1
20 T = LOF(1)
30 PRINT"Tamanho do arquivo:";T
40 PRINT"Lendo dados ..."
50 TIME=0
60 C$ = INPUT$(1,#1)
70 IF EOF(1) = 0 THEN 60
80 TT=TIME
90 PRINT "Tempo:";TT;"segundos."
Saída:
Tamanho do arquivo: 101
Lendo dados ...
Tempo: 63 segundos.
Através do INPUT, a leitura é feita aproximadamente 3 vezes mais rápida do que pelo GET. Para compensar essa perda de tempo, podemos utilizar o mecanismo de buffer para acelerar a leitura pelo método GET. Vejamos como.
Na linha 10 do programa do GET, foi criado um registro (buffer) com o tamanho de 1 byte, assim como o FIELD define o caractere com tamanho igual a 1.
Se aumentarmos o tamanho do registro no método GET para o tamanho do arquivo, os dados são lidos em apenas 7 segundos. Isto significa uma velocidade de leitura 29 vezes mais rápida do que através da leitura caractere a caractere.
10 OPEN"bigdata.dat" AS #1 LEN=101
20 FIELD #1, 101 AS C$
...
O tamanho de arquivo utilizado é de 101 bytes. Ele está dentro do limite de tamanho para um registro, que é de 256 caracteres. Entretanto, um arquivo pode ter qualquer tamanho. Dessa forma, se ele for maior do que o tamanho do buffer, ele será quebrado em N blocos do tamanho do buffer.
A fragmentação do arquivo pode acarretar em um bloco (último) menor que o tamanho do buffer. Felizmente, o método GET permite a leitura de um bloco no arquivo que seja menor que o tamanho do buffer. Ex:
10 OPEN"bigdata.dat" AS #1 LEN=255
20 FIELD #1, 255 AS C$
30 T = LOF(1)
40 GET #1,1
50 CLOSE 1
Mesmo o arquivo tendo apenas 101 bytes, ele é lido normalmente no buffer. Entretanto, uma tentativa de leitura no próximo registro irá causar o erro "Input past end".
Obs: devido a limitação de 255 caracteres no FIELD, limitamos o buffer para 255 caracteres.
A leitura dos dados recuperados do disco é feita diretamente na string definida pelo FIELD, através do comando MID$.
10 FOR I=1 TO 255
20 PRINT MID$(C$,I,1)
30 NEXT
Quando o bloco de dados for menor que o buffer, será carregado lixo na string C$. Dessa forma, o programa deve prever essa situação e limitar a leitura da string para a quantidade correta de bytes.
T = LOF(1) : ' Tamanho do arquivo
TR = FIX(T/255)+1 : ' Total de registros
LB = T MOD 255 : ' Tamanho do último bloco
...
R = 1 : ' Registro atual
...
FOR I=1 TO 255
PRINT MID$(C$,I,1)
IF R=TR AND I>=LB THEN I=255
NEXT
A seguir, o programa completo para ler um arquivo de qualquer tamanho utilizando o mecanismo de buffer.
10 OPEN"bigdata.dat" AS #1 LEN=255
20 FIELD #1, 255 AS C$
30 T = LOF(1) : TR = FIX(T/255)+1 : LB = T MOD 255
40 PRINT"Tamanho do arquivo:"; T
50 PRINT"Lendo dados ..."
60 R=1
70 GET #1,R
80 FOR I=1 TO 255
90 PRINT RIGHT$("00"+HEX$(ASC(MID$(C$,I,1))),2);" ";
100 IF R=TR AND I>=LB THEN I=255
110 NEXT I
120 R=R+1
130 IF R<=TR THEN 70
140 CLOSE 1
Gravação de Blocos de Memória
Podemos também gravar uma área de memória, o conteudo de variáveis ou de matrizes como um arquivo binário.
Modo 1: usando o PRINT# e INPUT$
O comando PRINT converte uma variável numérica em uma string. Entretanto, em um arquivo binário, necessitamos gravar o valor de uma variável numérica diretamente como um byte e não como um texto.
Para resolver essa questão, temos que gravar a informação byte a byte, utilizando o seguinte "truque": vamos gerar um caractere, cujo código ASCII é o valor que desejamos armazenar na área dos bytes. O comando CHR$ obtém um caractere a partir do código ASCII fornecido, resolvendo o nosso problema.
Por exemplo, para gravar o valor 25 no formato binário, usamos o comando PRINT # assim:
PRINT #1, CHR$(25)
Resultado:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000 | 19 | .
Observe que o valor 25 (&H19) está no lado dos bytes e não do texto.
Uma vez entendida esta questão, podemos passar para o programa a seguir, que irá gravar um trecho de memória compreendido entre &HC000 a &HC00A no fomato binário.
10 OPEN "memoria.dat" FOR OUTPUT AS#1
20 FOR P=&HC000 TO &HC00A
30 PRINT #1, CHR$(PEEK(P));
40 NEXT P
50 CLOSE 1
Obs: devemos colocar o sinal de ponto e vírgula ao final do comando PRINT #. Do contrário, a cada caractere gravado serão acrescidos dois bytes sinalizando o "fim de linha". O caractere de fim de arquivo, com o código ASCII igual a "&H1A", é colocado ao final do arquivo.
O programa para ler o arquivo de memória gerado é:
10 OPEN "memoria.dat" FOR INPUT AS#1
20 FOR P=1 TO LOF(1)-1
30 V = ASC(INPUT$(1,#1))
40 PRINT HEX$(V);" ";
50 NEXT P
60 CLOSE 1
Modo 2: usando o BSAVE e BLOAD
No modo anterior, a operação de leitura / gravação é feita byte a byte, o que a torna excessivamente lenta.
Os comandos BSAVE e BLOAD realizam operação de E/S por bloco, tornando esse processo bem mais rápido.
Sintaxes:
BSAVE nome_do_arquivo, endereço_inicial, endereço_final[, endereço_de_execução][, S]
BLOAD nome_do_arquivo[, R][, S][, deslocamento]
Onde:
- endereço_inicial - Endereço inicial do bloco de memória RAM ou VRAM a ser gravado no disco.
- endereço_final - Endereço final do bloco de memória RAM ou VRAM a ser gravado no disco.
- endereço_de_execução - Endereço inicial de execução, caso o arquivo seja um programa.
- S - Indica que o bloco de memória pertence a VRAM. Caso omitido, indica RAM.
- R - Executa arquivo carregado na memória RAM, a partir do endereço_de_execução (somente BLOAD).
- deslocamento - indica que o arquivo será carregado a partir do endereço de memória RAM ou VRAM endereço_inicial + deslocamento.
Exs:
BSAVE"memoria.dat", &HC000, &HC00A
BLOAD"memoria.dat"
Obs:
- O comando BSAVE adiciona um header de 7 bytes ao arquivo gerado.
- Formato do header:
- FE - Identificação de arquivo binário / FF - Indica arquivo do tipo Basic
- XX XX - Endereço incial do programa na memória (2 bytes)
- XX XX - Endereço final do programa na memória (2 bytes)
- XX XX - Endereço incial de execução do programa na memória (2 bytes)
- O comando BLOAD lê o endereço inicial de memória contido no header do arquivo binário e carrega os dados programa a partir desse endereço.
- Não podemos modificar diretamente o endereço inicial de carregamento do programa através do comando BLOAD, mas apenas utilizar um deslocamento.
Lendo e gravando variáveis e matrizes
Podemos gravar dados de variáveis ou matrizes, consultando diretamente a memória. Para isso, devemos saber a localização do dado e seu respectivo tamanho.
No exemplo a seguir, vamos definir a variável "A" como inteira, modificar seu conteúdo e depois salvar.
10 DEFINT A
20 A = 6
30 P = VARPTR(A)
40 BSAVE"a.dat", P, P+1
Lembre-se que as variáveis são, por padrão, do tipo precisão dupla. Por essa razão, redefinimos "A" como uma variável do tipo inteiro. Uma variável do tipo inteiro ocupa exatamente 2 bytes.
O comando VARPTR obtém o endereço inicial de memória onde está localizado o dado da variável.
Para recuperar a informação salva da variável, precisamos primeiramente definir novamente a variável "A" como inteira e inicializá-la com um valor qualquer.
Quando criarmos novamente essa variável, ela poderá estar em um endereço diferente do que foi gravado pelo programa anterior.
Conforme dito na seção anterior, não podemos determinar diretamente o endereço de memória que iremos carregar o arquivo. Nesse caso, vamos ler primeiro o arquivo, de forma a obter o endereço inicial do cabeçalho. Depois, é só calcular o deslocamento baseado nesse endereço.
10 DEFINT A
20 A=0
30 OPEN "a.dat" FOR INPUT AS#1
40 E = ASC(INPUT$(1,#1)) : ' Pula 0xFE
50 E = CVI(INPUT$(2,#1))
60 CLOSE 1
70 BLOAD"a.dat",VARPTR(A)-E
80 PRINT A
Saída:
6
Obviamente, não vale a pena utilizar este recurso para gravar apenas uma variável. Foi colocado aqui apenas para ilustrar a idéia.
Entretanto, este método é recomendado para salvar um bloco grande de dados, como, por exemplo, uma matriz.
O exemplo a seguir mostra como salvar uma matriz em um arquivo binário.
10 DEFINT T
20 DIM T(2,1)
30 T(0,0)=1 : T(1,0)=2 : T(2,0)=3
40 T(0,1)=4 : T(1,1)=5 : T(2,1)=6
60 P = VARPTR(T(0,0))
70 BSAVE"tabela.dat", P, P+(3*2*2)-1
O endereço inicial dos dados está na primeira célula da tabela. Os dados estão dispostos de forma contígua em memória e possuem o tamanho definido. Assim, o cálculo do tamanho dos dados é:
tamanho = largura x altura x tamanho_do_dado
Para recuperar os dados da tabela, vamos utilizar o seguinte programa:
10 DEFINT T
20 DIM T(2,1)
30 OPEN "tabela.dat" FOR INPUT AS#1
40 E = ASC(INPUT$(1,#1)) : ' Pula 0xFE
50 E = CVI(INPUT$(2,#1))
60 CLOSE 1
70 BLOAD"tabela.dat",VARPTR(T(0,0))-E
80 FOR I=0 TO 2
90 FOR J=0 TO 1
100 PRINT T(I,J);
110 NEXT J : PRINT : NEXT I
Saída:
1 4
2 5
3 6
Referências:
[1] - Drive Interface DDX - Manual de instruções.