Polimorfismo em C++ Explicado: Exemplos Práticos e Conceitos-Chave
O que é Polimorfismo em C++?
Em C++, polimorfismo faz com que uma função membro se comporte diferentemente baseada no objeto que a chama/invoca. Polimorfismo é uma palavra grega que significa "ter muitas formas". Ocorre quando você tem uma hierarquia de classes relacionadas através de herança.
Por exemplo, suponha que temos a função emitirSom(). Quando um gato chama esta função, ela produz o som "miau". Quando uma vaca invoca a mesma função, ela produz o som "muu".
Nota: Ilustração original omitida nesta versão otimizada.
Embora tenhamos uma função, ela se comporta diferentemente sob diferentes circunstâncias. A função tem muitas formas; portanto, alcançamos polimorfismo.
Neste tutorial C++, você aprenderá:
- O que é Polimorfismo?
- Tipos de Polimorfismo
- Polimorfismo em Tempo de Compilação
- Sobrecarga de Funções
- Sobrecarga de Operadores
- Polimorfismo em Tempo de Execução
- Sobrescrita de Funções
- Função Virtual em C++
- Polimorfismo em Tempo de Compilação vs. Tempo de Execução
Tipos de Polimorfismo
C++ suporta dois tipos de polimorfismo:
- Polimorfismo em tempo de compilação, e
- Polimorfismo em tempo de execução.
Nota: Diagrama original omitido nesta versão otimizada.
Polimorfismo em Tempo de Compilação
Você invoca as funções sobrecarregadas correspondendo o número e tipo de argumentos. A informação está presente durante a compilação. Isso significa que o compilador C++ selecionará a função correta em tempo de compilação.
Polimorfismo em tempo de compilação é alcançado através de sobrecarga de funções e sobrecarga de operadores.
Sobrecarga de Funções
Sobrecarga de funções ocorre quando temos muitas funções com nomes similares mas argumentos diferentes. Os argumentos podem diferir em número ou tipo.
Exemplo 1:
#include <iostream>
using namespace std;
void testar(int i) {
cout << " O int é " << i << endl;
}
void testar(double f) {
cout << " O double é " << f << endl;
}
void testar(char const *ch) {
cout << " O char* é " << ch << endl;
}
int main() {
testar(5);
testar(5.5);
testar("cinco");
return 0;
}
Saída:
O int é 5
O double é 5.5
O char* é cinco
Explicação do Código:
- Inclui o cabeçalho iostream no código. Poderemos usar suas funções.
- Inclui o namespace std no código. Poderemos usar suas classes sem chamá-lo.
- Cria uma função chamada testar que recebe parâmetro inteiro i. O { marca o início do corpo da função testar.
- Instrução a ser executada se a função testar acima for invocada/chamada.
- Fim do corpo da função testar acima.
- Cria uma função chamada testar que recebe parâmetro double f. O { marca o início do corpo da função testar.
- Instrução a ser executada se a função testar acima for invocada/chamada.
- Fim do corpo da função testar acima.
- Cria uma função chamada testar que recebe parâmetro caractere ch. O { marca o início do corpo da função testar.
- Instrução a ser executada se a função testar acima for invocada/chamada.
- Fim do corpo da função testar acima.
- Chama a função main(). O { marca o início do corpo da função.
- Chama a função testar passando 5 como valor do argumento. Isso invoca a função testar que aceita argumento inteiro, ou seja, a primeira função testar.
- Chama a função testar passando 5.5 como valor do argumento. Isso invocará a função testar que aceita argumento double, ou seja, a segunda função testar.
- Chama a função testar passando "cinco" como valor do argumento. Isso invocará a função testar que aceita argumento caractere, ou seja, a terceira função testar.
- O programa deve retornar um valor se executar com sucesso.
- Fim do corpo da função main().
Temos três funções com o mesmo nome mas diferentes tipos de argumentos. Alcançamos polimorfismo.
Sobrecarga de Operadores
Na Sobrecarga de Operadores, definimos um novo significado para um operador C++. Também muda como o operador funciona. Por exemplo, podemos definir o operador + para concatenar duas strings. Conhecemos como operador de adição para somar valores numéricos. Após nossa definição, quando colocado entre inteiros, ele os somará. Quando colocado entre strings, ele as concatenará.
Exemplo 2:
#include <iostream>
using namespace std;
class NumeroComplexo {
private:
int real, imag;
public:
NumeroComplexo(int rl = 0, int im = 0) {
real = rl;
imag = im;
}
NumeroComplexo operator + (NumeroComplexo const &obj) {
NumeroComplexo resultado;
resultado.real = real + obj.real;
resultado.imag = imag + obj.imag;
return resultado;
}
void imprimir() {
cout << real << " + i" << imag << endl;
}
};
int main() {
NumeroComplexo c1(10, 2), c2(3, 7);
NumeroComplexo c3 = c1 + c2;
c3.imprimir();
}
Saída:
13 + i9
Explicação do Código:
- Inclui o cabeçalho iostream no programa para usar suas funções.
- Inclui o namespace std no programa para usar suas classes sem chamá-lo.
- Cria uma classe chamada NumeroComplexo. O { marca o início do corpo da classe.
- Usa o modificador de acesso private para marcar variáveis como privadas, significando que só podem ser acessadas dentro da classe.
- Define duas variáveis inteiras, real e imag.
- Usa o modificador de acesso public para marcar o construtor como público, significando que será acessível mesmo de fora da classe.
- Cria o construtor da classe e inicializa as variáveis.
- Inicializa o valor da variável real.
- Inicializa o valor da variável imag.
- Fim do corpo do construtor.
- Precisamos sobrescrever o significado do operador +.
- Cria o tipo de dado resultado do tipo NumeroComplexo.
- Usa o operador + com números complexos. Esta linha somará a parte real de um número à parte real de outro número.
- Usa o operador + com números complexos. Esta linha somará a parte imaginária de um número à parte imaginária de outro número.
- O programa retornará o valor da variável resultado após execução bem-sucedida.
- Fim da definição do novo significado do operador +, ou seja, sobrecarga.
- Chama o método imprimir().
- Imprime o novo número complexo após adição no console.
- Fim do corpo da função imprimir().
- Fim do corpo da classe NumeroComplexo.
- Chama a função main().
- Passar os valores de ambas as partes real e imaginária a serem somadas. A primeira parte de c1 será somada à primeira parte de c2, ou seja, 10+3. A segunda parte de c1 será somada à segunda parte de c2, ou seja, 2+7.
- Realiza uma operação usando o operador + sobrecarregado e armazena o resultado na variável c3.
- Imprime o valor da variável c3 no console.
- Fim do corpo da função main().
Polimorfismo em Tempo de Execução
Isso acontece quando o método de um objeto é invocado/chamado durante a execução em vez de durante a compilação. Polimorfismo em tempo de execução é alcançado através de sobrescrita de funções. A função a ser chamada/invocada é estabelecida durante a execução.
Sobrescrita de Funções
Sobrescrita de funções ocorre quando uma função da classe base recebe uma nova definição em uma classe derivada. Naquele momento, podemos dizer que a função base foi sobrescrita.
Por exemplo:
#include <iostream>
using namespace std;
class Mamifero {
public:
void comer() {
cout << "Mamíferos comem...";
}
};
class Vaca : public Mamifero {
public:
void comer() {
cout << "Vacas comem grama...";
}
};
int main(void) {
Vaca v = Vaca();
v.comer();
return 0;
}
Saída:
Vacas comem grama...
Explicação do Código:
- Importa o cabeçalho iostream no programa para usar suas funções.
- Inclui o namespace std no programa para usar suas classes sem chamá-lo.
- Cria uma classe chamada Mamifero. O { marca o início do corpo da classe.
- Usa o modificador de acesso public para definir a função que estamos prestes a criar como publicamente acessível. Será acessível de fora desta classe.
- Cria uma função pública chamada comer. O { marca o início do corpo da função.
- Imprime a instrução adicionada à função cout quando a função comer() é invocada.
- Fim do corpo da função comer().
- Fim do corpo da classe Mamifero.
- Cria uma classe chamada Vaca que herda a classe Mamifero. Vaca é a classe derivada, enquanto Mamifero é a classe base. O { marca o início desta classe.
- Usa o modificador de acesso public para marcar a função que estamos prestes a criar como publicamente acessível. Será acessível de fora desta classe.
- Sobrescreve a função comer() que foi definida na classe base. O { marca o início do corpo da função.
- A instrução para imprimir no console quando esta função é invocada.
- Fim do corpo da função comer().
- Fim do corpo da classe Vaca.
- Chama a função main(). O { marca o início do corpo desta função.
- Cria uma instância da classe Vaca dando-lhe o nome v.
- Chama a função comer() definida na classe Vaca.
- O programa deve retornar um valor após conclusão bem-sucedida.
- Fim da função main().
Função Virtual em C++
Uma função virtual é outra forma de implementar polimorfismo em tempo de execução em C++. É uma função especial definida em uma classe base e redefinida na classe derivada. Para declarar uma função virtual, você deve usar a palavra-chave virtual. A palavra-chave deve preceder a declaração da função na classe base.
Se uma classe virtual é herdada, a classe derivada redefine a função virtual para atender suas necessidades. Por exemplo:
#include <iostream>
using namespace std;
class ClasseA {
public:
virtual void mostrar() {
cout << "A função show() na classe base invocada..." << endl;
}
};
class ClasseB : public ClasseA {
public:
void mostrar() {
cout << "A função show() na classe derivada invocada...";
}
};
int main() {
ClasseA* a;
ClasseB b;
a = &b;
a->mostrar();
}
Saída:
A função show() na classe derivada invocada...
Explicação do Código:
- Inclui o cabeçalho iostream no código para usar suas funções.
- Inclui o namespace std no código para usar suas classes sem chamá-lo.
- Cria uma classe chamada ClasseA.
- Usa o modificador de acesso public para marcar um membro da classe como publicamente acessível.
- Cria uma função virtual chamada mostrar(). Será uma função pública.
- O texto para imprimir quando show() for invocada. O endl é uma palavra-chave C++, que significa fim de linha. Move o cursor do mouse para a próxima linha.
- Fim do corpo da função virtual mostrar().
- Fim do corpo da classe ClasseA.
- Criando uma nova classe chamada ClasseB que herda a classe ClasseA. ClasseA torna-se a classe base enquanto ClasseB torna-se a classe derivada.
- Usa o modificador de acesso public para marcar um membro da classe como publicamente acessível.
- Redefine a função virtual mostrar() derivada na classe base.
- O texto para imprimir no console quando a função mostrar() definida na classe derivada é invocada.
- Fim do corpo da função mostrar().
- Fim do corpo da classe derivada, ClasseB.
- Chama a função main(). A lógica do programa deve ser adicionada dentro de seu corpo.
- Cria uma variável ponteiro chamada a. Ela aponta para a classe chamada ClasseA.
- Cria uma instância da classe chamada ClasseB. A instância recebe o nome b.
- Atribui os valores armazenados no endereço b na variável a.
- Invoca a função mostrar() definida na classe derivada. Late binding foi implementado.
- Fim do corpo da função main().
Polimorfismo em Tempo de Compilação vs. Tempo de Execução
Aqui estão as principais diferenças entre os dois:
| Polimorfismo em tempo de compilação | Polimorfismo em tempo de execução |
|---|---|
| Também chamado de early binding ou polimorfismo estático | Também chamado de late/dynamic binding ou polimorfismo dinâmico |
| O método é chamado/invocado durante a compilação | O método é chamado/invocado durante a execução |
| Implementado via sobrecarga de funções e operadores | Implementado via sobrescrita de métodos e funções virtuais |
| Exemplo, sobrecarga de método. Muitos métodos podem ter nomes similares mas diferentes número ou tipos de argumentos | Exemplo, sobrescrita de método. Muitos métodos podem ter nome similar e o mesmo protótipo |
| Execução mais rápida pois a descoberta dos métodos é feita durante a compilação | Execução mais lenta pois a descoberta do método é feita durante a execução |
| Menos flexibilidade para resolução de problemas pois tudo é conhecido durante a compilação | Muita flexibilidade é fornecida para resolver problemas complexos pois métodos são descobertos durante a execução |
Resumo:
- Polimorfismo significa ter muitas formas.
- Ocorre quando há uma hierarquia de classes relacionadas através de herança.
- Com polimorfismo, uma função pode se comportar diferentemente baseada no objeto que a invoca/chama.
- Em polimorfismo em tempo de compilação, a função a ser invocada é estabelecida durante a compilação.
- Em polimorfismo em tempo de execução, a função a ser invocada é estabelecida durante a execução.
- Polimorfismo em tempo de compilação é determinado através de sobrecarga de funções e operadores.
- Em sobrecarga de funções, há muitas funções com nomes similares mas argumentos diferentes.
- Os parâmetros podem diferir em número ou tipo.
- Em sobrecarga de operadores, um novo significado é definido para operadores C++.
- Polimorfismo em tempo de execução é alcançado através de sobrescrita de funções.
- Em sobrescrita de funções, uma classe derivada dá uma nova definição a uma função definida na classe base.
Linguagem C
- Como Passar Arrays para Funções em C++: Guia Prático
- Operadores em C++ Explicados: Tipos, Exemplos e Programas Práticos
- Dominando Switch-Case em C++: Sintaxe, Uso e Exemplos Práticos
- Dominando Alocação Dinâmica de Arrays em C++: Guia Prático com Exemplos de Código
- Sobrecarga de Operadores em C++: Guia Prático com Exemplos de Código
- Structs em C++ Explicadas com Exemplo Prático
- Dominando std::list em C++: Sintaxe, Funções e Exemplos Práticos
- Funções em C++ Explicadas com Exemplos Práticos de Código
- C++ Struct vs Class: Diferenças Principais, Sintaxe e Melhores Casos de Uso
- calloc() em C: Alocação de Memória Inicializada com Zero e Exemplo Prático