Day: September 4, 2021

POO, introdução III

………..herança
se for composição um classe tem outras classes
se for derivação, sendo um caso particular com caracteristicas adicionais
uma classe derivada: tem membros herdados, e membros adicionais

class Terrestre : public Veiculo { // classe derivada: Terrestre, classe base: Veiculo
int velocidade;
public:
// …
};
a classe derivada acede aos gets e sets das funções que herdou

class Terrestre : public Veiculo
por se usar public:
herança publica
os membros publicos da classe base, são também da classe derivada
e por essa via será também um membro publico da classe derivada

class Terrestre : private Veiculo
ou
class Terrestre : Veiculo
por se usar private:
os membros publicos da herança, são privados de terrestre

o uso de protected:
é um nivel de acesso
são visiveis na classe
nas classes derivadas são visiveis
no código exterior, que não a classe ou classe derivadas não são acessiveis

Forma de derivação
private
membros que na classe base são:
private
protected
public
na classe derivada são:
inacessíveis
private
private

Forma de derivação
protected
membros que na classe base são:
private
protected
public
na classe derivada são:
inacessíveis
private
private

Forma de derivação
public (normal)
membros que na classe base são:
private
protected
public
na classe derivada são:
inacessíveis
protected
public

p.e.:
class Veiculo {
int peso;
protected:
int comprimento;
public:
Veiculo( int p = 0, int c=0): peso (p), comprimento(c){}
int getPeso()const{ return peso; }
int getComprimento()const{ return comprimento; }
void setPeso( int p){ peso = p;}
void setComprimento( int c){ comprimento = c;}
};

class Terrestre : public Veiculo {
int velocidade;
public:
Terrestre(int p=0,int c=0,int vel=0):Veiculo(p,c) {
velocidade = vel;
}

int getVelocidade()const{ return velocidade; }

void setVelocidade( int vel){ velocidade = vel;}

void imprime(ostream & saida )const{
saida << “Peso: ” << getPeso() << endl;
saida << “Compr: ” << comprimento << endl;
saida << “Veloc: ” << velocidade << endl;
}
};

int main(){
Veiculo v(700);
cout << “Peso: ” << v.getPeso() << endl;
//cout <<“Compr:” << v.comprimento << endl; ERRO , é protected em veiculo

Terrestre t(600, 4, 90);
cout << “Peso: ” << t.getPeso() << endl;
//cout <<“Compr:” << t.comprimento << endl; ERRO, é protected em veiculo

cout<<“Veloc: “<< t.getVelocidade() << endl;
t.imprime( cout);
}

Quando um objecto de uma classe derivada é destruído, são chamados os destrutores por ordem inversa da ordem de derivação: primeiro o destrutor da classe derivada, depois o destrutor da classe base

situação:
temos duas classes iguais, temos que usar o nome da herança Herança::funcao

situação: uma classe derivada e composta
p.e.:
class Base {
int i;
public:
Base(int ii = 0) : i(ii) { cout << “Base(int ii)\n”; }
~Base() { cout << “~Base()\n”; }
void imprime(ostream& saida)const {
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk = 0) : k(kk) { cout << “Membro(int kk)\n”; }
~Membro() { cout << “~Membro()\n”; }
void imprime(ostream& saida)const {
saida << “Membro: k=” << k << endl;
}
};
class Derivada : public Base {
int j;
Membro m;
public:
Derivada(int jj) : Base(jj), j(jj), m(jj) {
cout << “Derivada(int jj)\n”;
}
~Derivada() { cout << “~Derivada()\n”; }
void imprime(ostream& saida)const {
saida << “Derivada: j=” << j << endl;
Base::imprime(saida);
m.imprime(saida);
saida << endl;
}
};
int main() {
Derivada d(2);
cout << “\nValores em d:\n”;
d.imprime(cout);
// chamada ao destrutor para d
}

situação: a classe derivada nao tem construtor nem destrutor
p.e.:
class Base {
int i;
public:
Base(int ii = 0) : i(ii) { cout << “Base(int ii)\n”; }
~Base() { cout << “~Base()\n”; }
void imprime(ostream& saida)const {
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk = 0) : k(kk) { cout << “Membro(int kk)\n”; }
~Membro() { cout << “~Membro()\n”; }
void imprime(ostream& saida)const {
saida << “Membro: k=” << k << endl;
}
};

