Implementação de Polimorfismo e Herança em C++ com Exercícios Práticos

Tarefa 1: Gerenciamento de Notas com Composição

Código Fonte

CalculoNotas.hpp

#pragma once

#include <vector>
#include <array>
#include <string>

class CalculoNotas {
public:
    CalculoNotas(const std::string &nomeDisciplina);      
    void inserirDados(int qtd);                         // Inserir qtd notas
    void exibirDados() const;                           // Exibir notas
    void ordenar(bool crescente = false);               // Ordenar (padrão decrescente)
    int obterMinimo() const;                            // Retornar nota mínima (-1 se vazio)
    int obterMaximo() const;                            // Retornar nota máxima (-1 se vazio)
    double calcularMedia() const;                       // Retornar média (0.0 se vazio)
    void mostrarInfo();                                 // Exibir informações da disciplina

private:
    void atualizarEstatisticas();     // Calcular estatísticas

private:
    std::string nomeDisciplina;       // Nome da disciplina
    std::vector<int> notas;            // Notas da disciplina
    std::array<int, 5> contagens;     // Contagem por faixa ([0,60), [60,70), [70,80), [80,90), [90,100])
    std::array<double, 5> taxas;      // Proporção por faixa
    bool dadosAlterados;              // Flag para dados alterados
};

CalculoNotas.cpp

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

#include "CalculoNotas.hpp"

CalculoNotas::CalculoNotas(const std::string &nomeDisciplina): nomeDisciplina{nomeDisciplina}, dadosAlterados{true} {
    contagens.fill(0);
    taxas.fill(0);   
}

void CalculoNotas::inserirDados(int qtd) {
    if(qtd < 0) {
        std::cerr << "Entrada inválida! Quantidade não pode ser negativa\n";
        std::exit(1);
    }

    notas.reserve(qtd);

    int nota;

    for(int i = 0; i < qtd;) {
        std::cin >> nota;

        if(nota < 0 || nota > 100) {
            std::cerr << "Entrada inválida! Nota deve estar em [0,100]\n";
            continue;
        }
        
        notas.push_back(nota);
        ++i;
    }

    dadosAlterados = true;
}

void CalculoNotas::exibirDados() const {
    for(auto nota: notas)
        std::cout << nota << ' ';
    std::cout << std::endl;
}
    
void CalculoNotas::ordenar(bool crescente) {
    if(crescente)
        std::sort(notas.begin(), notas.end());
    else
        std::sort(notas.begin(), notas.end(), std::greater<int>());
}

int CalculoNotas::obterMinimo() const {
    if(notas.empty())
        return -1;

    auto it = std::min_element(notas.begin(), notas.end());
    return *it;
}

int CalculoNotas::obterMaximo() const {
    if(notas.empty()) 
        return -1;

    auto it = std::max_element(notas.begin(), notas.end());
    return *it;
}

double CalculoNotas::calcularMedia() const {
    if(notas.empty())
        return 0.0;

    double media = std::accumulate(notas.begin(), notas.end(), 0.0)/notas.size();
    return media;
}

void CalculoNotas::mostrarInfo() {
    if(dadosAlterados) 
       atualizarEstatisticas();

    std::cout << "Disciplina:\t" << nomeDisciplina << std::endl;
    std::cout << "Média:\t" << std::fixed << std::setprecision(2) << calcularMedia() << std::endl;
    std::cout << "Máximo:\t" << obterMaximo() << std::endl;
    std::cout << "Mínimo:\t" << obterMinimo() << std::endl;
    
    std::vector<int> temp = notas;
    std::sort(temp.begin(), temp.end());
    if (notas.size() % 2 == 1)
        std::cout << "Mediana:\t" << static_cast<double>(temp[notas.size() / 2]) << '\n';
    else
        std::cout << "Mediana:\t" <<  static_cast<double>((temp[notas.size() / 2 - 1] + temp[notas.size() / 2]) / 2.0) << '\n';
    
    const std::array<std::string, 5> faixas{"[0, 60) ", 
                                           "[60, 70)", 
                                           "[70, 80)",
                                           "[80, 90)", 
                                           "[90, 100]"};
    
    for(int i = static_cast<int>(faixas.size())-1; i >= 0; --i)
        std::cout << faixas[i] << "\t: " << contagens[i] << " alunos\t"
                  << std::fixed << std::setprecision(2) << taxas[i]*100 << "%\n";
}

