Pessoal, boa tarde.
Estamos no dia 16 de julho de 2020.
Uma quinta-feira.
A aula de hoje é relativa a arquivos binários.
Grupo de Slides número 7 parte B.
Na aula passada,
nós vimos, justamente, arquivos de texto ou sequenciais,
que serviram pra gente fazer persistência de objetos.
Lá o exemplo dado foi sobre,
ou continuou, sendo o sistema acadêmico.
Aonde se fez possível
gravar informações de Alunos,
ou seja, de objetos Alunos criados em tempo de execução
e depois da execução do programa
em uma reexecução foi possível a gente recuperar
esses Alunos, gerando uma persistência
de objetos naquele âmbito.
Lá pra gravar e pra recuperar a coisa era feita via menu.
Obviamente que dá pra usar outras estratégias, tá.
Por exemplo, dá pra recuperar na construtora
lá da classe Principal
e também dá pra gravar na destrutora
da Principal, ou coisa que o valha.
São várias as estratégias possíveis.
Antes de eu começar aqui, inclusive, esse slide,
eu vou abrir a nossa página
pra fazer uma menção a aula anterior.
Tá aqui minha página.
Página principal. Vocês vão em Técnicas de Programação.
Aqui eu andei reformulando um pouquinho a página, tá.
Andei repaginando a página.
Fazendo menção aos Grupos de Slides que cada link trata.
Então, aqui em Técnicas,
Conceitos Fundamentais, por exemplo, vão do Grupo de Slides 1
ao Grupo de Slides 8, que é o que tá em voga nesse momento.
A gente tá no 7, saindo da parte A, indo pra parte B.
Se eu clicar aqui em Conceitos Fundamentais.
Antes tinha aqui Revisão ou Visão dos Conceitos Fundamentais
agora eu deixei só Conceitos Fundamentais de Linguagem C++.
Clico nisso.
Vocês vão perceber que particularmente essa parte dos Conceitos Fundamentais
ela está reorganizada, tá.
Não mudou nada demais, eu só melhorei aqui um pouco
a estruturação da página
pra ficar mais entendível o que é cada link.
O conteúdo é o conteúdo que a gente já vinha trabalhando
e agora fica mais fácil de utilizá-la.
Nós estamos justamente aqui na parte 7.
Vimos aula passada a parte A desse grupo de Slides 7.
Aqui, inclusive, é a menção que eu gostaria de fazer, tá.
Se você olhar aqui, nós temos uma versão estável
de todo o sistema
até o Grupo de Slides 7A.
Isso foi uma versão feita, em 2018, pelo então monitor da disciplina
o Vitor Corrêa,
que elaborou, inclusive, resolvendo
senão todos, grande parte dos exercícios, tá.
Por exemplo, no final aqui do Grupo de Slides anterior
eu pedia pra melhorar o nível de persistência dos objetos.
Além de gravar, persistir os objetos Aluno
persistir as Disciplinas, por exemplo, persistir os Departamentos
e, se possível, ainda persistir
a parte de relacionamentos entre os objetos.
Então, o Vitor propõe uma solução pra isso
utilizando arquivos sequenciais ou de texto
que compensa ver.
Inclusive aqui, ele traz a solução
e tudo o que foi proposto desde o Grupo de Slides 01
até aqui o Grupo de Slides 7 parte A.
Ainda na parte A, no final, no segundo momento desse exercício que eu falava há pouco,
tem também a possibilidade de fazer essa persistência
das instâncias dos objetos Aluno,
Disciplina, Departamento etc e dos relacionamentos deles
por meio, não de arquivo sequencial ou de texto,
mas sim por arquivo binário
que é justamente a temática da aula de hoje.
Aqui nessa parte B, aqui,
do Grupo de Slides 7, a gente trata de arquivo binário.
Conforme eu falo no início da aula
e aqui, inclusive, tem os códigos, tá.
Um detalhe importante, é que o exemplo que a gente vai trabalhar hoje,
ele não é parte do Sistema Acadêmico.
Ele é um exemplo independente,
mas ele poderia ser aplicado no Sistema Acadêmico.
Se vocês quiserem vocês podem
se inspirar nesse código do Grupo de Slides 7 parte B
e aplicar ao Sistema Acadêmico que a gente vem desenvolvendo na maior parte dos slides.
Isso considerado,
eu vou fechar aqui
e vou retomar a nossa aula
a partir dos slides
colocando a coisa em Modo Exibição, aqui.
Esse Grupo de Slides em questão também foi desenvolvido
em 2018, pelo Vitor C. M. Corrêa,
que foi monitor lá, se eu não me equivoco, primeiro e segundo semestre de 2018.
Legal.
Próximo slide.
Arquivos Binários.
Qual é a diferença em arquivo textual e binário.
Bom, o textual ele é gravado na forma de texto.
Você inclusive, se você
for no diretório onde você criou o arquivo
a partir do seu código e clicar nele, ele abre ali o arquivo de texto
no Notepad, no editor de texto pertinente.
O arquivo binário como eu já disse, tá, ele vai gravar as coisas em binário.
Vai gravar byte a byte.
Então, é outra abordagem.
que permite, inclusive, gravar dados heterogêneos, tá.
Você vai gravar tipos de dados distintos
porque justamente você vai poder gravar a coisa byte a byte, tá.
Claro que você vai ter que interpretar os dados byte a byte também.
Para ler e escrever arquivos binários
utiliza-se as classes
ifstream, input file stream para ler,
output file stream para escrever
e file stream para ler e escrever.
Elas estão presentes no arquivo, ou na biblioteca,
fstream, no namespace standard (std).
Para abrir um arquivo utiliza-se a função "open".
Tá aqui um exemplo, ó.
Então, eu criei um arquivo, ali, um objeto arquivo
que é do tipo ofstream.
E aqui eu vou no objeto e chamo o método
pré-programado preexistente open.
Aqui eu tô criando, pondo o nome do arquivo, né.
Aqui eu tô criando um arquivo chamado meu_arquivo.bin,
de exemplo obviamente.
Nos formatos que esse arquivo vai trabalhar.
Eu vou trabalhar com o arquivo em formato binário.
Tá vendo, ó.
Ou seja, input output, né, system,
O sistema de entrada e saída vai ser binário
e vai ser em formato de saída, em formato de gravação.
Pode-se também utilizar a construtora com parâmetros do objeto.
Tá aqui, ó.
Criei o objeto
e já passo o nome,
e o formato de abertura binário de escrita
desse arquivo.
Após o seu uso, pode-se utilizar a função close
para fechar o arquivo aberto.
Bastante aconselhável utilizar a função close
Sempre utilize, tá.
Não obstante, de qualquer forma, o arquivo será fechado automaticamente pelo destrutor
ao sair de seu escopo.
Ou seja, a hora que esse objeto for fechado
O destrutor vai fechar o arquivo
a vantagem de você fechar ele você mesmo
é que você sabe o momento em que ele está sendo fechado, né.
Então, você tem um controle melhor sobre isso.
Aqui uns detalhes sobre abertura de arquivo.
Então, tá ali o exemplo que a gente já tinha trazido, né.
Criei um objeto arquivo do tipo ofstream.
O que já tinha sido exemplificado no slide anterior.
Eu não tô usando a construtora eu tô usando a função open pra
também funciona, tá.
Primeiro parâmetro da função open.
Caminho relativo ao executável
e nome do arquivo a ser salvo.
É onde tá teu projeto, tá.
O caminho vai ser onde tá teu projeto,
mas você pode especificar outro projeto aqui, tá.
Imagine que você tem, sei lá, um diretório temporário, um temp.
Lá no drive C, você pode fazer aqui C:\temp.
E aí, colocar meu_arquivo.bin.
Daí vai, em vez de gravar,
lá no teu arquivo... no diretório do teu arquivo... no diretório de projeto,
ele vai gravar onde você especificou,
no exemplo dado, no C:\temp.
Segundo parâmetro, flags, né, o bandeiras que definem os modos
de operação de arquivo.
Flags, ou bandeiras, que podem ser utilizadas.
Tá ali, ó.
out, arquivo de saída; in, arquivo de entrada;
binary que é arquivo binário
e trunc que descarta o conteúdo antigo do arquivo
e substitui pelo novo.
Se eu não me equivoco
ele faz isso por definição se você não especificar,
mas nos testes vocês podem verificar.
Para incluir mais de uma flag deve-se utilizar o operador bitwise OR.
De fato vocês tão vendo que eu tô utilizando ali, tá.
Eu, particularmente, achava que seria mais interessante,
semanticamente falando,
o e (&), mas de fato eles utilizam o ou (|), aí,
binário por questões técnicas.
Muito bem.
Esse Grupo de Slide 5 ele é bem peculiar
pelo seguinte,
ele mostra pra nós a maneira de gravar o arquivo.
Então, aqui eu abri um arquivo
exemplo novamente, tá,
que vai gerenciar o... perdão eu tô...
Retificando meu discurso.
Eu tô aqui abrindo ou criando o objeto arquivo,
bem entendido,
que é instância da classe ofstream,
arquivo de saída via fluxo, tá.
E esse, essa instância,
aqui, vai gerenciar
o arquivinho lá que vai existir no diretório no caminho especificado
meu_arquivo.bin.
Vai tá abrindo em modo binário, né,
ou seja, vai ler byte a byte as coisas
e em modo de gravação.
E aqui eu tenho a função write.
A função write permite, em termos de arquivos binários,
gravar a informação no arquivo.
Vejam que interessante,
eu sou obrigado, aqui no segundo parâmetro,
colocar a quantidade de bytes que devem ser utilizados para salvar a variável,
para isso é recomendado o uso do operador sizeof.
Se eu tô usando
o arquivo binário, onde eu vou gravar byte a byte,
eu preciso saber a quantidade de bytes que eu vou utilizar,
que eu vou gravar num dado pedaço do arquivo,
por isso que eu uso o sizeof caractere, ali,
que vai me dar o tamanho de um caractere.
Via de regra, um caractere ele é um byte, né,
Não é atoa
que inclusive eu tô passando a variável caractere, né.
Podia ser qualquer outro nome, mas enfim, foi dado o nome caractere aqui nesse exemplo.
Passando ela por referência
e interpretando ela como um ponteiro char.
O que isso significa? Isso significa que eu tô interpretando essa variável byte a byte.
Aqui o exemplo é simplório, porque
a variável caractere só vai ter um byte mesmo,
mas eu posso ter elementos mais complexos
que vão ter n bytes
e ainda sim eu vou gravar byte a byte.
É isso que eu tô dizendo ali.
Então, o primeiro parâmetro.
Endereço da variável a ser salva
que deve ser convertida para o tipo
char* por meio de um cast,
pelo motivo já explicado.
Próximo slide aqui,
nós temos então, a leitura de um arquivo.
Para ler um arquivo utiliza-se a função read
da classe ifstream.
Aqui eu tô criando um objeto
uma instância da classe ifstream
arquivo de entrada via fluxo
que vai gerenciar o diretório pertinente
meu_arquivo.bin.
Ele tá em modo binário e tá em modo de leitura.
Aqui eu vou ler o caractere.
Então, ele vai entrar, vai pegar a variável
passar por referência aqui.
Ele vai o arquivo... acessar via objeto arquivo,
lá, o arquivinho do meu_arquivo.bin.
Ele vai ler essa informação byte a byte
e jogar no caractere.
Por isso que o caractere, inclusive, é passado por referência aqui.
Vejam que aqui só tem um byte, então tá fácil, né,
mas o exemplo tem que ser genérico,
porque daí você tem soluções aonde
que vai ser gravado tem mais de um byte,
conforme a gente já vai ver em exemplos subsequentes.
Falando em exemplo,
vamos ver um exemplo simples, aqui no próximo slide,
que é o slide número 7.
Essa função, gravar colocada abaixo,
se executada, irá ler 3 entradas
e gerar um arquivo com nome meu_arquivo.bin.
Óbvio que você pode criar arquivos com nome mais criativos, tá.
Relendo.
Essa função, se executada, irá ler 3 entradas
e gerar um arquivo com nome meu_arquivo.bin
no mesmo diretório do executável compilado,
com 9 bytes, contendo-as.
Contendo as variáveis, portanto.
Sendo que o...
ele tá colocando aqui que o
o char tem 1 byte, um inteiro tem 4 bytes e o float tem 4 bytes.
Em arquiteturas mais antigas, um inteiro ia até 2 bytes, tá,
mas isso vai depender de arquitetura pra arquitetura.
Em todo caso, o sizeof vai trazer pra você o tamanho apropriado.
Aqui eu tenho a função void gravar.
Uma função que podia tá aí no main.cpp da vida.
Digite um caractere.
Defini a função em questão e vou lê-la.
Digito um número inteiro.
Defino a função e leio.
Ah, perdão, função não, a variável. Bem entendido.
A variável.
Variável ali, variável aqui.
Digite um número real.
Defino a variável real, float,
e leio ela aqui através do cin.
Vocês vejam que, aqui o pertinente é
aqui eu tenho uma variável caractere,
uma inteira e uma float.
Se pegar arquiteturas aí, mais usuais,
apesar do que tá aqui, o char teria 1 byte,
um inteiro 2 bytes e o float 4 bytes.
E como é que ele vai conseguir gravar coisas que são de tamanho distinto em um arquivo?
Bom, aqui eu criei o objeto arquivo, tá.
Via de regra, eu não preciso disso aqui,
porque eu já vou fazer lá um
#include , using namespace std;
não preciso contar que o ofstream está dentro da std,
porque eu já fiz isso no using namespace.
Então, isso aqui é desnecessário, tal qual
isso aqui também é desnecessário nesse contexto.
Muito embora iria funcionar, tá.
Só tava recontando onde estavam as coisas.
Novamente, então aqui o lega,
é que eu tenho 3 variáveis distintas
de tipos distintos que eu vou colocar no mesmo arquivo.
É diferente lá do arquivo de texto,
onde tudo é convertido pra texto.
Ainda que seja convertido automaticamente lá, através de
operadores de fluxo e afim, né.
Conforme vocês viram no Grupo de Slides anterior.
Aqui, eu tô abrindo, então
meu_arquivo.bin.
Em modo binário de saída.
Utilizando para tal um objeto, instância da classe.
Aqui eu tô fazendo o objeto em questão
gravar lá no arquivinho.
O quê?
Gravar primeiro a variável 1,
depois a 2, depois a 3.
Vejam que, além de eu gravar,
sempre lendo byte a byte,
eu informo a quantidade de bytes que tem,
pra ler byte a byte e gravar certinho a variável lá dentro do arquivo.
Ainda em um exemplo simples, mas agora de leitura.
Devemos extrair as informações na mesma ordem em que ela foi gravada.
Então, isso é importante, tá.
Se eu gravei
Um caractere, um inteiro e float,
eu tenho que ler assim, tá.
Porque o primeiro byte vai ter o caractere,
os próximos 2 bytes ou n bytes vai ter o inteiro
e nos próximos x bytes, por exemplo, 4 bytes vai ter o float, né,
se a arquitetura respeitar essa quantidade de bytes
que eu tô usando no meu discurso.
Aqui eu tô abrindo o arquivo, então, em modo de leitura.
Conforme vocês tão vendo.
E tá aqui criação a do objeto
arquivo do tipo ifstream
que vai ler aquele meu arquivo .bin,
que está junto do meu projeto do meu executável,
e é binário em modo de leitura.
Se eu tirei o contexto... Bom, é que o contexto ios aqui, nesse caso, é importante.
Tem que deixar ele ali.
Aqui eu vou ler então,
vou ler a primeira variável,
mas quantos bytes eu vou ler
pra colocar nessa primeira variável que tá aqui?
Aqui eu tô fazendo o seguinte, gente.
Eu estou... Abriu esse arquivo aqui, ó.
Esse cara aqui não deixa de ser
um camarada que permite apontar pro arquivo
ir lá e ler o arquivo, tá.
É o endereço de memória onde o arquivo se encontra.
Onde o arquivo foi levantado.
Aí, acontece o seguinte.
Agora eu vou ler,
lá dentro daquele arquivo,
quantos bytes?
Vou ler 1 byte.
Por quê?
Porque o char tem 1 byte.
E vou armazenar aonde aquele byte?
Vou armazenar aqui, ó.
Vou armazenar nessa variável.
Depois eu vou ler aqui, os n bytes aqui,
relativo ao inteiro
e vou armazenar no var2.
Depois eu vou ler os x bytes
relativo a um float
e armazenar aqui no var3.
E aí, depois eu vou imprimir aqui, e cê vai...
na hora que cê executar o código lá, cê vai ver que ele funciona certinho.