class Derivada : public Base {
Membro m;
public:
void imprime(ostream& saida)const {
saida << “Derivada: \n”;
Base::imprime(saida);
m.imprime(saida);
saida << endl;
}
};

int main() {
Derivada d;
cout << “\nValores em d:\n”;
d.imprime(cout);
// destruição de d
}

análise:
surge o construtor gerado automaticamente
é chamado o construtor da base e do membro
e na destruição invoca o membro e depois a base
também é respeitado o funcionamento da base e derivada, pela ordem com que são construídos e destruídos

………..funções com tratamento especial relativamente à herança
situações:
Construtores:
um construtor de uma classe derivada chama explicitamente ou implicitamente o construtor da classe base correspondente.
Construtor por cópia:
no construtor por cópia de uma classe derivada é preciso chamar explicitamente o construtor por cópia da classe base.
Destrutores:
um destrutor de uma classe derivada chama o destrutor da classe base correspondente.
Operador atribuição:
a atribuição entre dois objectos de uma classe pode não ser suficiente para fazer a atribuição entre dois objectos de uma classe derivada. O operador atribuição não é herdado normalmente como pode ver-se no exemplo seguinte.

p.e.:

class Base {
int i;
public:
Base(int ii) : i(ii) {}
Base(const Base & b) : i(b.i) {
cout << “Base(const Base &)\n”;
}
Base & operator=(const Base & b){
i = b.i;
cout << “Base::operator=()\n”;
return *this;
}
void imprime( ostream & saida)const{
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk) : k(kk) {}
Membro(const Membro& m) : k(m.k) {
cout << “Membro(const Membro&)\n”;
}
Membro & operator=(const Membro & m){
k = m.k;
cout << “Membro::operator=()\n”;
return *this;
}
void imprime( ostream & saida)const{
saida << “Membro: k=” << k << endl;
}
};

class Derivada : public Base {
int j;
Membro m;
public:
Derivada(int jj) : Base(jj), j(jj), m(jj) {}

void imprime( ostream & saida)const{
saida << “Derivada: j=” << j << endl;
Base::imprime(saida);
m.imprime(saida);
}
};
int main() {
Derivada d(2);
cout << “\nConstrução por copia : ” << endl;
Derivada d2 = d; // construção por copia
cout << “\nValores em d2:\n”;
d2.imprime(cout);
Derivada d3(3);
cout << “\nAtribuicao: ” << endl;
d3 = d; // atribuição

cout << “\nValores em d3:\n”;
d3.imprime(cout);
}

………..herança, composição e construtor por cópia
outra versão da classe Derivada, com construtor por cópia e operador atribuição explícitos.
se nada dissermos na derivada, o construtor que é chamado é o da base por omissão e ainda assim temos que ter em atenção de que Base pode preencher com coisas por default
assim é necessário que se explicitem nestas chamadas a lista de inicialização do construtor por cópia, seriam chamados em seus lugares os correspondentes pois caso não sejam, construtores por defeito. O sub-objecto adquirido por herança e os membros objectos seriam inicializados pelos construtores por defeito em vez de receberem cópias dos correspondentes sub-objectos do objecto da classe Derivada considerado origem da cópia.
p.e.:

class Base {
int i;
public:
Base(int ii) : i(ii) {}
Base(const Base& b) : i(b.i) {
cout << “Base(const Base &)\n”;
}
Base& operator=(const Base& b) {
i = b.i;
cout << “Base::operator=()\n”;
return *this;
}
void imprime(ostream& saida)const {
saida << “Base: i=” << i << endl;
}
};

class Membro {
int k;
public:
Membro(int kk) : k(kk) {}
Membro(const Membro& m) : k(m.k) {
cout << “Membro(const Membro&)\n”;
}
Membro& operator=(const Membro& m) {
k = m.k;
cout << “Membro::operator=()\n”;
return *this;
}
void imprime(ostream& saida)const {
saida << “Membro: k=” << k << endl;
}
};
class Derivada : public Base {
int j;
Membro m;
public:
Derivada(int jj) : Base(jj), j(jj), m(jj) {}
Derivada(const Derivada& d) : Base(d), m(d.m), j(d.j) {
cout << “Derivada(const Derivada&)\n”;
}
Derivada& operator=(const Derivada& d) {
Base::operator =(d);
m = d.m;
j = d.j;
cout << “Derivada::operator=()\n”;
return *this;
}
void imprime(ostream& saida)const {
saida << “Derivada: j=” << j << endl;
Base::imprime(saida);
m.imprime(saida);
}
};