void CalculoNotas::atualizarEstatisticas() {
    if(notas.empty())
        return;

    contagens.fill(0); 
    taxas.fill(0.0);

    for(auto nota:notas) {
        if(nota < 60)
            ++contagens[0];
        else if (nota < 70)
            ++contagens[1];
        else if (nota < 80)
            ++contagens[2];
        else if (nota < 90)
            ++contagens[3];
        else
            ++contagens[4];
    }

    for(size_t i = 0; i < taxas.size(); ++i)
        taxas[i] = contagens[i] * 1.0 / notas.size();
    
    dadosAlterados = false;
}

teste1.cpp

#include <iostream>
#include <string>
#include "CalculoNotas.hpp"

void teste() {
    CalculoNotas c1("POO");

    std::cout << "Inserir notas:\n";
    c1.inserirDados(5);

    std::cout << "Exibir notas:\n";
    c1.exibirDados();

    std::cout << "Notas ordenadas:\n";
    c1.ordenar(); c1.exibirDados();

    std::cout << "*************Estatísticas*************\n";
    c1.mostrarInfo();
}

int main() {
    teste();
}

Resultados do Teste

Execução demonstra entrada, ordenação e exibição de estatísticas.

Respostas às Questões

  • Questão 1: Relação de composição: a classe CalculoNotas contém membros como std::string nomeDisciplina, std::vector<int> notas, etc.
  • Questão 2: c.inserirDados(5) é legal, pois é um método público. c.push_back(97) não é legal, pois CalculoNotas não herda ou expõe push_back.
  • Questão 3: (1) atualizarEstatisticas() é chamada apenas quando dadosAlterados é true. (2) Não precisa mudar a chamada; basta atualizar a flag.
  • Questão 4: A mediana é calculada em mostrarInfo(), usando ordenação temporária.
  • Questão 5: As linhas contagens.fill(0) e taxas.fill(0) em atualizarEstatisticas() são necessárias parra resetar os acumuladores.
  • Questão 6: (1) Sem impacto funcional. (2) Melhora performance por alocação prévia de memória.

Tarefa 2: Gerenciamento de Notas com Herança

Código Fonte

CalculoNotasHeranca.hpp

#pragma once

#include <array>
#include <string>
#include <vector>

class CalculoNotasHeranca: private std::vector<int> {
public:
    CalculoNotasHeranca(const std::string &nomeDisciplina);      
    void inserirDados(int qtd);                        
    void exibirDados() const;                      
    void ordenar(bool crescente = false);        
    int obterMinimo() const;                          
    int obterMaximo() const;                          
    double calcularMedia() const;                   
    void mostrarInfo();                              

private:
    void atualizarEstatisticas();               

private:
    std::string nomeDisciplina;     
    std::array<int, 5> contagens;   
    std::array<double, 5> taxas; 
    bool dadosAlterados;      
};

CalculoNotasHeranca.cpp

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include "CalculoNotasHeranca.hpp"

CalculoNotasHeranca::CalculoNotasHeranca(const std::string &nomeDisciplina): nomeDisciplina{nomeDisciplina}, dadosAlterados{true}{
    contagens.fill(0);
    taxas.fill(0);
}   

void CalculoNotasHeranca::inserirDados(int qtd) {
    if(qtd < 0) {
        std::cerr << "Entrada inválida! Quantidade não pode ser negativa\n";
        return;
    }

    this->reserve(qtd);

    int nota;

    for(int i = 0; i < qtd;) {
        std::cin >> nota;
        if(nota < 0 || nota > 100) {
            std::cerr << "Entrada inválida! Nota deve estar em [0,100]\n";
            continue;
        }

        this->push_back(nota);
        ++i;
    } 

    dadosAlterados = true;
}  

