Baixe o app para aproveitar ainda mais
Prévia do material em texto
Escola Politécnica da Universidade de São PauloEscola Politécnica da Universidade de São Paulo Laboratório de Programação Orientada a Objetos para Engenharia Elétrica Aula 7: Herança e Polimorfismo II PCS3111 Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 2 Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 3 Polimorfismo na aula anterior Na aula passada, vimos que uma subclasse pode herdar métodos da superclasse e alterar seu comportamento. Nesta aula, vamos trabalhar mais profundamente o conceito de polimorfismo. 4 Polimorfismo A palavra vem do grego: πολύς, polys, muito μορφή, morphē, forma 5 Morfeu é o deus grego do sono e dos sonhos, que podia assumir qualquer forma. 6 Imagem: ©Trustees of the British Museum, cedida mediante licença CC BY-NC-SA 4.0 Não é este Morfeu ;) 7 Definição Polimorfismo é a propriedade de um único nome ser usado com diversos significados. Na programação, isso significa que, dependendo do contexto, o comportamento pode ser feito diferente. Nome de quê? Comportamento de quem? R: de variáveis, de classes, mas principalmente de funções! Ao lado de encapsulamento, abstração e herança, polimorfismo é característica essencial da orientação a objetos! 8 Formas de polimorfismo Há quatro formas de polimorfismo nas linguagens de programação OO: • Sobrecarga ou overloading: um mesmo nome é usado com diferentes comportamentos e diferentes argumentos; • Redefinição ou overriding: uma classe derivada estabelece um comportamento diferente para um método herdado de sua classe base; • Variáveis polimórficas: uma variável que pode assumir valores de diferentes tipos durante a execução. • Templates: uma forma de criar modelos para classes parametrizando os tipos. (observação: essa última forma não será vista nesta aula!) 9 Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 10 Sobrecarga (Overloading) Um termo sobrecarregado pode ser usado em diferentes contextos, com significados diferentes. O mesmo ocorre com a linguagem natural: 11 A manga da camisa A fruta manga O contexto permite identificar o significado atribuído ao termo. Com certeza você já percebeu que alguns operadores têm diferentes implementações! 12 A sobrecarga é a forma já conhecida de polimorfismo! Seja o operador + Em C++, o mesmo operador pode ser usado em diferentes contextos: 4 + 5 (adição de inteiros) 3.14 + 2.0 (adição em ponto flutuante) “cata”+ “vento" (concatenação de strings!) Dá para imaginar que a implementação da função + é diferente para cada caso! Sobrecarga A sobrecarga pode ser de operadores e também de funções. C++ permite que o programador faça mais de uma definição para um mesmo nome dentro do mesmo escopo desde que as declarações tenham: • assinaturas diferentes e • implementações diferentes 13 Como o compilador decide? Quando o compilador encontra uma função ou operador sobrecarregados, decide a implementação mais adequada com base na comparação das assinaturas dos métodos. • Este processo é chamado de resolução da sobrecarga. 14 Assinatura Dentro do mesmo escopo, a assinatura dos métodos precisa ser diferente. A assinatura é uma combinação do número e tipo dos argumentos e do tipo do retorno. Não pode haver sobrecarga apenas variando o tipo de retorno. 15 Exemplo 01: Soma 16 1 #include <iostream> 2 using namespace std; 3 4 int soma (int a) { 5 return a; 6 } 7 8 int soma (int a, int b) { 9 return a + b; 10 } 11 12 int soma (int a, int b, int c) { 13 return a + b + c; 14 } 15 16 int main() { 17 // testando a sobrecarga por assinatura 18 19 cout << "6 = " << soma (6) << endl; 20 cout << "6 + 6 = " << soma (6, 6) << endl; 21 cout << "6 + 6 + 6 = " << soma (6, 6, 6) << endl; 22 23 return 0; 24 } Construtores sobrecarregados É comum haver mais de um construtor para uma classe, variando-se o número de parâmetros. No exemplo seguinte, considere uma classe Relogio, cujos objetos podem ser construídos de 4 diferentes formas. 17 Exemplo 02: Relogio 18 5 class Relogio { 6 public: 7 Relogio(); 8 Relogio (int hora); 9 Relogio (int hora, int minuto); 10 Relogio (int hora, int minuto, int segundo); 11 void imprimir (); 12 virtual ~Relogio(); 13 protected: 14 int hora; 15 int minuto; 16 int segundo; 17 private: 18 }; 19 Há 4 diferentes construtores! 19 6 Relogio::Relogio() { 7 hora = minuto = segundo = 0; 8 } 9 Relogio::Relogio (int hora) { 10 this->hora = hora; 11 this->minuto = this->segundo = 0; 12 } 13 14 Relogio::Relogio (int hora, int minuto) { 15 this->hora = hora; 16 this->minuto = minuto; 17 this->segundo = 0; 18 } 19 20 Relogio::Relogio (int hora, int minuto, int segundo) { 21 this->hora = hora; 22 this->minuto = minuto; 23 this->segundo = segundo; 24 } 25 26 void Relogio::imprimir() { 27 cout << hora << ":" << minuto << ":" << segundo << endl; 28 } 29 30 Relogio::~Relogio() { 31 //dtor 32 } 20 1 #include <iostream> 2 #include "Relogio.h" 3 4 using namespace std; 5 6 int main() { 7 Relogio *r1, *r2, *r3, *r4; 8 r1 = new Relogio (); 9 r2 = new Relogio (1); 10 r3 = new Relogio (1, 30); 11 r4 = new Relogio (1, 30, 45); 12 r1->imprimir(); 13 r2->imprimir(); 14 r3->imprimir(); 15 r4->imprimir(); 16 } Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 21 Redefinição (Overriding) A redefinição acontece quando uma classe derivada modifica um método que herdou da classe base. A função redefinida pela classe derivada tem a mesma declaração da classe base. Isto significa: • O mesmo nome • O mesmo tipo de retorno • A mesma assinatura (lista de parâmetros) 22 Qual a diferença de sobrecarga? A redefinição só faz sentido no contexto da herança; a sobrecarga ocorre numa mesma classe; As assinaturas precisam ser as mesmas; Os métodos redefinidos podem ser combinados com os que os redefinem; Em geral, é resolvida em tempo de execução e não em tempo de compilação. Exemplo 03: Pássaros Seja a classe Passaro Suponha que todos os pássaros cantem, cada um de sua forma. 24 classe Passaro 25 8 class Passaro { 9 protected: 10 bool emExtincao; 11 string corPredominante; 12 public: 13 Passaro(); 14 ~Passaro(); 15 void canta(); 16 bool isEmExtincao(); 17 string getCorPredominante(); 18 19 }; Passaro.h Construtor da classe base Método da classe base 26 1 #include "Passaro.h" 2 3 Passaro::Passaro() { 4 this->emExtincao = false; 5 this->corPredominante = "cinza"; 6 } 7 8 Passaro::~Passaro() {} 9 10 void Passaro::canta() { 11 cout << "Piu Piu Piu" << endl; 12 } 13 14 bool Passaro::isEmExtincao () { 15 return this->emExtincao; 16 } 17 18 string Passaro::getCorPredominante () { 19 return this->corPredominante;20 } Passaro.cpp classe Arara 27 9 class Arara: public Passaro { 10 public: 11 Arara(); 12 ~Arara(); 13 void canta (); 14 }; 15 Arara.h Construtor da classe derivada Método da classe derivada 28 1 #include "Arara.h" 2 3 Arara::Arara() : Passaro() { 4 this->emExtincao = true; 5 this->corPredominante = "azul"; 6 } 7 8 Arara::~Arara() { 9 } 10 11 void Arara::canta() { 12 cout << "A-RA-RA --- A-RA-RA" << endl; 13 } Arara.cpp O método da classe derivada redefine o método da classe base: o mesmo nome, a mesma assinatura, mas funções diferentes! 29 7 int main() { 8 9 10 Passaro *p = new Passaro (); 11 cout << "Passaro " << p->getCorPredominante() 12 << " em extincao? R: " << (p->isEmExtincao() ? “Sim" : 13 “Não") << endl; 14 p->canta(); 15 16 Arara *a = new Arara (); 17 cout << "Arara " << a->getCorPredominante() 18 << " em extincao? R: " << (a->isEmExtincao() ? “Sim" : 19 “Não") << endl; 20 21 a->canta(); 22 23 delete p; 24 delete a; 25 return 0; 26 } main.cpp Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 30 Ligação estática Exemplo 4 Se uma arara é um pássaro também, o que deve acontecer neste caso? 31 13 int main() { 14 Passaro *p [3]; 15 p[0] = new Passaro (); 16 p[1] = new Arara (); 17 p[2] = new Arara (); 18 19 for (int i = 0; i < 3; i++) { 20 p[i]->canta(); 21 } 22 23 delete p[0]; 24 delete p[1]; 25 delete p[2]; 26 return 0; 27 } Por quê? Porque o método canta é polimórfico na hierarquia. O compilador escolhe qual método ele vai acionar a partir do tipo da variável no código fonte, e no comando p[i]->canta() p é do tipo Passaro! Isto é, o compilador decide qual método usar antes de executar o programa (em tempo de compilação) Isto é chamado de ligação estática (static binding)! Observe que a atribuição p[1] = new Arara (); é válida pelo princípio da substituição. 32 Ligação estática (static binding) Na ligação estática, as referências são resolvidas em tempo de compilação. O compilador escolhe o método a partir do tipo declarado no código fonte. 33 Ligação dinâmica (Virtual) Para que o compilador adie a decisão de qual método polimórfico usar até o momento da execução do programa, declaramos o método como virtual. Isso indica ao compilador que espere a execução do programa para então decidir o método a ser usado com base na classe a que pertence o objeto. Este efeito é chamado de ligação dinâmica (dynamic binding) 34 Exemplo 5 Neste exemplo, o método canta é declarado como virtual. 35 8 class Passaro { 9 protected: 10 bool emExtincao; 11 string corPredominante; 12 public: 13 Passaro(); 14 virtual ~Passaro(); 15 virtual void canta(); 16 bool isEmExtincao(); 17 string getCorPredominante (); 18 }; O método da classe base é declarado como virtual, indicando que ele é polimórfico. No main (Exemplo 5) 36 6 7 int main() { 8 Passaro *p [3]; 9 p[0] = new Passaro (); 10 p[1] = new Arara (); 11 p[2] = new Arara (); 12 13 for (int i = 0; i < 3; i++) { 14 p[i]->canta(); 15 } 16 17 for (int i = 0; i < 3; i++) { 18 delete p[i]; 19 } 20 21 return 0; 22 } A declaração virtual na classe Passaro indica ao compilador que não resolva o método, mas prepare para a decisão ser tomada em tempo de execução. Ligação dinâmica (Dynamic binding) Na ligação dinâmica, as referências são resolvidas em tempo de execução. O compilador escolhe o método a partir do tipo obtido em tempo de execução. Para tanto, os métodos das superclasses têm que ser declarados virtual. 37 Destrutor virtual Observe que a declaração de um método como virtual, numa superclasse, é indicativa da intenção de herança e polimorfismo. O que acontece se não declararmos o destrutor dessa superclasse como virtual? 38 Destrutor virtual – Exemplo 06 39 6 7 int main() { 8 Passaro *p [3]; 9 p[0] = new Passaro (); 10 p[1] = new Arara (); 11 p[2] = new Arara (); 12 13 for (int i = 0; i < 3; i++) { 14 p[i]->canta(); 15 } 16 17 for (int i = 0; i < 3; i++) { 18 delete p[i]; 19 } 20 21 return 0; 22 } Se o destrutor de Passaro não fosse virtual, qual dos destrutores seria chamado aqui? O que aconteceria? Destrutor virtual – Exemplo 06 40 6 7 int main() { 8 Passaro *p [3]; 9 p[0] = new Passaro (); 10 p[1] = new Arara (); 11 p[2] = new Arara (); 12 13 for (int i = 0; i < 3; i++) { 14 p[i]->canta(); 15 } 16 17 for (int i = 0; i < 3; i++) { 18 delete p[i]; 19 } 20 21 return 0; 22 } Se o destrutor de Passaro não fosse virtual, qual dos destrutores seria chamado aqui? O que aconteceria? Implementação dos destrutores (Exemplo 6) 41 3 Passaro::Passaro() { 4 this->emExtincao = false; 5 this->corPredominante = "cinza"; 6 } 7 8 Passaro::~Passaro() { 9 cout << "Adeus Passaro! ";} 10 11 void Passaro::canta() { 12 cout << "Piu Piu Piu" << endl; 13 } 14 15 bool Passaro::isEmExtincao () { 16 return this->emExtincao; 17 } 18 19 string Passaro::getCorPredominante (){ 20 return this->corPredominante; 21 } 3 Arara::Arara() : Passaro() { 4 this->emExtincao = true; 5 this->corPredominante = "azul"; 6 } 7 8 Arara::~Arara() { 9 cout << "Adeus Arara! " << endl; 10 } 11 12 void Arara::canta() { 13 cout << "A Arara canta A-RA-RA --- A-RA-RA" << endl; 14 } Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 42 Substituição x refinamento Quando um método é redefinido, o código no método da classe derivada pode: • Substituir completamente o código do método da classe base (substituição) ou • Chamar o método da classe base e acrescentar a ele o código específico da classe derivada (refinamento) 43 Construtores sempre usam refinamento O construtor da classe base é acionado quando construímos a classe derivada. Desta forma garante-se que toda inicialização da classe base acontece também para os objetos da classe derivada. 44 Exemplo de refinamento 45 3 Arara::Arara() : Passaro() { 4 this->emExtincao = true; 5 this->corPredominante = "azul"; 6 } 7 8 Arara::~Arara() { 9 cout << "Adeus Arara! " << endl; 10 } 11 12 void Arara::canta() { 13 cout << "A Arara canta A-RA-RA --- A-RA-RA" << endl; 14 } O construtor da classe derivada refina o método da classe base, neste caso inicializando de forma diferente os parâmetros. Agenda 1. Polimorfismo 2. Sobrecarga 3. Redefinição 4. Ligação estática e ligação dinâmica 5. Refinamento 6. Variáveis polimórficas 46 47 1 #include <iostream> 2 3 using namespace std; 4 class A { 5 }; 6 7 class B: public A { 8 }; 9 10 int main() { 11 A *a = new A(); 12 delete a; 13 14 a =new B(); 15 delete a; 16 } A variável a é polimórfica, pois recebe objetos de classes diferentes. Variáveis também podem ser polimórficas! this this é a variável polimórfica mais comum! Ela pode receber diferentes classes! 48 Conclusões Polimorfismo é importante porque Favorece a componentização e consequentemente, o reúso de software, na medida em que um comportamento básico pode ser estendido para atender às peculiaridades das classes derivadas. Favorece a abstração e o encapsulamento, na medida em que nomes iguais escondem a implementação diferente. 49 Bibliografia Budd, T. An Introduction to Object-Oriented Programming. Addison-Wesley, 3rd ed. 2002. • Conceito de polimorfismo: Capítulo 14 • Sobrecarga: Capítulo 15 • Redefinição: Capítulo 16 • Variáveis polimórficas: Capítulo 17 Lafore, R. Object Oriented Programming in C++. Sams Publishing, 4th. ed. 2002. • Sobrecarga de operadores: Capítulo 8 • Herança: Capítulo 9 50
Compartilhar