………..polimorfismo, clases derivadas
uma espécie de, ou um tipo de, é uma herança
o upcasting, é feito do tipo ascendente, e não é preciso fazer nada, nenhum cast explicito.
p.e.:
class Veiculo {
/* … */
};
class Terrestre : public Veiculo {
/* … */
};
class Automovel : public Terrestre {
/* … */
};
assim é possivel que:
conversão de um ponteiro para Terrestre num ponteiro para Veiculo;
conversão de um ponteiro para Automovel num ponteiro para Veiculo;
conversão de uma referência para Terrestre numa referência para Veiculo;
conversão de uma referência para Automovel numa referência para Veiculo.
isto é:
class Veiculo {
/* … */
};
class Terrestre : public Veiculo {
/* … */
};
int main(){
Veiculo *p;
Veiculo v;
Terrestre t;
p=&v;
p=&t; // upcasting
Veiculo & r = t; // upcasting
}

p=&t; // upcasting
um ponteiro para uma classe base pode receber um endereço de um objecto de uma classe derivada ou derivada da derivada..
pode assim ser feito por inteiro ou pode ser feito por referencia:
Veiculo & r = t; // upcasting

o upcasting é sempre verdade
o downcasting nem sempre é verdade

uso na base da expressão virtual na base e nas suas funções (desta forma será sempre usada a referência para o objeto que é referido)
e na derivada faz-se uso nas funções de override

Um ponteiro ou uma referência para uma classe base podem apontar para, ou referir, um objecto dessa classe ou de uma classe derivada directa ou indiretamente e é invocada:
é executada a versão da função da classe correspondente ao ponteiro ou referência, se a função não for virtual;
é executada a versão da função correspondente à classe a que pertence o objecto, se a função for virtual;

p.e.:
class Veiculo { //classe base
int peso;
public:
Veiculo(int p = 0) : peso(p) {}
virtual void imprime(ostream& saida) const { //virtual
saida << “\nPeso: ” << peso << endl;
}
};

class Terrestre : public Veiculo { //classe derivada
int velocidade;
public:
Terrestre(int p = 0, int vel = 0) : Veiculo(p), velocidade(vel) {}
void imprime(ostream& saida)const override { //lidar com o virtual
Veiculo::imprime(saida);
saida << “Veloc: ” << velocidade << endl;
}
};

class Automovel :public Terrestre { //classe derivada de derivada
int cilindrada;
public:
Automovel(int p = 0, int vel = 0, int cil = 0) : Terrestre(p, vel), cilindrada(cil) {}
void imprime(ostream& saida)const override { //lidar com o virtual
Terrestre::imprime(saida);
saida << “Cilind: ” << cilindrada << endl;
}
};

class Aereo :public Veiculo {
int altura_voo;
public:
Aereo(int p = 0, int a = 0) : Veiculo(p), altura_voo(a) {}
//não tem imprime recebe por herança da base
};
ostream& operator<<(ostream& saida, const Veiculo& x) {
x.imprime(saida);
return saida;
}

int main() {
Veiculo* a[] = { // array de ponteiros para a classe base, Veiculo
new Veiculo(700), // inicialização
new Terrestre(800,80), // inicialização: upcasting
new Automovel(900,90,1400), // inicialização: upcasting
new Aereo(2000,50) // inicialização: upcasting
};
int n = sizeof(a) / sizeof(Veiculo*); //contar elementos
for (int i = 0; i < n; i++) //perocrrer o array
a[i]->imprime(cout); // ou cout << *a[i];

for (int i = 0; i < n; i++)
delete a[i];
}

………..polimorfismo, classes abstractas e funções virtuais
uma classe é abstracta se a classe tem pelo menos um função virtual pura
uma classe abstracta não pode criar objetos
uma classe abstracta pode ter ponteiros e referências
faz sentido existir um prototipo da função que representa apenas um interface comum das classes desta hierarquia
p.e.:
virtual string meio() = 0; // é uma função virtual pura e fica uma classe abstracta
e esta classe pode conter ou pode não conter uma definição da função.
vais er necessário definir na classe derivada todas as funções virtuais puras adquiridas por herança