void CalculoNotasHeranca::exibirDados() const {
    for(auto nota: *this)
        std::cout << nota << ' ';
    std::cout << std::endl;
} 

void CalculoNotasHeranca::ordenar(bool crescente) {
    if(crescente)
        std::sort(this->begin(), this->end());
    else
        std::sort(this->begin(), this->end(), std::greater<int>());
}  

int CalculoNotasHeranca::obterMinimo() const {
    if(this->empty())
        return -1;

    return *std::min_element(this->begin(), this->end());
}  

int CalculoNotasHeranca::obterMaximo() const {
    if(this->empty())
        return -1;

    return *std::max_element(this->begin(), this->end());
}    

double CalculoNotasHeranca::calcularMedia() const {
    if(this->empty())
        return 0.0;

    double media = std::accumulate(this->begin(), this->end(), 0.0) / this->size();
    return media;
}   

void CalculoNotasHeranca::mostrarInfo() {
    if(dadosAlterados) 
        atualizarEstatisticas();

    std::cout << "Disciplina:\t" << nomeDisciplina << std::endl;
    std::cout << "Média:\t" << std::fixed << std::setprecision(2) << calcularMedia() << std::endl;
    std::cout << "Máximo:\t" << obterMaximo() << std::endl;
    std::cout << "Mínimo:\t" << obterMinimo() << std::endl;

    const std::array<std::string, 5> faixas{"[0, 60) ", 
                                           "[60, 70)", 
                                           "[70, 80)",
                                           "[80, 90)", 
                                           "[90, 100]"};
    
    for(int i = static_cast<int>(faixas.size())-1; i >= 0; --i)
        std::cout << faixas[i] << "\t: " << contagens[i] << " alunos\t"
                  << std::fixed << std::setprecision(2) << taxas[i]*100 << "%\n";
}

void CalculoNotasHeranca::atualizarEstatisticas() {
    if(this->empty())
        return;
    
    contagens.fill(0);
    taxas.fill(0);

    for(int nota: *this) {
        if(nota < 60)
            ++contagens[0];
        else if (nota < 70)
            ++contagens[1];
        else if (nota < 80)
            ++contagens[2];
        else if (nota < 90)
            ++contagens[3];
        else
            ++contagens[4];
    }

    for(size_t i = 0; i < taxas.size(); ++i)
        taxas[i] = contagens[i] * 1.0 / this->size();
    
    dadosAlterados = false;
}

teste2.cpp

#include <iostream>
#include <string>
#include "CalculoNotasHeranca.hpp"

void teste() {
    CalculoNotasHeranca c1("POO");

    std::cout << "Inserir notas:\n";
    c1.inserirDados(5);

    std::cout << "Exibir notas:\n";
    c1.exibirDados();

    std::cout << "Notas ordenadas:\n";
    c1.ordenar(); c1.exibirDados();

    std::cout << "*************Estatísticas*************\n";
    c1.mostrarInfo();
}

int main() {
    teste();
}

Resultados do Teste

Similar à Tarefa 1, mas com implementação baseada em herança.

Respostas às Questões

  • Questão 1: Relação de herança: class CalculoNotasHeranca : private std::vector<int>
  • Questão 2: Os métodos de vector não são automaticamente públicos devido à herança privada. push_back não é acessível externamente.
  • Questão 3: for(auto nota : notas) usa iteradores do vector; for(int nota : *this) requer begin()/end() na classe.
  • Questão 4: Composição é mais flexível; herança fixa a estrutura de armazenamento.

Tarefa 3: Hierarquia de Gráficos com Polimorfismo

Código Fonte

Grafico.hpp

#pragma once

#include <string>
#include <vector>

