C/C++ · 11 min read · Oct 10, 2025

Aprendendo C/C++ Passo a Passo - Página 16

16. Passo a Passo C/C++ — Programação C++ - Polimorfismo

Polimorfismo

| | 1. Sobrecarga de Função

  1. Polimorfismo
  2. Tipos de polimorfismo
  3. Funções membro normais acessadas com ponteiros
  4. Função Virtual
  5. Função Pura
  6. Atribuição e Inicialização por Cópia
  7. O Construtor de CÓPIA
  8. Ponteiro ‘this’ |

1. Sobrecarga de Função

Se uma função com seu nome diferir pelo comportamento dos argumentos, é chamada de polimorfismo de funções ou sobrecarga de função.

| | // Um programa de exemplo para demonstrar o uso da sobrecarga de função
#include
using namespace std;
void printline()
{
for(int i=0;i<=80; i++) cout << “-“;
}

void printline(int n)
{
for(int i =0 ;i<=n;i++) cout << “-“;
}

void printline(int n,char ch)
{
for(int i=0;i<=n; i++) cout << ch;
}

int main()
{
printline();
printline(5);
printline(10, ‘*’);
return 0;
}
|

Polimorfismo

Polimorfismo é uma das características cruciais da POO. Significa simplesmente um nome, múltiplas formas. Já vimos como o conceito de polimorfismo é implementado usando funções e operadores sobrecarregados. As funções membro sobrecarregadas são selecionadas para invocação combinando argumentos, tanto tipo quanto número. O compilador conhece essa informação no tempo de compilação e, portanto, é capaz de selecionar a função apropriada para uma chamada particular no próprio tempo de compilação. Isso é chamado de vinculação antecipada ou vinculação estática ou linkagem estática. Também conhecido como polimorfismo em tempo de compilação, vinculação antecipada simplesmente significa que um objeto é vinculado às suas chamadas de funções no tempo de compilação.

Agora, vamos considerar uma situação onde o nome da função e o protótipo são os mesmos nas classes base e derivadas. Por exemplo, considere as seguintes definições de classe.

| | #include
using namespace std;
class A
{
int x;
public : void show();
};

class B : public A
{
int y;
public : void show();
};

int main()
{
B b;
b.show();
return 0;
} |

Como usamos a função membro show( ) para imprimir os valores dos objetos das classes A e B? Como o protótipo de show( ) é o mesmo em ambos os lugares, a função não é sobrecarregada e, portanto, a vinculação estática não se aplica. Na verdade, o compilador não sabe o que fazer e adia a decisão.

Seria bom se a função membro apropriada pudesse ser selecionada enquanto o programa está em execução. Isso é conhecido como polimorfismo em tempo de execução. Como isso poderia acontecer? O C++ suporta um mecanismo conhecido como função virtual para alcançar o polimorfismo em tempo de execução. Em tempo de execução, quando se sabe quais objetos de classe estão em consideração, a versão apropriada da função é chamada.

Como a função está vinculada a uma classe particular muito depois da compilação, esse processo é denominado vinculação tardia. Também é conhecido como vinculação dinâmica ou linkagem dinâmica porque a seleção da função apropriada é feita dinamicamente em tempo de execução.

3. Tipos de Polimorfismo

Polimorfismo é de dois tipos, a saber.

| 1 | Polimorfismo em tempo de compilação
Ou Vinculação Antecipada
Ou Vinculação Estática
Ou Polimorfismo de Linkagem Estática. Um objeto é vinculado à sua chamada de função em tempo de compilação. |

| 2 | Polimorfismo em tempo de execução
Ou vinculação tardia
Ou Vinculação Dinâmica
Ou Polimorfismo de Linkagem Dinâmica. A seleção e a função apropriada são feitas dinamicamente em tempo de execução. |

| | |

A vinculação dinâmica é uma das características poderosas do C++. Isso requer o uso de ponteiros para objetos. Discutiremos em detalhes como os ponteiros de objetos e funções virtuais são usados para implementar a vinculação dinâmica.

4. Funções Membro Normais Acessadas com Ponteiros

O programa abaixo consiste em uma classe base