a classe derivada continua abstracta, se não for concretizada pelo menos uma função virtual pura adquirida por herança e por esse motivo não pode haver objectos desta classe

p.e.:
class Veiculo { //classe abstracta
int peso;
public:
Veiculo(int p = 0) : peso(p) {}
virtual void imprime(ostream & saida) const = 0; //função abstracta, ou virtuais puras
virtual string meioOndeSeDesloca()const = 0; //função abstracta, ou virtuais puras
int getPeso()const { return peso; }
};

class Terrestre : public Veiculo {
int velocidade;
public:
Terrestre(int p = 0, int vel = 0) :
Veiculo(p), velocidade(vel) {}
void imprime(ostream & saida)const override {
saida << “\nPeso: ” << getPeso() << endl << “Veloc: ” << velocidade << endl;
} //faz o overide da função abstracta imprime, da base: herdou duas, concretizou uma
// não se define ainda a função meioOndeSeDesloca(), e por este motivo esta é uma classe abstracta
};

class Automovel :public Terrestre { //esta classe tem as duas funções concretas: concretizou meioOndeSeDesloca e redefiniu a concreta imprime
int cilindrada;
public:
Automovel(int p = 0, int vel = 0, int cil = 0) : Terrestre(p, vel), cilindrada(cil) {}
virtual void imprime(ostream & saida)const override {
Terrestre::imprime(saida);
saida << “Cilind: ” << cilindrada << endl;
} //redefine a função imprime
string meioOndeSeDesloca()const override {
return string(” desloca-se em estrada \n”);
}//herdou uma função asbtracta e concretizou
};
class Aereo :public Veiculo { //concretizou as duas funções, logo é uma classe concreta
int alturaVoo;
public:
Aereo(int p = 0, int a = 0) : Veiculo(p), alturaVoo(a) {}
void imprime(ostream & saida)const override {
saida << “\nPeso: ” << getPeso() << endl << “Altura de voo: ” << alturaVoo << endl;
}
string meioOndeSeDesloca()const override {
return string(” desloca-se por via aerea \n”);
}
};
ostream & operator<<(ostream & saida, const Veiculo & x){
x.imprime(saida);
return saida;
}
int main() {
Veiculo* a[] = { //pode-se criar um array de ponteiros de classes abstractas
//new Veiculo(700), // ERRO, é uma classe abstracta, e não se podem criar objectos de classes abstractas
//new Terrestre(800,80), // ERRO, é uma classe abstracta, e não se podem criar objectos de classes abstractas
new Automovel(900,90,1400), //é uma classe concreta
new Aereo(2000,50) //é uma classe concreta
};
int n = sizeof(a) / sizeof(Veiculo*);
for (int i = 0; i < n; i++)
cout << *a[i] << a[i]->meioOndeSeDesloca();
delete a[0];
delete a[1];
}

………..polimorfismo, destrutores virtuais
O destrutor de uma classe base pode não ser suficiente para “destruir” um objecto de uma classe derivada
o uso delete, vai invocar o destrutor:
se o destrutor for virtual na classe do ponteiro, vai ser usado o destrutor de classe apontado
se o destrutor não for virtual na classe do ponteiro, vai ser usado o destrutor da classe ponteiro, que não é o destrutor da classe do objeto que está a ser destruído

na herança, o destrutor da classe derivada é executado mas depois chama o destrutor da base

p.e.:
class Veiculo {
int peso;
public:
Veiculo(int p = 0) : peso(p) {}
virtual ~Veiculo() { cout << “Destruindo Veiculo\n”; } //é virtual
};
class Aereo :public Veiculo {
int altura_voo;
string * historico;
int n;
public:
Aereo(int p = 0, int a = 0) : Veiculo(p), altura_voo(a), historico(nullptr), n(0) {}
virtual ~Aereo()override { //foi feito o override
delete[] historico;
cout << “Destruindo Aereo\n”;
}
};
int main() {
Veiculo* p = new Aereo(2000, 50);
// …
delete p; // funciona o destrutor de Aereo
// porque o destrutor foi declarado como virtual na classe Veiculo
}

………..polimorfismo, Colecção polimórfica e composição, duplicação polimórfica
existem agora elementos diferentes na colecção
(ver exemplo do aquário)
queremos fazer uma copia (duplicar), se fossem todos iguais eram com o new, mas neste caso temos elementos diferentes.
a solução:
duplicação polimorfica
na classe base é definida uma função virtual que duplica o objeto
mas não podem haver objectos da classe base, porque ela é a abstracta
nas classes derivadas é concretizada a função duplica, e esta função vai fazer o return new Derivada(*this) <-“é o construtor por cópia gerado automaticamente” a funcionar
e na classe base, surge na função de atribuição a duplicação dos objetos, recorrendo a Peixe* p = orig.peixes[i]->duplica();

p.e.:
class Peixe {
// …
};
class TetraPeixe : public Peixe { //classe derivada
// …
};
class NeonPeixe : public Peixe { //classe derivada
// …
};

class Aquario {
// o aquario contém uma colecção de peixes que podem ser de diversos tipos
vector<Peixe*> peixes; //vector de ponteiros para peixes, e pode ser cada um de sua classe, é um vector polimorfico
// …
public:
Aquario() = default;
// Os peixes são criados e destruídos no aquario
// A relação entre o aquário e os peixes é de composição
Aquario(const Aquario& orig); // construtor por copia
Aquario& operator=(const Aquario& orig); // operador atribuicao
virtual ~Aquario(); // destrutor
// …

// funcao abstracta, nao se podem criar objectos desta classe
virtual void alimentar(int quantidade, Aquario* aquario) = 0;
// duplicação polimórfica dos peixes
// como a classe é abstracta, esta função também tem que ser abstracta porque
// não se podem criar objectos da classse Peixe
virtual Peixe* duplica()const = 0; //duplicação polimórfica
};

Aquario::~Aquario() {
for (Peixe* p : peixes) {
delete p;
}
}

class TetraPeixe : public Peixe {
// …
public:
// …
// cria uma cópia de si mesmo em memória dinâmica
Peixe* duplica()const override;
};
Peixe* TetraPeixe::duplica()const { //garante a duplicação
return new TetraPeixe(*this);
}

class NeonPeixe : public Peixe {
// …
public:
// …
// cria uma cópia de si mesmo em memória dinâmica
Peixe* duplica()const override;
};
Peixe* NeonPeixe::duplica()const { //garante a duplicação
return new NeonPeixe(*this);
}

Aquario& Aquario::operator=(const Aquario& orig) { //operador atribuição
// prevencao da auto-atribuicao
if (this == &orig) {
return *this;
}
// libertar memoria dinamica do primeiro membro da atribuição
for (int i = 0; i < peixes.size(); i++) {
delete peixes[i];
}
//esvaziar o vector
peixes.clear();
// copiar a informacao de orig, duplicando os objectos dinamicos do tipo Peixe
for (int i = 0; i < orig.peixes.size(); i++) {
Peixe* p = orig.peixes[i]->duplica();
peixes.push_back(p);
}
// …
return *this;
}

Aquario::Aquario(const Aquario& orig) { //construtor por cópia
//funcionou o construtor por omissao do vector de ponteiros
//o vector está vazio
// não há ponteiros com valores indefinidos
*this = orig;
}

Tags : , , , , ,

POO, introdução II

………..Conversões de tipo primitivos
p.e.:
long l = 10;
int i = static_cast<int>(l);

………..Conversões de tipo de objectos das classes
de um tipo qualquer num objeto da nossa classe e é feito com construtores que aceitam um só argumento do tipo da origem quando é de um objecto da nossa classe num outro tipo são feitas com operadores de conversão e estas conversões podem ser implícitas ou explícitas.

p.e.:
conversão de um outro tipo num objecto da nossa classe
class Complexo {
double a; // parte real
double b; // coeficiente da parte imaginária
public:
// construtor que admite um argumento => conversão:
// argumento -> objecto da classe
Complexo(double aa = 0, double bb = 0);
Complexo & operator+=(const Complexo & x); //operador membro
// …
};
Complexo operator+(const Complexo & x, const Complexo & y);
ostream & operator<<(ostream & saida, const Complexo & x);
assim:
Complexo(double aa = 0, double bb = 0);
pode receber nenhum argumento
um argumento //-> esté o que interessa neste caso
ou dois argumentos

conversão implícita:
Complexo b = 2; // pois o que acontece é Complexo b = Complexo(2)
conversão explicita
a = b + Complexo(2); //