enum class TipoGrafico {circulo, triangulo, retangulo};

// Classe base
class Grafico {
public:
    virtual void desenhar() {}
    virtual ~Grafico() = default;
};

// Derivadas
class Circulo : public Grafico {
public:
    void desenhar() override;
};

class Triangulo : public Grafico {
public:
    void desenhar() override;
};

class Retangulo : public Grafico {
public:
    void desenhar() override;
};

// Classe canvas
class Tela {
public:
    void adicionar(const std::string& tipo);
    void pintar() const;
    ~Tela();

private:
    std::vector<Grafico*> graficos;          
};

// Funções utilitárias
TipoGrafico stringParaTipo(const std::string& s);
Grafico* criarGrafico(const std::string& tipo);

Grafico.cpp

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

#include "Grafico.hpp"

void Circulo::desenhar()     { std::cout << "Desenhando círculo...\n"; }
void Triangulo::desenhar()   { std::cout << "Desenhando triângulo...\n"; }
void Retangulo::desenhar()  { std::cout << "Desenhando retângulo...\n"; }

void Tela::adicionar(const std::string& tipo) {
    Grafico* g = criarGrafico(tipo);
    if (g) 
        graficos.push_back(g);
}

void Tela::pintar() const {
    for (Grafico* g : graficos) 
        g->desenhar();   
}

Tela::~Tela() {
    for (Grafico* g : graficos) 
        delete g;
}

TipoGrafico stringParaTipo(const std::string& s) {
    std::string t = s;
    std::transform(s.begin(), s.end(), t.begin(),
                   [](unsigned char c) { return std::tolower(c);});

    if (t == "circulo")   return TipoGrafico::circulo;
    if (t == "triangulo") return TipoGrafico::triangulo;
    if (t == "retangulo") return TipoGrafico::retangulo;
    return TipoGrafico::circulo;
}

Grafico* criarGrafico(const std::string& tipo) {
    switch (stringParaTipo(tipo)) {
    case TipoGrafico::circulo:     return new Circulo;
    case TipoGrafico::triangulo:   return new Triangulo;
    case TipoGrafico::retangulo:   return new Retangulo;
    default: return nullptr;
    }
}

demo3.cpp

#include <string>
#include "Grafico.hpp"

void teste() {
    Tela tela;

    tela.adicionar("circulo");
    tela.adicionar("triangulo");
    tela.adicionar("retangulo");
    tela.pintar();
}

int main() {
    teste();
}

Resultados do Teste

Saída mostra desenho de cada gráfico.

Respostas às Questões

  • Questão 1: Composição: std::vector<Grafico*>; Herança: classes derivadas.
  • Questão 2: (1) Sem virtual, desenhar() base seria chamada, sem efeito. (2) Vetor de ponteiros permite polimorfismo; vetor de objetos não. (3) Destrutor virtual evita leaks.
  • Questão 3: Para adicionar estrela: adicionar classe Estrela, atualizar enum, stringParaTipo(), criarGrafico().
  • Questão 4: (1) Objetos criados com new são liberados pelo destrutor de Tela. (2) Vantagens: gerenciamento unificado; desvantagens: risco de leaks e complexidade.

Tarefa 4: Fábrica de Brinquedos com Padrão Factory

Código Fonte

Brinquedo.hpp

#pragma once
#include <string>

enum class TipoBrinquedo
{
	DESCONHECIDO,
	CACHORRO,
	GATO,
	URSO,
};

class Brinquedo
{
public:	
	Brinquedo(const std::string& nome = "Nome Padrão", const std::string& material = "Material Padrão", const std::string& audio = "Música Padrão");
	virtual ~Brinquedo();

	virtual void iniciar() = 0;
	virtual void agir() = 0;
	virtual void tocarAudio() = 0;
	virtual void desligar() = 0;
	void exibirInfo();

	const std::string& obterNome() const { return nome; }
protected:
	std::string nome;
	std::string material;
	std::string audio;
	TipoBrinquedo tipo;
};

Brinquedo.cpp

#include "Brinquedo.hpp"
#include <iostream>

Brinquedo::Brinquedo(const std::string& nome0, const std::string& material0, const std::string& audio0) :
	nome{ nome0 }, material{ material0 }, audio{ audio0 }, tipo{TipoBrinquedo::DESCONHECIDO} { }

Brinquedo::~Brinquedo() = default;

void Brinquedo::exibirInfo()
{
	std::cout << nome << '\t' << material << '\t';
	switch (tipo)
	{
	case TipoBrinquedo::CACHORRO:
		std::cout << "Cachorro\t";
		break;
	case TipoBrinquedo::GATO:
		std::cout << "Gato\t";
		break;
	case TipoBrinquedo::URSO:
		std::cout << "Urso\t";
		break;
	default:
		std::cout << "Tipo Desconhecido\t";
	}
	std::cout << audio << "\n";
}

BrinquedoCachorro.hpp

#pragma once
#include "Brinquedo.hpp"

class BrinquedoCachorro : public Brinquedo
{
public:
	BrinquedoCachorro(const std::string& nome = "Cachorro Amigo", const std::string& material = "Algodão", const std::string& audio = "Música Padrão");
	
	void iniciar() override;
	void agir() override;
	void tocarAudio() override;
	void desligar() override;

private:
	void latir();
	static constexpr const char* latido = "Au au!";
};

BrinquedoCachorro.cpp

#include "BrinquedoCachorro.hpp"
#include <iostream>

BrinquedoCachorro::BrinquedoCachorro(const std::string& nome0, const std::string& material0, const std::string& audio0) : Brinquedo(nome0,material0,audio0)
{
	tipo = TipoBrinquedo::CACHORRO;
}

void BrinquedoCachorro::iniciar()
{
	std::cout << "Oi! Meu nome é " << nome << ". Vamos brincar! ";
	latir();
	std::cout << std::endl;
}

void BrinquedoCachorro::agir()
{
	std::cout << nome << " está abanando o rabo!\n";
}

void BrinquedoCachorro::tocarAudio()
{
	std::cout << "Tocando: " << audio << '\n';
}

void BrinquedoCachorro::desligar()
{
	std::cout << "Adeus! ";
	latir();
	std::cout << '\n';
}

void BrinquedoCachorro::latir()
{
	std::cout << latido;
}

Notas: Outras classes como BrinquedoGato, BrinquedoUrso seguem estrutura similar.

FabricaBrinquedos.hpp

#include "Brinquedo.hpp"
#include "BrinquedoUrso.hpp"
#include "BrinquedoCachorro.hpp"
#include "BrinquedoGato.hpp"
#include <string>
#include <vector>
class FabricaBrinquedos
{
public:
	FabricaBrinquedos(const std::string& titulo0);
	~FabricaBrinquedos();
	void exibir() const;
	void acao() const;
	void tocarMusica() const;
	void adicionarBrinquedo(const std::string& tipo = "desconhecido", const std::string& nome = "Nome Padrão",
		const std::string& material = "Material Padrão", const std::string& audio = "Música Padrão");
	void removerBrinquedo(const std::string& alvo);

private:
	std::string titulo;
	std::vector<Brinquedo*> brinquedos;
};

TipoBrinquedo stringParaTipoBrinquedo(const std::string& s);

FabricaBrinquedos.cpp

#include "FabricaBrinquedos.hpp"
#include <algorithm>
#include <cctype>
#include <iostream>

FabricaBrinquedos::FabricaBrinquedos(const std::string& titulo0) : titulo{titulo0} { }
FabricaBrinquedos::~FabricaBrinquedos()
{
	for (Brinquedo* b : brinquedos)
		delete b;
}

