Considere o código abaixo:
#include <iostream>
using namespace std;
class Veiculo {
public:
void mover() { cout << "O veículo está se movendo.\n"; }
};
class Carro : public Veiculo {
public:
void mover() { cout << "O carro está andando.\n"; }
};
int main() {
Carro c;
c.mover();
}
#include <iostream>
using namespace std;
class Veiculo {
public:
void mover() { cout << "O veículo está se movendo.\n"; }
};
class Carro : public Veiculo {
public:
void mover() { cout << "O carro está andando.\n"; }
};
int main() {
Carro c;
c.mover();
}
Passo 1
O enunciado nos dá um programa em C++ (o código abaixo) e pede para determinar o que acontece quando ele é executado e por quê:
#include <iostream>
using namespace std;
class Veiculo {
public:
void mover() { cout << "O veículo está se movendo.\n"; }
};
class Carro : public Veiculo {
public:
void mover() { cout << "O carro está andando.\n"; }
};
int main() {
Carro c;
c.mover();
}
Informações relevantes fornecidas pelo enunciado:
- Há uma classe base chamada Veiculo com um método público chamado mover que imprime “O veículo está se movendo.\n”.
- Há uma classe derivada chamada Carro que herda publicamente de Veiculo e também declara um método público mover que imprime “O carro está andando.\n”.
- No
mainé criado um objetoCarro c;e é chamada a expressãoc.mover();. - A tarefa é identificar qual linha será impressa na saída e entender o comportamento do código.
Passo 2
Entendendo declaração das classes e dos métodos (passo a passo, devagarinho):
class Veiculo { public: void mover() { ... } };define uma classe Veiculo com um método membro públicomoverque não recebe parâmetros e não retorna nada (tipovoid). Esse método escreve uma mensagem nocout.class Carro : public Veiculo { public: void mover() { ... } };define Carro como derivada de Veiculo. Dentro de Carro existe outro método com o mesmo nome e mesma assinatura:void mover().- Quando uma classe derivada declara um método com a mesma assinatura de um método da base, a função da derivada “esconde” ou “substitui” a da base no contexto de objetos do tipo derivado. Em C++ há detalhamento entre “esconder” e “sobrescrever” dependendo de
virtual, mas a ideia prática é: quando você tem um objeto cujo tipo é exatamenteCarro, uma chamadac.mover()vai procurar emCarroprimeiro e encontraráCarro::mover.
Passo 3
O que acontece na chamada
c.mover(); do main:cfoi declarado comoCarro c;, ou seja, a variávelctem tipo estático e dinâmicoCarro.- O compilador resolve chamadas de métodos por nome e assinatura no tipo da expressão. Como a expressão é um objeto do tipo
Carro, o compilador encontraCarro::mover. - Não há necessidade de ligar para
Veiculo::moverporqueCarrojá temmover. - Resultado: é chamada a função que está implementada em
Carro, e ela imprime: O carro está andando. seguido de nova linha.
Passo 4
Explicando a diferença importante que costuma cair em prova: ligação estática vs. ligação dinâmica (ou runtime polymorphism)
- No código dado,
Veiculo::movernão é declaradovirtual. Em C++, métodos não-virtuais usam ligação estática: a função chamada é decidida em tempo de compilação com base no tipo da expressão. - Se você tivesse um ponteiro ou referência para a base, a chamada se comportaria diferente dependendo de
virtual:- Exemplo sem
virtual:
Saída: “O veículo está se movendo.”Carro c; Veiculo* p = &c; p->mover(); // chama Veiculo::mover porque mover não é virtual - Tornando o método virtual na base:
Agora:class Veiculo { public: virtual void mover() { cout << "O veículo está se movendo.\n"; } }; class Carro : public Veiculo { public: void mover() override { cout << "O carro está andando.\n"; } };
Saída: “O carro está andando.”Carro c; Veiculo* p = &c; p->mover(); // chama Carro::mover, porque mover é virtual -> ligação dinâmica
- Exemplo sem
- Resumo prático: para ter comportamento polimórfico via ponteiro/referência de base, declare o método base como
virtual. A palavraoverridena derivada é uma boa prática (ajuda o compilador a checar assinatura).
Passo 5
Como chamar explicitamente o método da classe base se precisar (truque útil em prova):
- Mesmo com
Carro c;, você pode forçar a chamada ao método da classe base usando resolução de escopo:
Isso imprimirá: “O veículo está se movendo.”c.Veiculo::mover(); // chama o mover definido em Veiculo
Passo 6
Executando mentalmente o programa original, linha a linha:
- Compilador lê as definições: Veiculo::mover e Carro::mover existem.
- No
main,Carro c;constrói um objeto Carro. - Chamada
c.mover();— pesquisa no tipoCarroencontraCarro::mover. Carro::moveré chamada e executacout << "O carro está andando.\n";.- Programa termina.
Passo 7
Dicas de prova e erros comuns que eu já fiz quando comecei:
- Se a pergunta for “qual a saída?”, confirme se a chamada é feita pelo objeto derivado (
Carro c; c.mover();) ou por um ponteiro/referência de base (Veiculo* p = &c; p->mover();). O comportamento muda semoverforvirtualou não. - Use
virtualna base quando quiser polimorfismo em tempo de execução. Useoverridena derivada para garantir que você está mesmo sobrescrevendo (o compilador avisa se a assinatura estiver errada). - Para provas, escreva pequenos exemplos como os acima para demonstrar entendimento: sem
virtualchamando via ponteiro de base chama método da base; comvirtualchama o da derivada. - Se precisar da versão da base mesmo quando houver a derivada, use
obj.Base::metodo().
Passo 8
Recapitulando com calma: no código original, a chamada
c.mover(); usa o método da classe derivada Carro::mover, que escreve a linha “O carro está andando.”.Resposta
O carro está andando.