………..extra: operador + e – devem ser global, recomendado, são mais abrangentes
ERRO:
a = 4 + b ,
pois neste caso equivaleria a
a = 4.operator+( b).
sendo global:

………..Construtores e conversões , Construtores explicit
p.e.:
na classe String,
String(int n)
e é feito na iniclização:
String s = ‘a’; //estávamos a reservar bytes
então podemos ter um conversor com a palavra explicit ele só vai trabalhar quando quisermos
class String{
// …
String(const char * s); // construtor conversor, conversão implícita
explicit String ( int n); // construtor conversor, conversão explicita
};

………..operador de conversão
é uma função obrigatoriamente membro
tem o nome operator
tem o tipo de destino
não tem parâmetros
não tem tipo de retorno, pois retornar o tipo de destino
const não altera o objecto
p.e.:
class Relogio {
int hora; // 0 – 23
int minuto; // 0 – 59
int segundo; // 0 – 59
public:
Relogio( int hora = 0, int minuto = 0, int segundo = 0);
operator int( )const; //serve para converter em inteiro
//…
};
e o operator surgia como:
Relogio::operator int( )const{
return segundo+60*(minuto+60*hora);
}
eficiência:
int main( ){
Relogio t(2, 10, 5);
int s = static_cast<int>(t); // conversão explicita
cout << “\n Total de segundos: ” << s;
s = t; // conversão implícita
cout << “\n Total de segundos: ” << s;
}

assim as Conversões implícitas devem ser para:
argumento com que uma função é chamada para o tipo do correspondente parâmetro com que é definida;
valor de retorno de uma função para o tipo de retorno especificado no protótipo da função;
operandos em expressões;
inicializadores.

………..Operador e Opção recomendada
resumo:
operadores unários
funções membros

=()[] operadores de conversão
têm que ser funções membros

+= -= /= *= etc (observar a diferença de “estatuto” entre o 1º e o 2º operandos)
funções membros

outros operadores binários (operandos com “estatuto” análogo)
funções globais

operadores binários em que o 1º operando não pertence à classe em causa da classe em causa
não podem ser funções membros

(construtores por cópia, operador atribuição, .. )
………..composição
a associação (relacionamento de duas classes) de objectos pode ter duas formas:
ou agregação (ter, tem, agrega p.e.: classe A tem objecto classe B, sendo que os objectos da classe B existem sempre, mesmo que A seja destruída; p.e.: turma e alunos)
ou composição (tem mas, se a classe A uca objecto da classes B, se A desaparecer, B também desaparece)

composição vs agregação
p.e.:
existe um relação de composição
se o poema for destruído, os versos são destruídos
um objecto poema tem posso exclusiva dos seus versos
class Poema {
string titulo;
string * pVersos; // ponteiro para um array dinâmico de strings
int nVersos; // obrigatório se não for char *c: numero de elementos do array dinâmico
public:
Poema(const string & t);
~Poema(); // destrutor, para libertar antes
void acrescentaVerso(const string & verso);
void eliminaVerso(int ordem);
// . . .
string getAsString()const;
};

exame: não se pode usar vectores, para usar este tipo de notação

assim surge uma situação de a = b
a atribuição não pode funcionar por omissão, a solução é usar um operador atribuição, e desta forma tudo o que a atribuição faz vai ficar bloqueado
o operador atribuição vai surgir na classe
possibilitando que os objectos sejam fisicamente independentes
public:
Poema(const string & t);
void acrescentaVerso(const string & verso);
void eliminaVerso(int ordem);
Poema & operator=(const Poema & ob);//operador atribuição, operador membro
~Poema(); // destrutor
// . . .
string getAsString()const;
};

//operador atribuição a=b
Poema& Poema::operator=(const Poema& ob) { //é o A, e é o *this, sendo B o op
if (this == &ob) { // prevenção da auto-atribuição
return *this;
}
// se os membros da atribuição forem objetos diferentes
titulo = ob.titulo;
nVersos = ob.nVersos;
// libertar a memoria dinâmica usada exclusivamente pelo primeiro membro
delete[] pVersos;
pVersos = nullptr;
// se a origem da copia for um poema sem versos …
if (ob.pVersos == nullptr || ob.nVersos == 0) {
return *this;
}
// reservar memoria dinâmica para conter uma copia exclusiva dos versos do segundo membro
pVersos = new string[ob.nVersos]; //array dinamico, reserar espaço em memória
// fazer essa copia
for (int i = 0; i < ob.nVersos; ++i) {
pVersos[i] = ob.pVersos[i];
}
return *this;
}
mesmo que a classe não tenha o construtor por cópia, ela existe sempre pois é gerada automaticamente como a atribuição.
e constrói por cópia membro a membro
neste caso copiar o ponteiro pVersos, ficando todos a apontar para a mesma zona de memória

