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();
}

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 objeto Carro c; e é chamada a expressão c.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úblico mover que não recebe parâmetros e não retorna nada (tipo void). Esse método escreve uma mensagem no cout.
  • 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 é exatamente Carro, uma chamada c.mover() vai procurar em Carro primeiro e encontrará Carro::mover.

Passo 3

O que acontece na chamada c.mover(); do main:
  • c foi declarado como Carro c;, ou seja, a variável c tem tipo estático e dinâmico Carro.
  • 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 encontra Carro::mover.
  • Não há necessidade de ligar para Veiculo::mover porque Carro já tem mover.
  • 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::mover não é declarado virtual. 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:
      Carro c;
      Veiculo* p = &c;
      p->mover(); // chama Veiculo::mover porque mover não é virtual
      
      Saída: “O veículo está se movendo.”
    • Tornando o método virtual na base:
      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"; }
      };
      
      Agora:
      Carro c;
      Veiculo* p = &c;
      p->mover(); // chama Carro::mover, porque mover é virtual -> ligação dinâmica
      
      Saída: “O carro está andando.”
  • Resumo prático: para ter comportamento polimórfico via ponteiro/referência de base, declare o método base como virtual. A palavra override na 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:
    c.Veiculo::mover(); // chama o mover definido em Veiculo
    
    Isso imprimirá: “O veículo está se movendo.”

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 tipo Carro encontra Carro::mover.
  • Carro::mover é chamada e executa cout << "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 se mover for virtual ou não.
  • Use virtual na base quando quiser polimorfismo em tempo de execução. Use override na 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 virtual chamando via ponteiro de base chama método da base; com virtual chama 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.

Exercícios relacionados