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;
}
0 thoughts on “POO, introdução III”