| | / Funções normais acessadas a partir de ponteiro /
/ Polimorfismo com classes (sem usar polimorfismo VIRTUAL /
#include
using namespace std;

class BASE
{
public :
void disp() { cout << “\nVocê está na classe BASE “; }
};

class DERIVED1 : public BASE
{
public :
void disp() { cout << “\nVocê está na classe DERIVED1”; }
};

class DERIVED2 : public BASE
{
public :
void disp() { cout << “\nVocê está na classe DERIVED2”; }
};

int main()
{
DERIVED1 d1;               // Objeto da classe derivada 1
DERIVED2 d2;               // Objeto da classe derivada 2
BASE *b;                      // ponteiro para a classe base

b=&d1;                          // Atribui o endereço de d1 ao ponteiro b
b->disp();                      // chamada para disp()
b=&d2;                          // Atribui o endereço de d2 ao ponteiro b
b->disp();                      // chamada para disp()
return 0;
} |

O programa acima demonstra:

| | • Uma classe BASE
• Classes DERIVED1, DERIVED2 derivadas de BASE
• Objetos de classes derivadas (d1,d2)
• Ponteiro da classe BASE b | | | *Saída Você está na classe BASE
Você está na classe BASE |

5. Função Virtual

Virtual significa existente em efeito, mas não na realidade.
Uma função membro pode ser feita como função virtual precedendo a função membro com a palavra-chave virtual.

| | / Polimorfismo com Classes (Polimorfismo Virtual) /
#include
using namespace std;
class B
{
public :
void show(){ cout << “\nclasse B método Show() “; }
virtual void disp() { cout << “\nclasse B método disp()”; }
};

class D : public B
{
public :
void show(){cout << “\nclasse D método Show() “; }
void disp(){ cout << “\nclasse D método disp()”; }
};

int main()
{
D d1;
d1.show();
d1.disp(); // Membro da classe base

B b;
D d;
B *Bptr;
Bptr = &b;
Bptr->show();
Bptr->disp(); // Membro da classe base

Bptr=&d;
Bptr->show(); // membros da classe derivada
Bptr->disp(); // Membro da classe base
return 0;
} | | | Saída método Show() da classe D
método disp() da classe D |

6. Função Pura

Uma função definida em uma classe base e que não tem definição relativa à classe derivada é chamada de função pura. Em palavras simples, uma função pura é uma função virtual sem corpo.

| | #include
using namespace std;
class B
{
public :
void show(){ cout << “\nclasse B método Show() “; }
virtual void disp() = 0; // função virtual pura
};

class D : public B
{
public :
void show(){cout << “\nclasse D método Show() “; }
void disp(){ cout << “\nclasse D método disp()”; }
};

int main()
{
D d1;
d1.show(); // O/P : Método show() da classe D
d1.disp(); // O/P : Método disp() da classe D

D d;
B *Bptr;

Bptr=&d;
Bptr->show(); // O/P : Método show() da classe B
Bptr->disp(); // O/P : Método disp() da classe D
return 0;
} |

Bptr -> show() * é a função executável padrão da Base Bptr -> disp() é a função executável padrão da Base, mas é declarada como uma função virtual pura, então em tempo de execução a disp()* da classe derivada será chamada.

| | / Programa para demonstrar a vantagem das funções virtuais puras /
#include
using namespace std;
enum boolean { false, true };
class NAME
{
protected : char name[20];
public :
void getname()
{ cout << “Digite o nome :”; cin >> name; }

void showname()
{ cout << “\nO nome é “<< name; }

boolean virtual isGradeA() = 0; // função virtual pura
};

class student : public NAME
{
private : float avg;
public :
void getavg()
{
cout << “\nDigite a Média do Estudante :”;
cin >> avg;
}

boolean isGradeA()
{ return (avg>=80) ? true : false ; }
};

class employee : public NAME
{
private : int sal;
public :
void getsal()
{ cout << “\nDigite o salário “; cin >> sal; }

boolean isGradeA()
{ return (sal>=10000) ? true : false ; }
};

int main()
{
NAME names[20]; // número de ponteiros para nome
student
s; // ponteiro para estudante
employee *e; // ponteiro para funcionário
int n = 0; // número de Nomes na lista
char choice;
do{
cout << “Digite Estudante ou Funcionário (s/e) “;
cin >> choice;
if(choice==’s’)
{
s = new student; // cria um novo estudante
s->getname();
s->getavg();
names[n++]=s;
}
else
{
e = new employee; // cria um novo funcionário
e->getname();
e->getsal();
names[n++]=e;
}
cout << “Digite outro (y/n) ?”; // faça outro
cin >> choice;
} while(choice==’y’);
for(int j=0; j{
names[j]->showname( );
if(names[j]->isGradeA( )==true)
cout << “Ele é uma pessoa do Grau 1”;
}
return 0;
} |

7. Atribuição e Inicialização por Cópia

O compilador C++ está sempre ocupado em seu nome, fazendo coisas que você não se incomoda em fazer. Se você assumir o controle, ele se submeterá ao seu julgamento; caso contrário, ele fará as coisas do seu próprio jeito. Dois exemplos importantes desse processo são o operador de atribuição e o construtor de cópia.

Você já usou o operador de atribuição muitas vezes, provavelmente sem pensar muito sobre isso. Suponha que a1 e a2 sejam objetos. A menos que você diga ao compilador o contrário, a instrução.

a2 = a1;            // define a2 para o valor de a1

Causará o compilador copiar os dados de a1, membro por membro, para a2. Esta é a ação padrão do operador de atribuição, =.

Você também está familiarizado com a inicialização de variáveis, inicializando um objeto com outro objeto, como em

alpha a2(a1);    // inicializa a2 para o valor de a1

Causa uma ação semelhante. O compilador cria um novo objeto, a2, e copia os dados de a1, membro por membro, para a2. Esta é a ação padrão do construtor de cópia.

Ambas essas atividades padrão são fornecidas, gratuitamente, pelo compilador. Se a cópia membro por membro é o que você deseja, você não precisa tomar mais nenhuma ação. No entanto, se você quiser que a atribuição de inicialização faça algo mais complexo, então você pode substituir as funções padrão. Discutiremos as técnicas para sobrecarregar o operador de atribuição e o construtor de cópia separadamente.

Sobrecarga do Operador de Atribuição

| | // Sobrecarga do Operador de Atribuição ( = )
#include
using namespace std;
class alpha
{
private:
int data;
public:
alpha() { } // construtor sem argumentos
alpha( int d )
{ data = d; } // construtor com um argumento
void display()
{ cout << data; } // Exibir dados
alpha operator =(alpha & a) // operador = sobrecarregado
{
data = a.data; // não feito automaticamente
cout << “\n Operador de atribuição invocado “;
return alpha(data);
}
};

int main()
{
alpha a1(37);
alpha a2;
a2 = a1; // Invoca sobrecarregado =
cout << “\n a2 = “; a2.display(); // exibe a2

alpha a3 = a2; // NÃO invoca =
cout << “\n a3 = “; a3.display(); // exibe a3
return 0;
}
| | | Saída: a2 = 37
a3 = 37 |

8. O CONSTRUTOR DE CÓPIA

Como discutimos, podemos definir e ao mesmo tempo inicializar um objeto para o valor de outro objeto com dois tipos de instrução:

alpha a3(a2);                            // Inicialização por cópia
alpha a3 = a2;                          // inicialização por cópia, sintaxe alternativa

Ambos os estilos de definição invocam um construtor de cópia: ou seja, um construtor que copia seu argumento em um novo objeto. O construtor de cópia padrão, que é fornecido automaticamente pelo compilador para cada objeto, realiza uma cópia membro por membro. Isso é semelhante ao que o operador de atribuição faz; a diferença é que o construtor de cópia também cria um novo objeto.

O seguinte exemplo demonstra o construtor de cópia.

| | #include
using namespace std;
class alpha
{
private :
int data;
public:
alpha( ) { } // construtor sem argumentos
alpha(int d) { data = d; } // construtor com um argumento
alpha(alpha& a) // construtor de cópia
{
data = a.data;
cout << “\nConstrutor de cópia invocado”;
}
void display( )
{ cout << data; }
void operator = (alpha& a) // operador = sobrecarregado
{
data = a.data;
cout << “\nOperador de atribuição invocado”;
}
};

int main()
{
alpha a1( 37 );
alpha a2;

a2 = a1; // invoca sobrecarregado =
cout << “ a2 = “; a2.display(); // exibe a2

alpha a3( a1 ); // invoca construtor de cópia

// alpha a3 = a1; // definição equivalente de a3

cout << “ a3 = “; a3.display(); // exibe a3
return 0;
} |

O programa acima sobrecarrega tanto o operador de atribuição quanto o construtor de cópia.
O operador de atribuição sobrecarregado é semelhante ao do exemplo anterior.

9. Ponteiro ‘this’

O C++ usa uma palavra-chave única chamada this para representar um objeto que invoca uma função membro. This é um ponteiro que aponta para o objeto para o qual a função this foi chamada.

Esse ponteiro simplesmente realiza a tarefa de retornar o objeto em si.

O seguinte programa define os objetos i, j e i é atribuído com o valor de 5 e o objeto inteiro de i é atribuído pela sua função membro a j

| | #include
using namespace std;
class A
{
int a;
public:
A() { }
A(int x)
{ a = x; }
void display()
{
cout << a;
}
A get()
{

return *this; // Retornar a si mesmo

}
};
int main()
{
A i(5);
A j;

j = i.get();
j.display();
return 0;
} |

Ref: Programação orientada a objetos em Turbo C++: Robert Lafore

Share: X/Twitter LinkedIn

Receba novas postagens na sua caixa de entrada

Sem spam. Cancele a assinatura a qualquer momento.