TipoBrinquedo stringParaTipoBrinquedo(const std::string& s)
{
	std::string t = s;
	std::transform(s.begin(), s.end(), t.begin(), [](unsigned char c) {return std::tolower(c); });
	if (t == "cachorro")
		return TipoBrinquedo::CACHORRO;
	if (t == "gato")
		return TipoBrinquedo::GATO;
	if (t == "urso")
		return TipoBrinquedo::URSO;
	return TipoBrinquedo::DESCONHECIDO;
}

void FabricaBrinquedos::exibir() const
{
	std::cout << "Informações dos Brinquedos: \n";
	if (brinquedos.size() == 0)
	{
		std::cout << "fábrica vazia!";
		return;
	}
	for (Brinquedo* b : brinquedos)
		b->exibirInfo();
}

void FabricaBrinquedos::acao() const
{
	for (auto b : brinquedos)
		b->agir();
}

void FabricaBrinquedos::tocarMusica() const
{
	for (auto b : brinquedos)
		b->tocarAudio();
}

void FabricaBrinquedos::adicionarBrinquedo(const std::string& tipo, const std::string& nome, const std::string& material, const std::string& audio)
{
	for (Brinquedo* b : brinquedos)
	{
		if (b->obterNome() == nome)
		{
			std::cerr << nome << " já existe!\n";
			return;
		}
	}
	Brinquedo* novoBrinquedo;
	switch (stringParaTipoBrinquedo(tipo))
	{
	case TipoBrinquedo::CACHORRO:
		novoBrinquedo = new BrinquedoCachorro(nome, material, audio);
		break;
	case TipoBrinquedo::GATO:
		novoBrinquedo = new BrinquedoGato(nome, material, audio);
		break;
	case TipoBrinquedo::URSO:
		novoBrinquedo = new BrinquedoUrso(nome, material, audio);
		break;
	default:
		std::cerr << "Falha ao adicionar brinquedo: " << nome << " Tipo desconhecido\n";
		return;
	}
	brinquedos.push_back(novoBrinquedo);
	std::cout << "Sucesso ao adicionar brinquedo: " << nome << '\n';
}

void FabricaBrinquedos::removerBrinquedo(const std::string& alvo)
{
	for (auto it = brinquedos.begin(); it != brinquedos.end(); it++)
	{
		if (*it && (*it)->obterNome() == alvo)
		{
			delete *it;
			brinquedos.erase(it);
			std::cout << "Remoção bem-sucedida\n";
			return;
		}
	}
	std::cerr << "alvo não encontrado";
}

principal.cpp

#include <iostream>
#include <string>
#include "FabricaBrinquedos.hpp"

void teste1()
{
	FabricaBrinquedos fabrica("Minha Fábrica de Brinquedos");
	fabrica.adicionarBrinquedo("gato", "kitty", "algodão", "Miau Matinal");
	fabrica.adicionarBrinquedo("CACHORRO", "Bruce", "Plástico", "Erica");
	fabrica.adicionarBrinquedo("Urso", "Teddy", "Algodão", "Hotel California");
	fabrica.exibir();
	fabrica.acao();
	fabrica.tocarMusica();
	fabrica.removerBrinquedo("kitty");
	fabrica.exibir();
}

int main()
{
	teste1();
}

Resultados do Teste

Demonstra criação, ação e remoção de brinquedos.

Respostas às Questões

  • Questão 1: Composição em FabricaBrinquedos (vector de ponteiros); Herança nas classes de brinquedo.
  • Questão 2: (1) Saída vazia sem virtual. (2) Vector de ponteiros permite ploimorfismo. (3) Destrutor não virtual causa leaks.
  • Questão 3: Para adicionar estrela: adicionar classe BrinquedoEstrela, atualizar enum, stringParaTipoBrinquedo(), adicionarBrinquedo().
  • Questão 4: (1) Objetos liberados pelo destrutor de FabricaBrinquedos. (2) Vantagens: gerenciamento centralizado; desvantagans: riscos de memória.

Tags: C++ Herança Polimorfismo Classes Abstratas padrão factory

Publicado em 6-19 05:40