Bom, outro exemplo ainda.
Gravação de não-primitivos e serialização.
Aqui eu entro com uma classe Principal.
Lembre-se, isso aqui não é o Sistema Acadêmico,
isso aqui é um outra classe Principal, de outro sistema.
Inclusive, eu podia chamar essa aqui de classe
sei lá eu, Sistema se eu quisesse,
mas foi chamada de Principal nesse ambiente aqui.
Ele tem um atributo do tamanho do grupo
e pelo jeito o grupo é um conjunto de Pessoas.
É um ponteiro, grupo é um ponteiro de Pessoa,
então, de alguma maneira aqui
eu vou usar esse ponteiro pra criar um conjunto de Pessoas.
Construtora, destrutora.
Temos um executar,
um cadastrarPessoas,
um mostrarPessoas,
um salvarGrupo
e um carregarGrupo.
Se eu olhar aqui na construtora,
construtora, inclusive, sem parâmetros, tá,
eu tô colocando o tamanho do grupo em 0.
O que procede.
e tô colocando o grupo como nulo,
ou seja, esse ponteiro tá aterrado,
não é nesse momento que eu crio o grupo pelo jeito.
Vou destruir, aliás, aqui eu tenho a destrutora
que destróis o grupo se o grupo existir.
Opa, e aqui eu tô dando um delete, não um delete grupo,
tô dando um delete[] grupo.
Isso significa que, provavelmente, esse grupo aqui, então
ele serva para o quê?
Pra alocar dinamicamente um vetor de Pessoas.
Coisa que eu já adianto, porque eu sei,
que acontece aqui dentro da função cadastrarPessoas.
Se você olhar a função executar
ela chama a função cadastrarPessoas que vai criar
o vetorzinho de pessoas dinamicamente.
Depois eu vou salvar esse conjunto de Pessoas,
esse vetor de Pessoas num arquivo
Vou deletar o grupo,
inclusive, aterrando ele com nulo aqui.
E depois eu vou recarregar esse grupo
só pra fins de teste, pra demonstrar a técnica aqui que eu tô utilizando arquivos binários.
Ah, ali tem algumas observações que eu vou ler a seguir.
Para manter a simplicidade do exemplo,
o grupo será salvo em arquivo, apagado
e depois recuperado do arquivo na mesma função executar.
Que é essa função exemplificada aqui.
Na prática, a opção de salvar ou carregar
deveria ser dada ao usuário,
assim como a opção de salvar/carregar múltiplos arquivos.
É uma abordagem, né.
Então, por exemplo,
no Grupo de Slides 7A,
lá, a gente tinha um menuzinho, de fato,
perguntando se a pessoa queria gravar, se queria recuperar, etc e tal.
Aqui foi usado outra abordagem, mesmo porque é uma demonstração apenas
de cadastrar as pessoas, salvar num grupo, deletar o grupo e depois
recuperar o grupo a partir do arquivo onde foi salvo o grupo.
Arquivo binário aqui, no âmbito desse Grupo de Slides.
Vamos ver a função cadastrarPessoas.
Nada demais, tá.
Digite o tamanho do grupo.
Leia o tamanho do grupo na prática, né.
E aqui que tá a parte interessante, tá,
e eu vou ter que destacar em vermelho.
Deixa eu só voltar aqui, tá, pessoal.
Voltar um slide.
Bom, vou colocar em Modo Exibição.
Tô no slide 15, vou fazer uma volta pro slide 16... 14, aliás.
Tá aqui a classe Principal.
Isso aqui é o Principal.h.
Inclusive, tem a inclusão aqui dos Pessoa.h.
Natural, porque eu vou ter um grupo que é ponteiros de Pessoa.
Tem o pragma once, né,
pra evitar a reinclusão de vários .h's,
no âmbito do projeto.
Tenho aqui as bibliotecas externas
aqui, necessárias pro projeto.
iostream, string e, particularmente, o fstream.
Tudo usando o namespace standard de cada um.
Então, tá ali, o grupo Pessoas, tá.
E esse grupo ele vai ter que ser alocado em algum momento.
Tá aqui, ó.
É no cadastrarPessoas que isso acontece.
Se a gente der sequência aqui,
vocês vão perceber que eu perguntei pro usuário,
dentro da função cadastrarPessoas, o tamanho do grupo.
E agora eu vou alocar dinamicamente,
new Pessoa, mas não é só uma Pessoa
é x Pessoas.
Se o tamanho do grupo for 30, então eu vou alocar 30 Pessoas.
Aqui, insisto, não estou alocando dinamicamente uma Pessoa,
mas 30 Pessoas.
É um momento que é pra Pessoa prestar bastante atenção no que eu tô falando.
Eu não tô alocando apenas um objeto,
eu tô alocando um grupo de objetos, um do ladinho do outro.
No exemplo que eu tô presumindo aqui,
seriam 30 Pessoas uma do ladinho da outra.
E o que que é 30 Pessoas uma do ladinho da outra? É um vetor de Pessoas.
Então, aqui eu criei um vetor.
Vetor raiz, criado dinamicamente.
Eu lembro de um exercício que eu passei pra vocês
lá, num Grupo de Slides pra trás, acho que o Grupo de Slides número 2,
aonde, em algum momento,
o Departamento, aliás, a Universidade ia ter lá 50 Departamentos.
Você poderia criar um vetor dinamicamente, por exemplo,
pra tratar daquilo.
Usando esse tipo de técnica.
Muito bem.
Quando você aloca um vetor de Pessoas e não apenas Pessoa
a hora que você vai fazer um delete,
você não faz apenas um delete
por ponteiro grupo,
foi o grupo
que tá apontando pra esse conjunto de Pessoas.
Então, o grupo, na prática, ele aponta pra primeira Pessoa
e a partir do operador de colchetes eu posso acessar cada uma das Pessoas que estão lá dentro.
Você sabe que você aloca um vetor dinamicamente
mesmo que você tenha alocado ele dinamicamente e não estaticamente,
você pode utilizar o operador de colchetes pra navegar nele.
Tanto é, se você olhar aqui, ó.
Eu vou fazer o que aqui?
Através de um for, usando uma variável i, inteira,
que recebe zero, enquanto i for menor que tamanho do grupo
eu vou incrementando de um em um.
Em cada passagem eu vou ler o quê?
Vou pegar cada Pessoa que tá no meu vetor,
por exemplo, a Pessoa da posição 1,
e vou ler seu nome,
depois vou ler a sua idade, e vou ler o seu CPF.
Na passagem do loop, eu vou ler a segunda Pessoa,
vou ler a terceira Pessoa,
e meu ponto aqui é que a gente tá utilizando o operador de colchetes, ó.
Muito embora, o vetor tenha sido criado dinamicamente
e não estaticamente eu posso continuar utilizando o operador de colchetes,
conforme vocês já sabem
lá da disciplina de Fundamentos de Programação 1, Computação 1 ou coisa que o valha.
Conforme vocês sabem dos conhecimentos de linguagem C de vocês.
Isso considerado, então,
se eu aloquei um vetor dinamicamente,
eu posso usar o operador de colchetes.
Só que se eu aloquei dinamicamente, tem uma peculiaridade.
Eu vou ter que desalocar em algum momento.
É por isso que você vê aqui, ó, que
na destrutora eu faço um delete grupo,
pois grupo é nome do vetor que eu criei dinamicamente, na prática.
Só que como é um vetor eu tenho que usar o operador de colchetes, gente.
Tenho que usar o operador de colchetes pra ele desalocar o grupo inteiro,
e não só o início do grupo.
Aqui também, ó.
Eu vou cadastrar Pessoas, executar,
vou salvar o grupo e depois eu vou querer deletar o grupo.
Tô usando o operador de colchetes.
Então, isso aqui é uma coisa muito importante a se registrar
se você usou um new pra alocar um vetor,
no delete você tem que usar o operador de colchetes associado,
conforme já mostrado em slide anterior.
Bom, aqui, enfim, eu aloquei um vetor de Pessoas
através do ponteiro grupo, portanto o grupo passa a ser o vetor
e justamente por isso que eu posso ler, em cada posição do grupo,
o nome, a idade e o CPF de cada Pessoa
que está nesse vetor.
Bom, aqui não tem muito o que explicar, tá.
Digite o nome da n-ésima Pessoa,
Crio uma stringzinha...
olha a string lá, ó. c_nome, e leio a string.
Professor, então cin funciona com string? Funciona.
Eu tô passando pro setNome o string.
Ah, então, pera aí, então a classe Pessoa,
tem o setNome, tem o setIdade, tem o setCPF?
Certamente tem. Tanto é que eu tô chamando aqui.
Até eu posso dar um pulo lá pra frente, tá.
Tô no slide 15, vou lá pro grupo de slides número 19.
E aqui tem uma classe Pessoa.
Vejam, essa classe Pessoa não é a Pessoa do Sistema Acadêmico, tá.
É a classe Pessoa desse sistema.
E nesse sistema, essa classe Pessoa, tem o nome, idade e o CPF.
Vetor de... aliás, tem uma construtora
que permite inicializar os elementos.
Destrutora. Tem aqui, inclusive, um gravar, um ler.
ou seja, cada Pessoa é capaz de se gravar,
é capaz de se ler a partir de um arquivo.
E aqui vai ter os sets e gets, naturalmente.
Então, vamos voltar lá pra aonde a gente estava,
que era o grupo... aliás, que era o slide 15 desse Grupo de Slide em questão.
E agora vocês entendem porque que eu crio a Pessoa e posso, daí, utilizar, né,
crio as Pessoas no vetor e posso utilizar os sets, por exemplo,
de cada Pessoa que tá no vetor indicando
o índice em que a Pessoa se encontra.
Aqui é o índice i.
Começa em 0 e varia de 1 em 1 o tempo todo, enquanto for menor que o tamanho do grupo.
Na prática, isso vai permitir o quê?
Vai permitir que eu popule com informações
as pessoas que estão no meu grupo.
salvarGrupo.
Aqui criei um objeto arquivo do tipo ofstream.
Faço um open.
Tô chamando de grupo1.dat.
Não é um nome muito criativo, podia chamar de outro nome, mas enfim, assim está.
grupo1.dat.
Esse arquivo tá sendo aberto em modo binário e de gravação, né. Out.
Aqui, primeira coisa que eu vou gravar,
no primeiro pedacinho do meu arquivo, gente,
é o tamanho que esse grupo tem.
Esse grupo de Pessoas, se é 30 Pessoas, se é 300, se é 3 mil.
Então, eu sei isso
porque eu armazenei isso em tamanho do grupo.
Se você voltar um pouquinho aqui, ó.
Você tá vendo que eu perguntei pro usuário o tamanho do grupo.
Se era 30, se era 3 mil ou se era 3 milhões, sei lá eu.
Então eu sei o tamanho,
vou pegar esse número,
vamos utilizar 30 aqui,
e vou armazenar esse 30 dentro do meu arquivo
grupo1.dat.
A primeira informação que vai ter em binário no grupo1.dat
que é um arquivo binário, bem entendido,
é ali o os bytes relativos a um inteiro
que tem a informação do tamanho do grupo, 30.
Depois eu vou fazer o quê?
Eu vou navegar no meu vetor,
Pessoa a Pessoa, e mandar cada Pessoa se gravar.
Se gravar aonde?
No arquivo que eu tô passando por parâmetro.
Pessoa recebe esse arquivo aqui, ó.
Porque ela vai ter acesso ao grupo1.dat
e vai se gravar lá dentro.
Depois, a hora que a gente chegar no grupo... aliás, chegar no slide 19, que é o último lá,
ele tem classe Pessoa.h e .cpp
lá, a gente vai ver a função gravar,
mas o fato é que cada Pessoa pode ser gravada conforme você tá vendo aqui.
Aonde?
Ué, aonde o objeto que tá vindo por parâmetro, me contar.
Aqui, no caso o arquivo,
tá me contando que tem que gravar em grupo1.dat.
Como é que faz o carregarGrupo?
Eu vou criar um objeto de leitura agora de arquivo.
Esse objeto vai ler o grupo1.dat
em modo binário e entrada agora, não em saída, é entrada.
leitura.
Aqui, ele vai ler
a primeira partezinha do arquivo
onde traz o tamanho do grupo.
Eu vou armazenar isso no atributo tamanho da classe Principal,
conforme vocês tão vendo aqui.
Aí, eu vou criar...
recriar o grupo agora,
porque se você pensar um pouquinho, tá.
Na minha experimentação aqui,
depois que eu cadastrei e salvei, eu deletei, tá,
e depois que eu vou carregar.
Se eu deletei o grupo, o grupo tá vazio.
Então, isso explica porque que lá no carregarGrupo,
que é a função que a gente tá olhando agora,
porque que eu faço essa arte aqui, ó,
de realocar memória.
Claro, pois eu tinha desalocado.
Agora eu tô realocando com o tamanho
que eu tô trazendo lá do arquivo, que tava registrado no arquivo,
e recompondo o meu vetor.
E agora eu vou fazer o quê?
Em cada posição do vetor,
em cada Pessoa que tem, eu vou recarregar a pessoa
a partir do arquivo.
Que arquivo que é?
grupo1.dat, a partir dos binários que tão lá.
E depois aqui eu vou chamar a função mostrarPessoas
que mostram que as Pessoas de fato foram carregadas para fins de
de consistência pra ver que de fato tá funcionando, enfim.
Aqui é o mostrarPessoas, tá, que faz o quê?
Testa se o grupo não tá nulo, obviamente, né.
O grupo tem que tá alocado.
E aí, ele vai de 0 até tamanho mostrando Pessoa a Pessoa.
Então, ele vai lá colocando, ó.
Em cada posição do vetor de Pessoas, ele mostra
o nome, idade e o CPF usando a função get.
Já foi dito que tem os sets e gets de cada Pessoa.
Agora que eu mostrei pra vocês
como é que essa função principal, que tá permitindo
cadastrar, gravar em arquivo,
deletar o grupo de Pessoas,
recuperar o grupo de Pessoas e
mostrar o grupo de Pessoas,
eu vou mostrar a classe Pessoa em maior detalhes agora.
Inclusive, isso é pertinente, porque a gravação
e a recuperação de Pessoas depende da própria classe Pessoa
sendo que cada objeto Pessoa se grava
em arquivo e se recupera do arquivo também.
O que é algo bem interessante.
Então, tá aqui, gente, ó. A classe Pessoa.
Ela tem nome que é do tipo string,
idade que é inteiro, e um CPF que é inteiro.
Tem ali uma construtora com parâmetros e valor default,
o que faz a vez de construtora sem parâmetros, portanto.
c_nome, c_idade e c_CPF,
que permite alimentar esses atributos naturalmente, né.
Se olhar, inclusive, tá aqui, ó.
A implementação da respectiva .cpp.
Pessoa.cpp.
Tem a destrutora que não faz nada,
mas, ainda assim, por padrão a gente explicita ela.
Tem a função gravar, a função ler
e os sets e gets que eu não coloquei aqui, porque são evidentes.
Em todo o caso, tão lá nos códigos disponíveis no site, tá.
Site que eu mostrei no início dessa exposição pra vocês.
Ainda gente, eu queria dizer,
que isso aqui é o Pessoa.h. Isso aqui é o Pessoa.cpp.
Queria dizer que aqui está a construtora.
Aqui está a destrutora.
Aqui está o gravar que eu já vou falar a respeito dele.
Aqui tá o carregar que eu já vou falar a respeito dele também, desses métodos.
Só que além dos métodos da classe Pessoa,
eu tenho duas funções soltas aqui
que não são funções membro.
São funções soltas, são funções procedimentais,
que é o grava_string e o leia_string.
Essas funções eu já mostrei aqui pra vocês em slide anterior.
Agora eu tô contando pra vocês aonde elas estão posicionadas.
Elas estão posicionadas aqui, ó, no Pessoa.cpp.
Só que elas são funções que não pertencem a Pessoa.cpp
elas tão aqui porque elas são uteis, nesse contexto.
Elas podiam tá em outro lugar,
num .h e seu respectivo .c ou .cpp, né,
.cpp, no caso.
E ser incluído aqui de alguma maneira,
através de um include, portanto.
Mas aqui eu coloquei só pra fins pedagógicos,
posicionei elas, justamente, aqui junto ao .cpp pra ficar de fácil acesso.
Por quê?
Porque pra Pessoa se gravar
ela vai precisar usar a grava_string,
e pra Pessoa se recuperar
ela vai precisar da leia_string,
bem entendido.
Bom, aqui, na função gravar de Pessoa,
ela vai receber o objeto que vai permitir acessar o arquivo pertinente.
Ela faz o quê?
O seu nome, que é uma string,
ela vai conseguir gravar o seu nome no arquivo
usando a funçãozinha grava_string
que eu já mostrei pra vocês lá nos slides anteriores.
Depois, ainda, além de gravar a string...
essa string é peculiar, tá.
Tô incisando essa função aqui,
mas se vocês voltarem um pouco os slides
vocês vão ver que nessa função aqui pra gravar a string
eu tenho que gravar a quantidade de caracteres que tem na string
e depois gravar caractere a caractere que tem na string
Então, no final das contas,
se você olhar o arquivo
aonde as coisas tão sendo gravadas.
Deixa eu voltar um slidezinho aqui.
Como é que é nome do arquivo mesmo?
O grupo1.dat
ele vai ter várias informações dentro do grupo1.dat.
A primeira coisa que vai ter no grupo1.dat
é o número com a quantidade de Pessoas.
Depois vai ter a gravação de cada Pessoa.
Tudo em binário, hein.
Pra cada Pessoa gravada
primeiro é gravado o número de caracteres que tem o nome da Pessoa
e o nome da Pessoa.
Depois vai ter gravado subsequentemente, a idade da Pessoa.
Depois o CPF da Pessoa.
A hora que chegar a segunda Pessoa a ser gravada no arquivo grupo1.dat,
vai ter a quantidade de caracteres do nome da Pessoa,
os caracteres do nome da Pessoa,
daí a idade da Pessoa,
e daí o CPF da Pessoa.
Tudo em binário, bem entendido.
E aqui o carregar faz o quê?
Faz com que ele vá no arquivo
e ele leia lá no arquivo,
o arquivo no caso vai ser o grupo1.dat,
de uma posição dada o nome da Pessoa e depois vai ler idade e vai ler o CPF.
Professor, mas como é que ele sabe a posição?
Pessoal, vocês tem que entender o seguinte, tá.
Voltar aqui um pouquinho, por exemplo,
aqui no carregarGrupo, tá.
A hora que eu abro o arquivo,
esse grupo1.dat,
o objeto arquivo ele tá apontando pro início do arquivo lá, tá.
Internamente, a hora que eu abro,
existe um ponteiro que é o ponteiro de leitura do conteúdo do arquivo.
Esse ponteiro tá sempre na posição inicial do arquivo.
A hora que eu venho aqui e leio uma quantia de bits, tá.
Depois da leitura, o ponteiro ele é atualizado
lá pro final dessa quantia ali dada.
Isso significa o seguinte
que seu eu li aqui o tamanho
que é um inteiro
então ele vai ter, o ponteiro de leitura interno aqui,
ele vai ter navegado 2 bytes pra frente,
já que o tamanho de um inteiro tem 2 bytes, ou n bytes que seja.
Então, a hora que eu vier aqui e for carregar a primeira Pessoa,
o ponteiro de leitura,
o cabeçote de leitura do arquivo,
vai tá já na posição certa pra carregar a Pessoa.
A hora que eu faço aqui o carregar da Pessoa,
ele vai fazer o quê?
Ele vai ler,
usar a função leia_string, que faz o quê?
Recupera a quantidade de caracteres
e recupera caractere a caractere do nome, recompondo o nome aqui.
E aí, cada vez que ele vai fazer uma leitura,
o cabeçote de leitura vai avançando,
de maneira tal que
a hora que ele vai recuperar a idade da Pessoa em questão
o cabeçote de leitura lá, tá no lugar certo,
e pro CPF idem.
Então, depois que ele lê uma Pessoa,
aí depois ele vai poder ler a segunda Pessoa no loop.
sempre com o arquivo na posição certa,
porque você tá indicando a quantidade certa de bytes que você tá andando no arquivo.
Na verdade, essa aula aqui
ela é uma aula suplementar, tá.
Porque tanto na ementa da disciplina de Técnicas de Programação
quanto nesse nosso curso que a gente tá fazendo aqui de orientação a objetos em C++,
a gente não chegava a prever a utilização de arquivos binários,
mas como o Corrêa, que era o monitor da disciplina,
tinha feito esse material e esse material ficou legal
eu achei legal fazer essa exposição aqui pra vocês, tá.
Pela primeira vez, inclusive,
para que vocês possam ter essa introdução sobre arquivos binários
e ter uma noção de como é que funciona o arquivo binário.
Óbvio que se você se interessou pelo assunto, tá.
Você pode refazer, lá no Sistema Acadêmico,
aquela persistência de objetos
e dos seus relacionamentos, tá.
Não por meio de arquivos sequenciais de texto,
inclusive, fez o próprio monitor
como exemplo experimental pra vocês lá, né.
Você pode substituir o que ele fez
do ponto de vista sequencial
uma solução toda binária,
mas isso se lhe interessar trabalhar com esse assunto
e antes de fazer isso eu aconselho a dar uma boa lida
no capítulo, ou nas seções pertinentes, sobre arquivo binário lá do livro dos Deitel,
pai e filho Deitel,
porque lá traz mais informações e mais detalhamento
sobre todas essas informações que eu trouxe, tá.
A maneira de gravar binariamente
que você tem que colocar o tamanho em bytes de cada elemento que você vai gravar
que quando você vai gravar em um arquivo
ele, cada vez que você grava, ele vai avançando com o cabeçote de gravação
vai gravando no arquivo.
Cada vez que você tá lendo o arquivo
ele vai automaticamente trazendo pra vocês também
o posicionamento apropriado do cabeçote de leitura
dentro do arquivo binário, desde que você passe a quantidade de bytes corretas,
bem entendido, né.
Senão não tem como ele se localizar, esse é o parâmetro que ele se utiliza pra navegar corretamente.
Então, lá no capítulo dos Deitel pertinente ele traz todo esse detalhamento pra vocês.
Espero que tenham aproveitado a aula, tá.
Nós nos encontramos no próximo Grupo de Slides,
a ser tratado aqui nessas exposições que eu tô fazendo
que eu tô gravando.
Naturalmente, vai ser o Grupo de Slides número 8,
aonde a gente vai falar sobre sobrecarga de operadores, tá,
que é algo bem interessante e peculiar.
Até breve e bons estudos.