Jiraspiom

Blog Pessoal

Lição 13

Lição 13 – Finalizando POO

Continuação…

class Retangulo: public Poligono { … }


Esta palavra-chave depois dos dois-pontos ( : ) denota o nível de acesso máximo para todos os membros herdados da classe que o segue (neste caso, Poligono). Como public é o nível mais acessível, especificando esta palavra-chave a classe derivada herda todos os membros com os mesmos níveis que eles tinham na classe base. Se especificarmos um nível de acesso mais restrito como protected, todos os membros public da classe base são herdados como protected na classe derivada. Também, se especificarmos o mais restrito dos níveis de acesso, private, todos os membros da classe base são herdados como private.

Por exemplo, uma classe mae é definida como:

class filha: protected mae;

, onde filha é uma classe derivada de mae. Isto setaria protected como o nível de acesso máximo para os membros de filha herdados de mae. Isto é, todos os membros que forem public em mae se tornarão protected em daughter. Claro que isto não restringiria filha de declarar seus próprios membros public. Este nível de acesso máximo é apenas setado para os membros herdados de mae.

Se não explicitarmos nenhum nível de acesso para a herança, o compilador assume private para classes declaradas com a palavra-chave class e public para aquelas declaradas com struct.

O que é herdado da classe base?

A princípio, uma classe derivada herda todo membro de uma classe base, exceto:

* seu construtor e destrutor
* seus membros de operador=()
* seus friends

Apesar de os construtores e destrutores da classe base não serem herdados si mesmas, seu construtor padrão (sem parâmetros) e seu destrutor são sempre chamados quando um novo objeto de uma classe derivada é criado ou destruido.

Se a classe base não tem um construtor padrão ou queremos que um construtor sobrecarregado seja chamado quando um novo objeto derivado é criado, podemos especificá-lo em cada definição de construtor da classe derivada:

[nome_do_construtor_derivado] (parametros) : [nome_do_construtor_base](parametros) {…}

Exemplo:

#include <iostream>
using namespace std;

class mae{
public:
mae ()
{ cout << “Mae: sem parametros\n”; }
mae (int a)
{ cout << “Mae: Parametro int\n”; }
};

class filha : public mae{
public:
filha (int a)
{ cout << “filha: Parametro int\n\n”; }
};

class filho : public mae {
public:
filho (int a) : mae (a)
{ cout << “filho: Parametro int\n\n”; }
};

int main () {
filha cynthia (0);
filho daniel(0);

return 0;
}

, o que retorna:

mae: no parameters
filha: int parameter

mae: int parameter
filho: int parameter

Note que a diferença entre qual construtor ‘mae’ é chamado quando um novo objeto filha é criado e qual é chamado quando é um objeto filho. A diferença é entre a declaração do construtor de filha e filho:

filha (int a) // nada especificado: chamada padrao
filho (int a) : mae (a) // construtor especificado: chama este

Herança Múltipla

É perfeitamente possível que uma classe herde membros de mais de uma classe. Isto é feito simplesmente separando as diferentes classes base com vírgulas na declaração da classe derivada. Por exemplo, se tivermos uma classe específica para imprimir na tela (COutput) e quisermos que nossas classe Retangulo e Triangulo também herdem seus membros em adição àqueles de Poligono, poderíamos escrever:

class Retangulo: public Poligono, public COutput;
class Triangulo: public Poligono, public COutput;

, aqui temos um exemplo completo:

#include <iostream>
using namespace std;

class Poligono {
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b;}
};

class COutput {
public:
void saida (int i);
};

void COutput::saida (int i) {
cout << i << endl;
}

class Retangulo: public Poligono, public COutput {
public:
int area ()
{ return (comprimento * altura); }
};

class Triangulo: public Poligono, public COutput {
public:
int area ()
{ return (comprimento * altura / 2); }
};

int main () {
Retangulo retang;
Triangulo trgl;
retang.seta_valores (4,5);
trgl.seta_valores (4,5);
retanng.saida (retang.area());
trgl.output (trgl.area());
return 0;
}

Polimorfismo

Ponteiros para classes base

Uma das funcionalidades principais de classes derivadas é que um ponteiro para uma classe derivada é compatível tipamente com um ponteiro para esta classe base. Polimorfismo é a arte de tirar vantagem dessa funcionalidade. Começaremos reescrevendo nosso programa sobre o retângulo e o triângulo da seção anterior considerando esta propriedade de compatibilidade de ponteiros:

#include <iostream>
using namespace std;

class Poligono {
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b; }
};

class Retangulo: public Poligono {
public:
int area ()
{ return (comprimento * altura); }
};

class Triangulo: public Poligono {
public:
int area ()
{ return (comprimento * altura / 2); }
};

int main () {
Retangulo retang;
Triangulo trgl;
Poligono * poli1 = &retang;
Poligono * poli2 = &trgl;
poli1->seta_valores (4,5);
poli2->seta_valores (4,5);
cout << retang.area() << endl;
cout << trgl.area() << endl;
return 0;
}


, o que mostra na tela:

20
10


Na função main, criamos dois ponteiros que apontam para objetos da classe Poligono (poli1 e poli2), então atribuímos referências para retang e trgl a esses ponteiros, e como ambas são objetos de classes derivadas de Poligono, são atribuições válidas.

A única limitação em usar *poli1 e *poli2 ao invés de retang e trgl é que ambos *poli1 e *poli2 são do tipo Poligono* e assim apenas podemos usar estes ponteiros para nos referirmos a membros que Retangulo e Triangulo herdam de Poligono. Por esta razão quando chamamos os membros de area() no final do programa, temos que usar diretamente os objetos retang e trgl ao invés dos ponteiros *pont1 e *pont2.

Para usar area() com os ponteiros para a classe Poligono, este membro também deve ter sido declarado na classe Poligono, e não apenas nas suas classes derivadas, mas o problema é que Retangulo e Triangulo implementam versões diferentes de area. Assim, não podemos implementá-lo na classe base. Aí os membros virtuais vêm a calhar.

Membros Virtuais

Um membro de uma classe que pode ser redefinido em suas classes derivadas é conhecido como um membro virtual. Para declarar um membro de uma classe como virtual, devemos preceder sua declaração com a palavra-chave virtual:

#include <iostream>
using namespace std;

class Poligono {
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b; }
virtual int area ()
{ return (0); }
};

class Retangulo: public Poligono {
public:
int area ()
{ return (comprimento * altura); }
};

class Triangulo: public Poligono {
public:
int area ()
{ return (comprimento * altura / 2); }
};

int main () {
Retangulo retang;
Triangulo trgl;
Poligono poli;
Poligono * poli1 = &retang;
Poligono * poli2 = &trgl;
Poligono * poli3 = &poli;
poli1->seta_valores (4,5);
poli2->seta_valores (4,5);
poli3->seta_valores (4,5);
cout << poli1->area() << endl;
cout << poli2->area() << endl;
cout << poli3->area() << endl;
return 0;
}


, o que mostra na tela:

20
10
0


Agora as três classes têm todas os mesmos membros: comprimento, altura, seta_valores() e area(). A função membro area() foi declarada como virtual na classe base pois é redefinida depois em cada classe derivada. Podemos verificar se removermos essa palavra-chave virtual da declaraç~ao de area() em Poligono, e rodarmos o programa o resultado será 0 para os três polígonos ao invés de 20, 10 e 0. Isso porque ao invés de chamar a função area() correspondente para cada objeto (CRectangle::area(), Triangulo::area() and Poligono::area(), respectivamente), Poligono::area() será chamado em todos os casos, já que as chamadas são via ponteiro cujo tipo é Poligono.

Então, o que a palavra-chave virtual faz é permitir a um membro de uma classe derivada com o mesmo nome de um na classe base seja apropriadamente chamado a partir de um ponteiro, e mais precisamente quando o tipo do ponteiro é um ponteiro para a classe base, mas está apontando para um objeto da classe derivada, como no exemplo acima.

Uma classe que declara ou herda uma função virtual é chamada uma classe polimórfica. Note que apesar de sua virtualidade, também fomos capazes de declarar um objeto do tipo Poligono e chamar sua própria função area(), que sempre retorna 0.

Classes base abstratas

Classes base abstratas são algo parecido a nossa classe Poligono do exemplo anterior, com a diferença que neste definimos uma função area() válida com uma funcionalidade mínima para objetos que eram da classe Poligono (como poli), onde em uma classe base abstrata poderíamos deixar que a função membro area() ficasse sem implementação. Isto poderia ser feito anexando =0 (igual a zero) á declaração da função. Uma classe base abstrata Poligono poderia ser algo assim:

class Poligono {
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b; }
virtual int area () =0;
};


Note que adicionamos =0 ao int area() virtual ao invés de especificar uma implementação para a função.Este tipo de função é chamado uma função virtual pura, e todas as classes que contém no mínimo uma função virtual pura são classes base abstratas.

A principal diferença entre uma classe base abstrata e uma classe polimórfica normal é que, nas primeiras, no mínimo um de seus membros carece de implementações as quais não podemos criar instâncias.

Entretanto, uma classe que não pode instanciar objetos não é totalmente inútil. Podemos criar ponteiros para ela e tirar vantagem de todas suas habilidades polimórficas. Assim,

Poligono poli;


não seria válida para a classe base abstrata que acabamos de declarar, pois tenta instanciar um objeto. Apesar disto, os seguintes ponteiros seriam válidos:

Poligono * poli1;
Poligono * poli2;


Isto é assim durante o tempo que Poligono inclui uma função virtual puta e com isso não é uma classe base abstrata. No entanto, ponteiros para essa classe base abstrata podem ser usados para apontar para objetos de classes derivadas. Exemplo completo:

#include <iostream>
using namespace std;

class Poligono{
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b; }
virtual int area (void) =0;
};

class Retangulo: public Poligono {
public:
int area (void)
{ return (comprimento * altura); }
};

class Triangulo: public Poligono {
public:
int area (void)
{ return (comprimento * altura / 2); }
};

int main () {
Retangulo retang;
Triangulo trgl;
Poligono * poli1 = &retang;
Poligono * poli2 = &trgl;
poli1->seta_valores (4,5);
poli2->seta_valores (4,5);
cout << poli1->area() << endl;
cout << poli2->area() << endl;
return 0;
}


, o que mostra na tela:

20
10

Se revisarmos o programa, notaremos que nos referimos a objetos de uma classe diferente mas relacionada, usando um único tipo de ponteiro (Poligono*). Isto pode ser tremendamente útil. Por exemplo, agora podemos criar uma função membro da classe base abstrata Poligono que é capaz de imprimir na tela o resultado da função area() mesmo que Poligono em si não tenha nenhuma implementação para esta função:

#include <iostream>
using namespace std;

class Poligono {
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b; }
virtual int area (void) =0;
void imprimearea (void)
{ cout << this->area() << endl; }
};

class Retangulo: public Poligono {
public:
int area (void)
{ return (comprimento * altura); }
};

class Triangulo: public Poligono {
public:
int area (void)
{ return (comprimento * altura / 2); }
};

int main () {
Retangulo retang;
Triangulo trgl;
Poligono * ppoly1 = &retang;
Poligono * ppoly2 = &trgl;
poli1->seta_valores (4,5);
poli2->seta_valores (4,5);
poli1->imprimearea();
poli2->imprimearea();
return 0;
}


, o que mostra na tela:

20
10

Membros virtuais e classes abstratas garantem as características polimórficas de C++ e fazem POO um instrumento tão útil em grandes projetos. Claro que vimos usos muito simples destas estruturas mas essas funcionalidades podem ser aplicadas a arrays de objetos ou objetos dinamicamente alocados. Vamos finalizar POO com o mesmo exemplo, desta vez alocando os objetos dinamicamente:

// alocação dinâmica e polimorfismo
#include <iostream>
using namespace std;

class Poligono {
protected:
int comprimento, altura;
public:
void seta_valores (int a, int b)
{ comprimento=a; altura=b; }
virtual int area (void) =0;
void imprimearea (void)
{ cout << this->area() << endl; }
};

class Retangulo: public Poligono {
public:
int area (void)
{ return (comprimento * altura); }
};

class Triangulo: public Poligono {
public:
int area (void)
{ return (comprimento * altura / 2); }
};

int main () {
Poligono * poli1 = new Retangulo;
Poligono * poli2 = new Triangulo;
poli1->seta_valores (4,5);
poli2->seta_valores (4,5);
poli1->imprimearea();
poli2->imprimearea();
delete poli1;
delete poli2;
return 0;
}


, o que retorna na tela:

20
10


Note que os ponteiros poli:

Poligono * poli1 = new Retangulo;
Poligono * poli2 = new Triangulo;

são declarados como sendo do tipo ponteiro para Poligono, mas os objetos dinamicamente alocados foram declarados como tendo o tipo da classe derivada diretamente.

Anúncios
%d blogueiros gostam disto: