Atenção, deve-se tomar cuidado
com ponteiros e referências,
que serão corrompidos pelo processo de gravação/leitura.
Verdade, tá.
Você não deveria
gravar o endereço de ponteiro num arquivo,
porque a hora que você reexecuta o sistema
os ponteiros, os endereços já estão em outros locais da memória, né.
Cada vez que você levanta a execução de um software,
essa execução ela vai rodar numa
posição distinta de memória em relação à última execução.
E aí, todo o endereçamento vai ser outro, né.
Precisa toma muito cuidado
e isso é particularmente pertinente pros relacionamentos dos objetos.
Por isso que lá na aula anterior eu falava que
a gente cria um Id pra cada objeto
e a gente ia manter a relação
desses objetos por meio de Id's.
Daí o Id é um elemento, um termo mais relativo,
que não é relativo ao endereço de memória,
é uma informação do objeto em si.
Eu fico daí independente do endereço de memória, tá.
Então, cuidado com isso.
Além disso, o processo de armazenamento de objetos
mais complexos é um pouco diferente.
De fato.
Se você olhar com a devida atenção
você vai perceber que não é assim tão direto
você gravar todas as informações de um objeto inteiro
em arquivo binário.
Você precisa de alguma estratégia.
Esse slide conta aqui, já, teoricamente a estratégia
pra depois a gente ver na prática.
Pra armazenar instâncias de tipos não-primitivos
deve-se fazer antes um processo chamado de serialização,
isto é, a transformação de um objeto
ou uma estrutura de dados em um formato armazenável.
Um meio simples de fazer isso
é criar dois métodos dentro de uma classe
que terão essa função, né,
ou essas funções,
de armazenamento, né, tanto de gravação daí, quanto de recuperação, tá.
Também devemos armazenar o tamanho
de uma estrutura com o tamanho variável.
Esse é outra informação importante, tá.
As vezes um objeto, ele vai ter dentro de si,
por exemplo, um vetor de caracteres
que vai ter uma quantia de caracteres dentro.
então, ali, pode ser que eu tenha
um vetor de caracteres de 10 caracteres, ou tenha 20 ou tenha 30,
assim, eu tenho que armazenar a quantidade de caracteres que eu tô gravando também,
porque isso pode ser variável, tá, em certos contextos.
Inclusive, nesse tocante,
eu primeiro vou tratar essa questão aqui de tamanho variável
pra depois tratar essa questão da serialização, tá.
Por isso que eu entro com outro exemplo
que fala da gravação
de estrutura com tamanho variável.
Tem um observação aqui: antes estudar o Grupo de Slides sobre string.
Tem um grupo de Slides número 13,
que trata de um tipo preexistente
chamado string no C++.
Aqui eu trago uma pitadinha disso, tá,
que suficiente pra essa exposição que tô fazendo.
No C++ existe a classe preexistente aqui chamada string,
que nada mais é que um vetor de caractere fancy,
mais estiloso.
Na prática, é uma classe, a string,
e essa classe permite que seus objetos
gerenciem vetores de caracteres de maneira
mais dinâmica no final das contas.
Aqui, eu tenho que fazer um include iostream,
porque eu vou usar cout e cin.
Conforme vocês já estão aí... incluindo, na verdade, cout;
e vou incluir a classe string;
e using namespace std.
Vejam que aqui, ó,
eu faço o include de um, depois do outro
e logo em seguida eu coloco using namespace std,
isso significa que vou usar o nome de espaços da string
e do iostream, tá.
Aqui, tem a função main e o _tmain.
_tmain porque eu fiz lá no projeto Microsoft Visual C++
uma dada configuração que gerava um _tmain e não um main.
Aqui eu tenho o s1,
que é um objeto de string,
que vai gerenciar um vetorzinho de caracteres,
nesse caso aqui, é o "bom dia!",
que eu passei pra construtora do objeto.
Aqui eu tenho o s2,
que é um vetorzinho também,
que vai gerenciar um...
perdão, é um objeto s2
da classe string que vai gerar um vetorzinho de caracteres.
Vetor esse que eu não contei ainda.
Tô usando a construtora sem parâmetros aqui, né, ou equivalente.
Valor default que seja.
Agora eu posso fazer s2 receber s1.
Veja, que legal.
Até posso usar o operador de recebe
entre objetos de string.
Por que que eu posso usar o operador de recebe?
Porque essa classe foi preparada pra utilizar esse operador aqui,
tal qual essa classe também foi preparada
pra utilizar, se utilizar de operador de fluxo.
Tá vendo, ó.
Posso jogar o objeto aqui pro fluxo
e ele vai imprimir lá, a essência do objeto
que é o vetor de caracteres.
Tanto pra s1 como pra s2, que ele vai colocar ali
"bom dia!", né... aqui, e "bom dia!" ali.
Já que o s2 vai receber o s1.
Quando eu faço o s2 receber o s1, na verdade, o
s2 recebe a essência do s1
que é o vetorzinho de caractere que tá aqui, tá, gente.
Agora, eu posso também ir em alguns desses
objetos aí, string,
e utilizar o operador de colchetes.
Coisa que eu faço aqui, ó.
Como ele é um vetor de caracteres
mais avançado, mas ainda sim ele representa, né, ele quer agir como um vetor de caracteres,
ainda que em melhor dinâmica,
o objeto foi preparado pra utilizar o operador de colchetes aqui, tá.
Operador de colchetes é um... é um operador, ué,
e aqui eu tô utilizando no objeto s2.
Na prática, graças a esse operador de colchetes aqui
você olha pro s2 e já vê ele como um... na verdade,
como se ele fosse o próprio vetor de caracteres, né.
Então, aqui eu tô fazendo o quê?
Tô indo no primeiro caractere
do suposto vetor de caracteres e alterando aqui
a posição 0 dele.
Então, vai acontecer que o primeiro
que o s1 vai ter bom dia agora com b minúsculo
e o s2 vai ter bom dia com b maiúsculo.
A hora que imprimir isso vai apresentar aqui.
O string
que a gente pode simplesmente chamar de vetor de caractere,
se a gente quiser chamar assim em alto nível,
mas ele é um objeto que gerencia um vetor de caractere
e que tem uma série de métodos, tá,
e operadores.
Operador de igual, operador de colchetes,
operador de fluxo que ele tem acesso pra trabalhar.
Além disso, ele tem método solto, como por exemplo aqui o length.
Length vai trazer o comprimento,
na prática, se a gente quiser chamar assim o tamanho
do vetor de caracteres que tá sendo gerenciado.
Então, eu vou armazenar isso em tamanho
e agora vamos usar um forzinho aqui.
Uma variável x, inteira que recebe zero,
que vai executar o for enquanto x for menor que tamanho
e esse x é incrementado de um em um.
E aqui eu uso, então,
esse for aqui pra imprimir
caractere a caractere da string s2.
Eu posso imprimir caractere a caractere
usando o operador de colchete, como vocês tão vendo aqui
ou imprimir diretamente como eu fiz ali.
Isso aqui é só uma introduçãozinha, então, pra cês verem
o que que é a classe string
que já existe no C++ pronto, tá.
Muito útil. Você pode, se quiser, a partir de agora,
voltar ou na sua... perdão, na sua nova versão do Sistema Acadêmico
substituir todos os vetores de caractere
que tinha lá que você criou na unha, à moda antiga
da linguagem C, né,
que funciona em C++ também, bem entendido.
mas você pode substituir por algo mais moderno por assim dizer
que é o objeto string.
E, como é que eu faço pra gravar
esse tipo de objeto, aí, que é o tipo de objeto que pode ter o tamanho variável?
Assim como aqui eu recebi "bom dia!", podia ter aqui, sei lá eu,
um poema aqui dentro, tá.
Meus 8 anos lá,
dentro do s1, a título de exemplo.
Então, aqui, ó.
Aqui, eu tô com uma função grava_string,
que recebe uma string por parâmetro
e vai jogar num dado arquivo
que também tá vindo por parâmetro.
Tá vendo, ó.
Inclusive, aqui, esse arquivo tá vindo por referência escondida.
A gente falou de referência escondida nos Grupos de Slides anteriores, tá.
Então, eu não vou entrar no âmbito,
mas é só pra saber que não é referência escondida.
ou seja, a passagem de parâmetro é por referência na prática.
Aqui, eu posso, nessa string, descobrir o tamanho.
Eu posso descobrir o tamanho por length ou por size.
Tem uma pequena diferença semântica entre o tamanho e o size
explicado lá no Grupo de Slides 13.
Em todo caso, aqui cê vai ter o teu
tamanho do vetor, enfim,
armazenado aqui na variável tamanho.
O que vai permitir eu fazer o quê?
Ir lá no meu arquivo e escrever
sizeof(tamanho)
Se a string tem, por exemplo,
dentro de si 80 caracteres.
O que eu vou fazer é gravar 80 caracteres.
E o comando write, aqui de arquivos, é preparado pra receber
as informações dos caracteres que tão lá dentro.
Aliás, aqui é peculiar até pelo seguinte.
São dois grandes momentos até especificando melhor o meu discurso.
Vejam que o que eu gravar por primeiro aqui,
nessa linha, gente, é o tamanho
da estrutura que eu vou gravar
e depois que eu vou gravar a estrutura em si.
Então, vejam só
No primeiro pedacinho do arquivo,
eu vou armazenar
usando a quantidade de bytes necessárias pro inteiro.
Isso tá aqui, ó.
sizeof(tamanho) que é um inteiro.
Eu vou armazenar o quê?
Vou armazenar um número que tem o tamanho do arquivo...
perdão, tem que ter o tamanho da string.
Se essa string tem o tamanho, sei lá eu, 30.
Então, ele vai gravar o número 30,
no formato inteiro,
ou seja, na quantidade de bytes necessárias de inteiro
na primeira parte de arquivo.
Depois, ato contínuo no arquivo,
ele vai gravar o tamanho.
Aqui, vejam que aqui eu não tô passando sizeof(tamanho),
tô passando tamanho mesmo.
Por que eu tô colocando só tamanho aqui?
Porque eu sei que cada vetor de caracteres tem 1 byte.
Então, se o vetor, aliás, se a string passar daqui
que não deixa de ser vetor de caracteres na sua essência,
tiver o tamanho 30, gente.
Eu vou gravar 30 caracteres.
E a partir da onde?
A partir da posição inicial da minha string.
Graças ao operador de colchetes aqui,
eu vou conseguir apontar pro início
do vetor de caracteres que é tratado pelo objeto string
e gravar cada um dos caracteres dentro do meu arquivo.
Então, novamente, como resumo.
Eu gravei primeiro o tamanho,
usando um inteiro no arquivo.
No mesmo arquivo depois,
eu estou gravando um conjunto de caracteres.
Professor, mas eles são tipos distintos,
o tamanho é inteiro e o outro é um conjunto de caractere.
Não tem problema, o arquivo binário ele suporta
esse tipo de gravação de tipos distintos,
conforme eu já mostrei no exemplo anterior ali.
Aqui eu tenho, ainda, leia_string, né,
que faz a leitura de string.
Agora eu vou receber um arquivo aqui,
um objeto arquivo que vai ser da classe ifstream.
Input file stream.
E tô recebendo a coisa também em referência escondida,
ou seja, a passagem de parâmetro por referência, portanto.
E tem um retorno string aqui.
Enquanto que lá na gravação era void porque não retornava nada,
só gravava as coisas.
Aqui eu vou ler... vou criar um objeto temporário, perdão.
str string.
Vou criar um inteiro tamanho.
O que eu vou fazer?
Primeira coisa que eu vou ler lá no arquivo,
no arquivo que tá sendo acessado pelo objeto que tá vindo por parâmetro,
eu vou ler o tamanho da string que tá gravada naquele arquivo.
Você tem que lembrar que no arquivo,
que supostamente você recebeu por parâmetro,
na primeira parte dele foi armazenado o quê?
Tamanho do vetor de caracteres lá registrado, tá.
Inclusive, esse tamanho tá armazenado em... na quantidade de bytes compatível com inteiro..
Aí, você vai ler aqui o tamanho, na prática.
Uma vez que eu tenho o tamanho
eu pego essa string criada aqui
e faço resize nela.
Resize é uma funçãozinha gente da classe string
que permite seus objetos expandirem o tamanho,
ou seja, alocar memória, lá internamente,
pro tamanho apropriado pra vetores de caractere.
E, sim, o vetor de caractere...
perdão, o objeto string
que gerencia vetores de caractere
ele vai alocar memória e desalocar memória
conforme a necessidade do...
que a string dada se apresente.
Aqui, como eu vou precisar de uma string de tamanho tal,
eu vou usar o resize que vai permitir lá,
esse objeto string
alocar uma quantidade de espaços
pra armazenar o que eu quero.
E aqui eu venho no final das contas
e faço agora um read
a partir do arquivo que tá vindo por parâmetro
num tamanho dado, imagino que seja 30.
E eu descobri o tamanho através da leitura da primeira parte do arquivo.
E aí, agora, eu vou
a partir da posição inicial do meu vetorzinho
aqui, na prática, da minha string,
mas aqui eu tô acessando, no final das contas, o âmago da string, então, o vetorzinho em questão.
Vou armazenar as informações agora nesse vetor.
Na prática, aconteceu o quê?
Eu fui lá e li o arquivo que tá sendo apontado,
referenciado por esse objeto aqui.
Primeira coisa que eu fiz foi, da primeira parte do arquivo,
recuperar o tamanho do conjunto de caracteres que tá lá dentro.
Repaginar aqui a minha string
pra que suporte o tamanho,
e depois eu vou ler nessa string, lá do arquivo,
caractere a caractere
jogando os caracteres aqui eu retorno essa string.
E quem receber lá vai utilizar,
ou a sua cópia no caso,
pra fins desejados.
No próximo exemplo que a gente vai trabalhar agora, tá.
Aí num momento dado eu vou utilizar essa função
grava_string e essa função leia_string,
mas vocês vão ver ela na prática, tá.
Aqui eu tava mais descrevendo o corpo delas, né.
Corpos em termos de código,
tava explicando tecnicamente como é que ela funciona
e a gente vai ver a utilização funcional delas daqui a pouquinho.