………..construtor por cópia
análogo à atribuição

p.e.:
na classe Poema surge o construtor por cópia
class Poema {
string titulo;
string* pVersos; // ponteiro para um array dinamico de strings
int nVersos; // numero de elementos do array dinamico
public:
Poema(const string& t);
void acrescentaVerso(const string& verso);
void eliminaVerso(int ordem);
Poema& operator=(const Poema & ob);//operador atribuicao, operador membro
Poema(const Poema & ob); //construtor por cópia
~Poema(); // destrutor
// . . .
string getAsString()const;
};

e a função vem:
Poema::Poema(const Poema& ob):titulo(ob.titulo), nVersos(ob.nVersos) {
// titulo e nVersos inicializados com os mesmos valores de ob
// se a origem da copia for um poema sem versos …
if (ob.pVersos == nullptr || ob.nVersos == 0) {
pVersos = nullptr;
return;
}
// reservar memoria dinamica para onde copiar os versos de ob
pVersos = new string[ob.nVersos];
// copiar o conteudo do array dinamico
for (int i = 0; i < nVersos; ++i) {
pVersos[i] = ob.pVersos[i];
}
}

o construtor por cópia funciona:
quando se cria um objecto por cópia de outro;
quando passamos um objecto por valor a uma função (surge um novo objecto);
quando uma função retorna um objecto por valor, sendo que o que sair no return é a cópia;
assim
O construtor por cópia tem usualmente a forma
EstaClasse::EstaClasse( const EstaClasse & ob)

as diferenças entre Construtor por cópia / operador atribuição:
operador atribuição tem a prevenção da auto-atribuicao, que não aparece no construtor por cópia;
no construtor por cópia não é preciso tratar da memória velha, pois o objecto está a ser criado;
e no construtor por cópia não tem os retornos;

assim sendo podíamos escrever o construtor por cópia em função do operador atribuição:
(fazer um à custa do outro)

Poema::Poema(const Poema & ob):pVersos(nullptr){ // preparar pVersos para ser operando de delete
// preparar o objeto que esta a ser criado para ser alvo do operador atribuicao
*this = ob; // aplicar o operador atribuicao: operator=(s)
}
Poema & Poema::operator=(const Poema & ob){
if (this == &ob) {// prevencao da auto-atribuição
return *this;
}
// se os dois membros da atribuicao forem objetos diferentes
titulo = ob.titulo;
nVersos = ob.nVersos;
// libertar a memoria dinamica usada exclusivamente pelo primeiro membro
delete[] pVersos;
pVersos = nullptr;
// etc.
// . . .
}

void Poema::acrescentaVerso(const string & verso){
// reserva espaço para mais um
string * aux = new string[nVersos + 1];
// copia os versos existentes
for (int i = 0; i < nVersos; ++i) {
aux[i] = pVersos[i];
}
// o novo verso está em último lugar
aux[nVersos] = verso;
++nVersos; // incrementa o número de versos
delete[] pVersos; // liberta array dinâmico anterior
// pVersos aponta para o novo array dinamico
pVersos = aux;
}

void Poema::eliminaVerso(int ordem) {
if (ordem < 0 || ordem >= nVersos){ return; } //fora dos limites
if (nVersos == 1) {// se retirar o ultimo
delete[] pVersos;
pVersos = nullptr;
nVersos = 0;
return;
}
// reserva espaço ajustado para menos um
string * aux = new string[nVersos – 1]; //nao for o ultimo, aloco novo espaço
// copia os versos anteriores ao verso a eliminar
for (int i = 0; i < ordem; ++i) {
aux[i] = pVersos[i];
}
// copia os versos posteriores ao verso a eliminar
for (int i = ordem; i < nVersos – 1; ++i) {
aux[i] = pVersos[i + 1];
}
–nVersos; // decrementa o número de versos
delete[] pVersos; // liberta array dinâmico anterior
pVersos = aux; // pVersos aponta para o novo array dinamico
}

