Vamos melhorar um pouco mais a solução ainda.
Através de uma versão 5.
E aqui entra num momento peculiar da nossa explicação,
porque a gente vai entrar, justamente,
na sobrecarga de operadores.
Tem aqui o operador que eu vou carregar... que eu vou sobrecarregar,
ou seja, que eu vou permitir objetos
de MinhaString utilizar operador de recebe.
Como é que sobrecarrega um operador, professor?
Eu faço assim, ó.
Eu venho aqui e coloco void, no caso do operador de recebe,
void operator=
operador recebe,
e ponho o quê esse operador vai poder receber.
Quando eu faço isso
eu já vou avançar um slide aqui, só a título de exemplo,
me permite isso, tá.
Vou poder fazer tipo aqui, ó. S2 MinhaString.
Vou poder utilizar o operador de recebe no S2.
Inclusive, eu podia escrever dessa forma que tá aqui, tá.
Igualzinho vocês tão vendo aqui.
Eu poderia escrever dessa forma, ou da forma que segue.
Também funciona.
Isso aqui é a forma mais, vamos dizer...
menos maquiada.
Essa é uma forma maquiada, mais em alto nível e mais pedagógica.
Essa forma é mais roots, tá, forma mais raiz,
aonde eu tô dizendo explicitamente e também funciona, tá. Compila e executa normal.
Tô dizendo o seguinte
eu tô chamando a função operador recebe (operator=)
do objeto S2
passando como parâmetro o seguinte:
"Operador de atribuição sobrecarregado é muito útil."
É isso que tá acontecendo na prática.
E ainda assim, eu posso utilizar da forma como eu dizia antes.
Que é essa aqui que é muito mais intuitiva.
Inclusive, é por isso que a gente sobrecarrega operador
em objetos, pra poder utilizar de maneira intuitiva.
O S2 recebe aquilo.
Muito bem.
Essa que é a ideia então de sobrecarregar os operadores pras nossas classes,
pra poder, intuitivamente, utilizar esse tipo de situação, aí, que você tá vendo em tela, tá.
Voltando um pouquinho aqui, ainda.
Aqui, eu tenho, então,
como tenho a possibilidade de usar o operador de recebe.
Como é que eu sei que é void?
Porque se você olhar na gramática da linguagem C++,
você percebe que é void.
Além disso, se eu pegar a literatura de C++
eles vão contar pra você
quais são os operadores que podem ser sobrecarregados e quais que não podem
e qual que é o tipo de retorno e qual o tipo de parâmetro que você pode utilizar.
Particularmente, aqui, o operador de recebe o valor é void.
E, aqui, parâmetros do tipo preexistente você vai poder, normalmente, utilizar
que é o caso de ponteiro de caractere.
Tá aqui a implementação, ó.
O que eu vou fazer aqui?
Eu tô recebendo, então, algo, não tô?
Inclusive, se você olhar o slide aqui, ó.
Aqui eu criei o S2. Que tamanho tem o S2?
Nesse momento aqui, tá.
Ele passou pra construtora o quê?
Vamos voltar aqui.
Opa.
Eu tenho uma evolução aqui.
Eu comentei a construtora sem parâmetros, mesmo porque ela não tava...
Bom, até poderia utilizar nesse âmbito dado,
mas nesse momento não vou mais utilizar, porque ela tá comentada.
Em contrapartida, como eu comentei a construtora sem parâmetros,
eu tenho que ter algo equivalente.
Por isso que eu coloquei um valor default no parâmetro s,
que é esse abre e fecha aspas, aqui.
Na prática, vai criar um vetor de caracteres de tamanho 1
que vai ter o \0.
Ele vai chamar aqui, vai passar esse cara vazio.
O strlen vai dar 0.
A hora que passar pra cá, ele vai alocar um pStr que é tamanho+1
e daí ele vai o strcpy pra copiar o s dentro do str
que vai copiar, na prática... fazer uma cópia com \0.
Ou seja, uma string vazia que só tem, no final das contas,
o caractere de finalização.
Bom, isso dito, gente,
o que acontece a hora que eu venho aqui e executo...
criei o S2 com parâmetro ali
valor default, né.
Deu aquele resultado que eu acabei de explicar.
O que acontece quando eu venho aqui e faço esse operador de recebe?
Significa que eu vou alterar o conteúdo que vai tá dentro de pStr.
Por quê? Porque eu tô passando uma informação nova pro S2.
Vou mudar o conteúdo que tá dentro de pStr e o tamanho da string tratada.
Por isso que tamanho, agora, deixou de ser constante, ele voltou a ser normal.
Continuo usando essa inicialização,
porque essa inicialização aqui, ela funciona tanto pra objeto constante
quanto pra objeto não-variável.
Só uma ressalva, tá, que eu não falei antes.
Se fosse constante aqui,
eu só posso utilizar essa forma.
Não posso vir aqui e fazer tamanho recebe strlen.
Como não é mais constante,
eu posso usar assim ou assado.
Eu também posso vir aqui e fazer tamanho recebe strlen(s),
porque ele daí, não sendo constante, sendo variável, eu posso usar o operador de recebe nesse contexto.
Insisto.
Parâmetro quando ele é constante ele vai ter que ser inicializado dessa forma aqui, tá.
Via construtora.
Mas tudo bem.
Aqui não é mais constante, deixou de ser, ele era
deixou de ser,
e parâmetro variável também pode ser inicializado assim.
E por que ele deixou de ser constante?
Por causa do operador de recebe.
O que eu faço agora é deletar o que tinha
que é o \0 lá.
Vou calcular o tamanho da string que tá vindo
e vou fazer um setString.
O que o setString faz mesmo?
Vai alocar um novo...
nova memória, com tamanho apropriado.
Apontar em pStr,
ou seja, isso vai ser gerenciado
essa memória alocada vai ser gerenciada por pStr
pStr tá recebendo, na prática,
um conjunto de caracteres alocados dinamicamente.
O que acontece quando um ponteiro aponta pra um conjunto de caracteres?
Ele se torna o vetor de caracteres em questão
e o nome sozinho, que é o pStr,
é um ponteiro pro início desse vetor, conforme vocês já sabem.
E tá aqui eu copiando, então,
o que tá vindo por parâmetro pra esse novo vetor constituído.
Então, aqui permite eu alterar,
através do operador de recebe que foi sobrecarregado,
o conteúdo de S2.
Então, eu vou desalocar pStr,
vou recalcular o tamanho apropriado, tal,
vou realocar memória
e copiar pra dentro dessa memória alocada esse conteúdo aqui.
Tudo de maneira transparente pro usuário.
Isso tudo gerenciado...
esses vetores estão sendo gerenciados bonitamente
através dessas astúcias que a gente têm programado na classe MinhaString,
Sem o usuário precisar saber disso, tá.
Usuário do objeto, bem entendido.
Ele vem aí e faz S2 recebe tal coisa
e o objeto vai se adaptar, à luz dos códigos explicados,
pra comportar esse conteúdo dado.
Vocês já sabem, que eu posso usar assim.
Conforme tá ali.
Ou assado.
Muito bem.
Agora vamos fazer uma versão 6 do nosso código.
Na versão 6 do nosso código
tem uma bela evolução aqui
que é o seguinte.
Agora, além do operador de recebe,
a gente vai poder utilizar o operador de igualdade
Isso significa que eu vou poder comparar uma string com a outra.
Vou poder fazer coisas assim, ó.
Vou criar aqui, por exemplo,
S1, S2A, S2B.
S2A e S2B inicializadas com operador de recebe.
Posso imprimir o S1, S2A e S2B.
Depois eu posso comparar se S2A igual igual S2B.
Isso é muito legal.
Muito bem, gente.
E como é que eu consigo, então, programar esse tipo de coisa, aqui, tá?
Antes de programar esse tipo de coisa vocês tem que ficar atento ao seguinte.
Porque aqui eu tô colocando no formato alto nível. S2A igual igual S2B,
mas o que acontece, no final das contas, por baixo dos panos, por assim dizer, né.
Como é que acontece lá na baixaria técnica?
Acontece assim, ó.
Na prática, eu tô dizendo que o string S2A
Você tá chamando essa função de operação de igualdade
recebendo como parâmetro o S2B
e lá internamente dessa função,
ele vai comparar o conteúdo disso, com o conteúdo daquilo.
É isso que tá acontecendo.
Então, vou voltar ali pra implementação da classe,
voltando pro slide 31, pra ver.
Ora, o operador de igual,
ele compara se uma coisa é igual a outra,
então ele vai dizer se é true ou é false,
por isso que o retorno é bool.
À luz, inclusive, da literatura a respeito.
Se eu olhar a implementação.
Tá aqui ó.
É o operator==
que pertence à classe MinhaString,
o retorno é bool, e o parâmetro é a MinhaString.
Inclusive, prestem atenção, tá.
Isso aqui é parâmetro por cópia, por valor.
Lá internamente, o que eu faço?
Eu vou fazer o conteúdo de quem tá recebendo
um objeto do mesmo tipo que ele
ter seu conteúdo comparado
com o objeto do mesmo tipo que ele que tá vindo por parâmetro.
Aqui, como o conteúdo desses dois caras são vetores de caractere,
eu tô usando o strcmp do string.h.
Se eles forem iguais vai dar um retorno zero, que daí eu retorno true,
do contrário eu retorno false.
Graças essa implementação eu posso fazer isso aqui.
S2A igual S2B.
que vocês já sabem que na prática é isso que tá acontecendo.
Inclusive, se vocês programar assim, do jeito que tá aí, tá,
ele funciona.
Versão 7.
Eu disse pra vocês
que tanto quanto possível
a gente vai deixar os parâmetros constante.
Então, eu deixei aqui o operador de recebe constante,
deixei aqui também o operador de igual, constante.
Nesse caso, me deu um probleminha.
Porque a função strcpy
ela não aceita
ou não estava aceitando, em todo caso, quando eu fiz esse código
vetores constante,
ele aceita vetores normais.
Daí, aqui eu tive que fazer todo uma estratagema que é criar um auxiliar,
que é um vetorzinho de caractere auxiliar,
criado dinamicamente,
com o mesmo tamanho do que tá vindo por parâmetro +1, né.
Fazer a cópia pra depois poder ir fazer a comparação.
Por quê?
Basicamente, o strcmp, aqui, não aceita,
não aceitaria eu colocar direto aqui
o s aqui, né, de MinhaString, tanto porque, porque ele é constante.
Então, nesse caso aqui, teve essa peculiaridade.
Via de regra, a gente utiliza constância,
mas nem sempre a regra vai
se aplicar a todos os casos.
Tá aí um contraexemplo.
Além de ter essa dificuldade de implementação, tá.
Fazer toda essa manipulação com vetor auxiliar,
ainda, na chamada, não fica intuitivo.
Porque se esse cara vai receber constante como parâmetro.
ele vai comparar com constante e ele tem que ser constante na definição.
Tá vendo, ó.
Tem que definir constante lá.
Bom, aí a gente pode então, nesse contexto,
voltar a não ser constante.
Deixar como já estava
que é o caso aqui.
Inclusive, aqui,
eu criei, ademais,
mais uma sobrecarga de operador de recebe.
Eu já tinha um operador de recebe,
permitindo o operador receber um vetor de caracteres.
Só que eu gostaria também de poder receber outros objetos do tipo MinhaString
Tipo, fazer assim, além de fazer...
vou avançar um pouquinho aqui.
Além de fazer S2A, por exemplo, receber um vetor de caractere,
S2B receber S2A.
Por isso eu sobrecarreguei mais uma vez o operador de recebe
que é o que vocês tão vendo ali.
Tá vendo aqui, ó.
Então, recebe isso aqui, né.
E como é que você fez isso, professor? Tá ali, ó.
É que eu posso sobrecarregar com vários operadores, né.
Eu podia colocar mais um operator aqui, ó.
void operator= sendo o parâmetro um inteiro.
Aí eu pegaria aquele inteiro e transformaria em caractere
e armazenaria aqui dentro.
Aqui, não é o caso, aqui eu tô recebendo um MinhaString,
que tá ali, ó.
Daí eu faço o quê?
O que que é o MinhaString?
O que que é a essência do MinhaString?
É um vetor de caracteres.
Então, eu venho aqui e chamo o operador recebe,
que recebe o vetor de caractere,
que é aquele ali,
que vai executar aquilo ali.
Aqui, na prática, eu fiz um encadeamento,
de chamadas de definição e chamada de operadores de recebe.
Tô definindo o operador de recebe pra trabalhar com MinhaString
e uso o próprio operador de recebe,
agora com vetor de caractere,
pra tratar a coisa aqui no âmago
Agora se você prestar atenção
que tanto aqui nesse operador de recebe
sobrecarregado com MinhaString
quanto no operador de igualdade
sobrecarregado que também
recebe MinhaString.
Os dois tão recebendo MinhaString com parâmetro por valor.
Não é isso?
E aqui tem uma observação.
Cuidado, como s é uma cópia de outro objeto,
s que tá ali,
como s é uma cópia de outro objeto,
ocorre também a cópia de ponteiro,
o que o leva a desalocar a memória em s,
afetando a string passada como parâmetro.
Isto porque, no final das contas,
o ponteiro da cópia s
aponta para o mesmo local que o ponteiro do objeto original.
Isso é uma coisa muito importante, muito importante
que fica mais entendível
a hora que eu mostro na execução do código ali, tá.
Vocês tão vendo a execução do código aqui, não tão?
Então, tá tudo muito bonito etc,
tá fazendo o S2B receber o S2A.
Legal.
Joia.
Aqui, eu tô mostrando, inclusive, que eu posso usar
o operador assim ou assado,
e aqui vem a observação derradeira.
Na prática, a hora que eu faço aqui, por exemplo, tá.
S2A == S2B.
Vocês sabem que, na prática, o que tá acontecendo é isso, tá.
Eu tô dizendo pro S2A chamar a sua função operator==
que recebe como parâmetro um outro objeto de MinhaString
que é o S2B, nesse caso.
E vejam o que acontece aqui.
Cuidado.
como s é uma cópia de S2B,
ocorre também a cópia do ponteiro,
o que leva a desalocar memória em s,
afetando S2B no tocante ao ponteiro.
Isto porque, no final das contas,
o ponteiro da cópia aponta para o mesmo local que o ponteiro do objeto original.
De fato.
Se você olhar com atenção, internamente,
dentro do operator==
que recebe o S2B, vai ter o s.
s é um parâmetro por valor, ou seja,
o s é uma cópia de S2B.
O s vai ter dentro dos seus atributos
o mesmo conteúdo dos caras que estão em S2B.
Inclusive, o pStr de s.
Vou voltar aqui.
O pStr de s
vai ser o mesmo de S2B.
Só que você tem que ver que depois da execução dessa função,
o s é uma variável temporária, é um parâmetro.
O s que vai terminar de executar.
O que acontece quando o s deixa de executar?
Perdão. O que acontece quando o operator== deixa de executar?
O que acontece com o s? O s vai sumir.
O que acontece, agora sim, quando o s some?
Mesmo ele sendo um objeto temporário,
todo objeto que é destruído ele executa a destrutora,
não executa a destrutora?
O que acontece lá na destrutora?
A gente já viu lá, tá,
nas versões passadas, ele faz um delete
desse cara aqui, ó.
Se ele faz um delete, ele vai deletar a memória apontada por pStr.
Só que s, naquela chamada em questão, gente,
recebeu como parâmetro o S2B.
Assim, o pStr de s
aponta pro mesmo lugar que o pStr de S2B.
s vai deixar de existir.
Vai fazer um delete
da memória que ele tá apontando pro pStr
e fazendo com que esse cara aqui aponte pra um nada.
Porque aquele objeto temporário
apagou o apontamento que esse cara tava utilizando.
Isso é um pepino sem tamanho.
Daí, ao meu alerta de atenção no próximo slide.
Ao utilizar o atributo pStr alocado dinamicamente,
deve-se tomar cuidado para que nenhuma função
receba a string em passagem por valor.
Isto porque a variável estática criada
irá copiar o endereço daquela passada como parâmetro
e terá sua memória alocada dinamicamente
desalocada pela sua destrutora ao sair do seu escopo.
Que é o que eu disse, tá.
Como s recebeu lá o S2B.
s recebeu S2B.
O s passou no seu pStr
a apontar pro mesmo lugar que pStr do S2B, aqui, gente.
E aí, a hora que o s terminou de executar, ele executa a destrutora
e elimina a memória apontada por pStr
deixando o S2B a ver navios, ali.
Porque ele vai ter o pStr apontando pro nada.
Daí essa atenção.
De uma forma mais simples:
Ao trabalhar com classes que possuem ponteiros
como um de seus atributos,
ou memória dinamicamente alocada, né, por isso ponteiros,
não utilize passagem por valor.
Então, é isso que a gente tá dizendo, tá.
Ou seja, não deixe esse tipo de coisa acontecer.
Não deixe acontecer,
porque, inevitavelmente,
você vai cair nessa situação.
O s vai ser uma cópia do objeto original,
qualquer ponteiro que o s tenha
vai apontar pro mesmo lugar de memória que o objeto original.
Só que o s ele é efêmero, ele já termina de executar aqui, ó.
termina de executar, já termina de existir
executando a destrutora.
A destrutora, lá, vai desalocar memória.
E a memória que ele vai desalocar
é a memória apontada por pStr
que é a mesma memória apontada por s nesse contexto dado,
e por S2B.
Assim S2B vai ter a sua memória desalocada,
por meio do s que copiou os seus atributos, inclusive, seu ponteiro.
Então, releiam de volta isso aqui.
Lembrando que
ao trabalhar com classes que possuem ponteiros como seus atributos
para fins de memória alocada dinamicamente, né,
não utilizar passagem de parâmetro por valor
por esse motivo explicado e re-explicado por mim aqui.
Daí, a gente vai pra classe MinhaString 8B.
MinhaString 8B que tenta resolver isso.
Se eu não posso usar parâmetro por valor,
eu vou usar parâmetro por referência.
Eu posso usar a maneira clássica
que é através de ponteiro.
Tá ali, ó.
Então, aqui, o operador de recebe, tanto ali quanto aqui,
porque aqui acontece o mesmo problema, tá.
By the way.
E eu usei ali como comparação, como exemplo,
o operador de igual, tá.
Mas o operador de recebe ia acontecer a mesma coisa.
Bem entendido.
Então, o que acontece aqui?
Tô usando passagem de parâmetro por referência,
optei nesse momento por usar a abordagem clássica de ponteiro.
Se é ponteiro, na definição, tem asterisco aqui.
Vocês tão vendo ali, ó.
Bom, se eu tô usando ponteiro, aqui, na implementação,
eu vou ter que usar o operador de seta e não de ponto
conforme já é largamente sabido.
E lá na chamada da função
eu tenho que usar o &.
Professor, mas parece que fica estranho isso.
É porque você tem que lembrar que na prática é isso aqui, tá.
Tô fazendo o S2B
operator=,
ou seja, eu tô chamando o método operator= de S2B,
sendo que esse método recebe por parâmetro aquilo ali, ó.
Se o parâmetro é ponteiro, ele exige, então, um endereço
Eu concordo que não fica intuitivo.
Nem ali, e nem mesmo aqui,
muito embora aqui fique mais entendível,
mas é isso que acontece.
Se na prática eu tô passando um parâmetro
por referência usando um ponteiro
eu tenho que colocar o & na variável que eu tô passando por referência.
E por que eu tô passando por referência aqui?
Ué, pra não ter o problema da cópia de valor, lá, desalocando memória
de maneira equivocada.
Bom, eu não tenho como encontrar um compromisso
entre a passagem de referência
que fique mais pedagógica
e que me permite tá utilizando esse & na chamada?
Tem, né, gente.
Ainda bem que a linguagem C tem aquela segunda opção
de passagem de referência ou passagem de parâmetro por referência
que é justamente a referência escondida.
Pra referência escondida você põe um & aqui na definição.
Volta a utilizar o operador de ponto normalmente,
mas a passagem é por referência.
É um ponteiro escondido
por assim dizer.
Sendo esse ponteiro escondido
eu só ponho o & na definição
volto a usar o operador de ponto aqui, não preciso usar o operador de seta
e, na chamada, eu só passo o parâmetro
sem me preocupar com por & na frente.
Inclusive, se eu por dá erro.
E aí, a coisa volta a ser pedagógica, tá.
Muito embora eu não goste de usar operador de...
perdão, não goste de usar referência escondida
na passagem de parâmetro
nos meus métodos, em geral.
Aqui, na sobrecarga de operador, ele se faz útil,
porque deixa a coisa intuitiva
e evita aquele tipo de
desalocação de memória equivocada da
passagem de parâmetro por valor,
além de melhorar desempenho etc e tal.
Então, resolvida essa questão.