………..composição de objectos
um objecto dentro de outro objecto
quando o objecto for criado, antes funcionam todos os construtores dos sub-objectos. funcionam porque nos especificamos ou então é por omissao.
p.e.:
class String {
char* p;
void setString(const char * s);
public:
String(); // construtor por defeito
String(const char * s); // construtor conversor
String(const String & s); // construtor por copia
~String(); // destrutor

const char* getString()const;
ostream & imprime(ostream & saida)const;
String & operator=(const String & x); // operador atribuicao
String & operator+=(const String & x);

static int strTam(const char * s);
};
String::String() {
p = 0;
cout << “Constr. string nula” << endl;
}

String::String(const char * s) {
setString(s);
cout << “Constr. string ” << (p ? p : “nula”) << endl;
}

String::String(const String & s) {
setString(s.p);
cout << “Constr. por copia string ” << (p ? p : “nula”) << endl;
}

String::~String() {
cout << “Destr. string ” << (p ? p : “nula”) << endl;
delete[] p;
}

void String::setString(const char * s) {
int t = strTam(s);
if (!t) {
p = 0;
return;
}
p = new char[t + 1];
if (!p) {
cout << “\nMemoria insuficiente”;
return;
}
strcpy_s(p, 5 ,s);
}

const char* String::getString()const { return p; }

ostream& String::imprime(ostream & saida) const {
if (p) saida << ” ” << p;
else saida << ” String nula”;
return saida;
}

String& String::operator=(const String & x) {
if (this != &x) {
delete[] p;
setString(x.p);
}
cout << “Oper. atrib. string ” << (p ? p : “nula”) << endl;
return *this;
}
String& String::operator+=(const String & x) {
char * aux = new char[strTam(p) + strTam(x.p) + 1];
if (!aux) {
cout << “\nMemoria insuficiente”;
return *this;
}
if (p) strcpy(aux, p);
if (x.p) strcpy(aux + strTam(p), x.p);
delete[] p;
p = aux;
return *this;
}
int String::strTam(const char * s) {
return (s ? strlen(s) : 0);
}
ostream& operator<<(ostream & saida, const String & x) {
return x.imprime(saida);
}

class Data {
int dia;
int mes;
int ano;
public:
Data(int d = 1, int m = 1, int a = 2000);
~Data();
int getDia() const;
int getMes() const;
int getAno() const;
void setDia(int d);
void setMes(int m);
void setAno(int a);
void anosDepois(int n);
};
Data::Data(int d, int m, int a) {
cout << “Constr. data ” << d << “/” << m << “/” << a << endl;
dia = d; mes = m; ano = a;
}
Data::~Data() {
cout << “Destr. data ” << dia << “/” << mes << “/” << ano << endl;
}
int Data::getDia() const { return dia; }
int Data::getMes() const { return mes; }
int Data::getAno() const { return ano; }
void Data::setDia(int d) { dia = d; }
void Data::setMes(int m) { mes = m; }
void Data::setAno(int a) { ano = a; }
void Data::anosDepois(int n) { ano += n; }

class Pessoa {
String nome;
String endereco;
String telefone;
Data nascimento;
public:
Pessoa(const char* n, const char* end, const char* tel, int d, int m, int a);
Pessoa(const char* n);
~Pessoa();
int getAnoNascimento()const;
};

Pessoa::~Pessoa() {
cout << “Fim\n”;
}

Pessoa::Pessoa(const char * n, const char * end, const char * tel, int d, int m, int a) : nome(n), endereco(end), telefone(tel), nascimento(d, m, a) {
cout << “Inicio\n”;
}

Pessoa::Pessoa(const char* n) : nome(n) {
cout << “Inicio\n”;
}

int Pessoa::getAnoNascimento()const {
return nascimento.getAno();
}

int main() {
Pessoa a(“Pedro Sousa”, “Rua do La Vai Um, 1, Coimbra”, “11111”, 1, 11, 1980);
Pessoa b(“Teresa Oliveira”);
cout << “Pedro Sousa nasceu em ” << a.getAnoNascimento() << endl;
cout << “Teresa Oliveira nasceu em “<< b.getAnoNascimento() << endl;
Pessoa c = a; // construção por cópia
c = a; // atribuição

}

Tags : , , , , ,