Conceitos de Programação Orientada a Objetos em C++

Este documento explora vários cenários de implementação em C++ relacionados à programação orientada a objetos (POO), incluindo polimorfismo, herança, sobrecarga de operadores e templates.

Experimento 1: Polimorfismo e Classes Abstratas

O primiero experimento demonstra o polimorfismo em tempo de execução usando classes abstratas e funções virtuais puras. Uma classe base Publisher é definida com métodos virtuais puros publish() e use(), tornando-a uma classe abstrata. Classes derivadas como Book, Film e Music herdam de Publisher e implementam esses métodos virtuais.

Código Fonte:


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

// Implementação da classe Publisher
Publisher::Publisher(const std::string &name_): name {name_} {
}

// Implementação da classe Book
Book::Book(const std::string &name_ , const std::string &author_ ): Publisher{name_}, author{author_} {
}

void Book::publish() const {
    std::cout << "Publicando livro: <<" << name << ">> de " << author << '\n';
}

void Book::use() const {
    std::cout << "Lendo livro: <<" << name << ">> de " << author << '\n';
}

// Implementação da classe Film
Film::Film(const std::string &name_, const std::string &director_):Publisher{name_},director{director_} {
}

void Film::publish() const {
    std::cout << "Publicando filme: <" << name << "> dirigido por " << director << '\n';
}

void Film::use() const {
    std::cout << "Assistindo filme: <" << name << "> dirigido por " << director << '\n';
}

// Implementação da classe Music
Music::Music(const std::string &name_, const std::string &artist_): Publisher{name_}, artist{artist_} {
}

void Music::publish() const {
    std::cout << "Publicando música: <" << name << "> por " << artist << '\n';
}

void Music::use() const {
    std::cout << "Ouvindo música: <" << name << "> por " << artist << '\n';
}


#pragma once

#include <string>

// Classe Publisher (classe abstrata)
class Publisher {
public:
    Publisher(const std::string &name_ = "");           
    virtual ~Publisher() = default;

public:
    virtual void publish() const = 0;                 
    virtual void use() const = 0;                    

protected:
    std::string name;   
};

// Classe Book
class Book: public Publisher {
public:
    Book(const std::string &name_ = "", const std::string &author_ = "");  

public:
    void publish() const override;        
    void use() const override;            

private:
    std::string author;         
};

// Classe Film
class Film: public Publisher {
public:
    Film(const std::string &name_ = "", const std::string &director_ = "");  

public:
    void publish() const override;    
    void use() const override;         

private:
    std::string director;       
};

// Classe Music
class Music: public Publisher {
public:
    Music(const std::string &name_ = "", const std::string &artist_ = "");

public:
    void publish() const override;        
    void use() const override;           

private:
    std::string artist;     
};


#include <memory>
#include <iostream>
#include <vector>
#include "publisher.hpp"

void testRawPointers() {
   std::vector<Publisher *> publications;

   publications.push_back(new Book("Harry Potter", "J.K. Rowling"));
   publications.push_back(new Film("The Godfather", "Francis Ford Coppola"));
   publications.push_back(new Music("Blowing in the Wind", "Bob Dylan"));

   for(Publisher *p : publications) {
        p->publish();
        p->use();
        std::cout << '\n';
        delete p; // Liberar memória manualmente
   }
}

void testDirectUsage() {
    Book book("A Philosophy of Software Design", "John Ousterhout");
    book.publish();
    book.use();
}

int main() {
    std::cout << "Polimorfismo em Tempo de Execução: Funções Puras, Classes Abstratas\n";

    std::cout << "\nTeste 1: Usando ponteiros brutos\n";
    testRawPointers();

    std::cout << "\nTeste 3: Uso direto de classes\n";
    testDirectUsage();
    
    return 0;
}

Perguntas e Respostas:

  1. Motivo para Publisher ser uma classe abstrata:

    Publisher é uma classe abstrata porque contém funções virtuais puras (virtual void publish() const = 0; e virtual void use() const = 0;). Funções virtuais puras não têm implementação na classe base e impedem a instanciação direta de objetos da classe Publisher. Seu propósito é definir uma interface comum para as classes derivadas.

  2. Necessidade de implementar funções virtuais puras:

    As classes derivadas (Book, Film, Music) devem implementar as funções virtuais puras publish() e use() herdadas de Publisher para se tornarem classes concretas e poderem ser instanciadas. A assinatura da função deve corresponder exatamente à da classe base, incluindo o especificador override e const.

    void publish() const override;

    void use() const override;

    A remoção do const alteraria a assinatura da função, impedindo a sobreposição correta da função virtual pura e resultando em um erro de compilação.

  3. Ponteiros e Polimorfismo:

    No loop for(Publisher *p : publications), o tipo do ponteiro p é Publisher*. Este ponteiro pode apontar para objetos de qualquer classe derivada de Publisher.

    • Na primeira iteração, p aponta para um objeto Book.
    • Na segunda iteração, p aponta para um objeto Film.
    • Na terceira iteração, p aponta para um objeto Music.

    A declaração do destrutor em Publisher como virtual ~Publisher() = default; é crucial. Ela garante que, ao usar delete p;, o destrutor correto da classe derivada (~Book(), ~Film() ou ~Music()) seja chamado antes do destrutor da classe base. Sem o destrutor virtual, apenas o destrutor de Publisher seria chamado, levando a vazamentos de memória se as classes derivadas tivessem recursos próprios para gerenciar.

Experimento 2: Sobrecarga de Operadores e Gerenciamento de Dados

Este experimento foca na sobrrecarga de operadores para classes personalizadas, especificamente para manipulação e exibição de dados de livros e registros de vendas. As classes Book e BookSale são definidas, e o operador << é sobrecarregado para permitir a saída direta para fluxos de saída (como std::cout).

Código Fonte:


#include <iomanip>
#include <iostream>
#include <string>
#include "book.hpp"

// Classe Book: Detalhes da descrição do livro
Book::Book(const std::string &name_, 
          const std::string &author_, 
          const std::string &translator_, 
          const std::string &isbn_, 
          double price_):name{name_}, author{author_}, translator{translator_}, isbn{isbn_}, price{price_} {
}

// Implementação da sobrecarga do operador << para Book
std::ostream& operator<<(std::ostream &out, const Book &book) {
    using std::left;
    using std::setw;
    
    out << left;
    out << setw(15) << "Título:" << book.name << '\n'
        << setw(15) << "Autor:" << book.author << '\n'
        << setw(15) << "Tradutor:" << book.translator << '\n'
        << setw(15) << "ISBN:" << book.isbn << '\n'
        << setw(15) << "Preço:" << book.price;

    return out;
}


#pragma once
#include <string>

// Classe Book: Declaração
class Book {
public:
    Book(const std::string &name_ = "", 
         const std::string &author_ = "", 
         const std::string &translator_ = "", 
         const std::string &isbn_ = "", 
         double price_ = 0.0);

    friend std::ostream& operator<<(std::ostream &out, const Book &book);

private:
    std::string name;        
    std::string author;     
    std::string translator; 
    std::string isbn;       
    double price;        
};


#include <iomanip>
#include <iostream>
#include <string>
#include "booksale.hpp"

// Classe BookSale: Registros de Venda de Livros
BookSale::BookSale(const Book &rb_, 
                   double sales_price_, 
                   int sales_amount_): rb{rb_}, sales_price{sales_price_}, sales_amount{sales_amount_} {
}

int BookSale::get_amount() const {
    return sales_amount;
}

double BookSale::get_revenue() const {
    return sales_amount * sales_price;
}

// Implementação da sobrecarga do operador << para BookSale
std::ostream& operator<<(std::ostream &out, const BookSale &item) {
    using std::left;
    using std::setw;
    
    out << left;
    out << item.rb << '\n'
        << setw(15) << "Preço de Venda:" << item.sales_price << '\n'
        << setw(15) << "Quantidade Vendida:" << item.sales_amount << '\n'
        << setw(15) << "Receita:" << item.get_revenue();

    return out;
}


#pragma once

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

// Classe BookSale: Declaração
class BookSale {
public:
    BookSale(const Book &rb_, double sales_price_, int sales_amount_);
    int get_amount() const;   
    double get_revenue() const;  
    
    friend std::ostream& operator<<(std::ostream &out, const BookSale &item);

private:
    Book rb;         
    double sales_price;      
    int sales_amount;       
};


#include <algorithm>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "booksale.hpp"

// Comparador para ordenar por quantidade de vendas
bool compareByQuantity(const BookSale &s1, const BookSale &s2) {
    return s1.get_amount() > s2.get_amount();
}

void runSalesAnalysis() {
    using std::cin;
    using std::cout;
    using std::getline;
    using std::sort;
    using std::string;
    using std::vector;
    using std::ws;

    vector<BookSale> salesRecords;        

    int numBooks;
    cout << "Digite a quantidade de livros: ";
    cin >> numBooks;

    cout << "Insira os registros de vendas dos livros\n";
    for(int i = 0; i < numBooks; ++i) {
        string name, author, translator, isbn;
        double price;
        cout << string(20, '-') << " Entrada do Livro " << i+1 << string(20, '-') << '\n';
        cout << "Título: "; getline(cin >> ws, name);
        cout << "Autor: "; getline(cin >> ws, author);
        cout << "Tradutor: "; getline(cin >> ws, translator);
        cout << "ISBN: "; getline(cin >> ws, isbn);
        cout << "Preço de Capa: "; cin >> price;

        Book book(name, author, translator, isbn, price);

        double salePrice;
        int saleAmount;

        cout << "Preço de Venda: "; cin >> salePrice;
        cout << "Quantidade Vendida: "; cin >> saleAmount;

        BookSale record(book, salePrice, saleAmount);
        salesRecords.push_back(record);
    }

    // Ordenar por quantidade de vendas em ordem decrescente
    sort(salesRecords.begin(), salesRecords.end(), compareByQuantity);

    // Exibir os registros de vendas ordenados
    cout << string(20, '=') <<  " Relatório de Vendas de Livros " << string(20, '=') << '\n';
    for(auto &record : salesRecords) {
        cout << record << '\n';
        cout << string(40, '-') << '\n';
    }
}

int main() {
    runSalesAnalysis();
    return 0;
}

Perguntas e Respostas:

  1. Sobrecarga do Operador <<:

    O operador << é sobrecarregado duas vezes:

    • Uma vez para a classe Book, permitindo que objetos Book sejam enviados diretamente para um fluxo de saída (std::ostream).
    • Outra vez para a classe BookSale, que utiliza a sobrecarga do operador << da classe Book internamente para exibir os detalhes do livro, além dos detalhes específicos da venda.

    A linha cout << record << '\n'; utiliza a versão sobrecarregada do operador << para a classe BookSale.

  2. Ordenação por Quantidade de Vendas:

    A função compareByQuantity é utilizada para definir o critério de ordenação. Ela compara a quantidade de vendas (obtida através de get_amount()) de dois objetos BookSale. Se a quentidade do primeiro for maior que a do segundo, retorna true. A função std::sort, quando chamada com salesRecords.begin(), salesRecords.end() e compareByQuantity, ordena o vetor salesRecords em ordem decrescente com base na quantidade de vendas.

Experimento 3: Templates de Classe

Este experimento introduz o conceito de templates de classe em C++. Templates de classe permitem escrever código genérico que pode operar com diferentes tipos de dados sem a necessidade de reescrever o código para cada tipo.

Código Fonte:


#include <iostream>

// Definição da classe A (não template)
class A {
public:
    A(int x0, int y0);
    void display() const;

private:
    int x, y;
};

A::A(int x0, int y0): x{x0}, y{y0} {
}

void A::display() const {
    std::cout << x << ", " << y << '\n';
}

// Definição da classe B (não template)
class B {
public:
    B(double x0, double y0);
    void display() const;

private:
    double x, y;
};

B::B(double x0, double y0): x{x0}, y{y0} {
}

void B::display() const {
    std::cout << x << ", " << y << '\n';
}

void testNonTemplateClasses() {
    std::cout << "Testando Classe A: " << '\n';
    A a(3, 4);
    a.display();

    std::cout << "\nTestando Classe B: " << '\n';
    B b(3.2, 5.6);
    b.display();
}

int main() {
    testNonTemplateClasses();
    return 0;
}


#include <iostream>
#include <string>

// Definição do template de classe X
template<typename T>
class X{
public:
    X(T x0, T y0);
    void display();

private:
    T x, y;
};

// Implementação do construtor do template de classe X
template<typename T>
X<T>::X(T x0, T y0): x{x0}, y{y0} {
}

// Implementação do método display do template de classe X
template<typename T>
void X<T>::display() {
    std::cout << x << ", " << y << '\n';
}

void testClassTemplates() {
    std::cout << "Teste 1: Instanciando template de classe X com int" << '\n';
    X<int> x1(3, 4);
    x1.display();

    std::cout << "\nTeste 2: Instanciando template de classe X com double" << '\n';
    X<double> x2(3.2, 5.6);
    x2.display();

    std::cout << "\nTeste 3: Instanciando template de classe X com string" << '\n';
    X<std::string> x3("hello", "oop");
    x3.display();
}

int main() {
    testClassTemplates();
    return 0;
}

Experimento 4: Polimorfismo com Templates e Classes Abstratas

Este experimento combina templates de classe com classes abstratas para criar um sistema mais flexível. A classe base abstrata MachinePet define uma interface para diferentes tipos de "animais de estimação mecânicos", e classes derivadas como PetCat e PetDog implementam o comportamento específico. O uso de ponteiros brutos e ponteiros inteligentes é demonstrado.

Código Fonte:


#include <iostream>
#include <memory>
#include <vector>
#include "pet.hpp"

void testRawPointersPets() {
    std::vector<MachinePet *> pets;

    pets.push_back(new PetCat("miku"));
    pets.push_back(new PetDog("da huang"));

    for(MachinePet *ptr : pets) {
        std::cout << ptr->get_nickname() << " says " << ptr->talk() << '\n';
        delete ptr;  // Liberar memória manualmente
    }   
}

void testDirectUsagePets() {
    // MachinePet pet("little cutie"); // Erro de compilação: não é possível instanciar uma classe abstrata

    const PetCat cat("miku");
    std::cout << cat.get_nickname() << " says " << cat.talk() << '\n';

    const PetDog dog("da huang");
    std::cout << dog.get_nickname() << " says " << dog.talk() << '\n';
}

int main() {
    std::cout << "Teste 1: Usando ponteiros brutos para animais de estimação\n";
    testRawPointersPets();

    std::cout << "\nTeste 3: Uso direto de classes de animais de estimação\n";
    testDirectUsagePets();
    
    return 0;
}


#include <string>

// Classe base abstrata MachinePet
class MachinePet {
protected:
    std::string nickname; 

public:
    MachinePet(const std::string& name) : nickname(name) {}
    std::string get_nickname() const {
        return nickname;
    }
    // Função virtual pura para o som do animal
    virtual std::string talk() const = 0;
    // Destrutor virtual
    virtual ~MachinePet() {}
};

// Classe derivada PetCat
class PetCat : public MachinePet {
public:
    PetCat(const std::string& name) : MachinePet(name) {}
    // Implementação de talk para gato
    std::string talk() const override {
        return "meow";
    }
};

// Classe derivada PetDog
class PetDog : public MachinePet {
public:
    PetDog(const std::string& name) : MachinePet(name) {}
    // Implementação de talk para cachorro
    std::string talk() const override {
        return "wang";
    }
};

Experimento 5: Templates de Classe para Números Complexos

Este experimento final envolve a criação de um template de classe chamado Complex para representar números complexos. O template permite que os números complexos sejam instanciados com diferentes tipos de dados (como int ou double). Operadores como adição (+, +=), igualdade (==), e operadores de fluxo de entrada/saída (<<, >>) são sobrecarregados para a classe.

Código Fonte:


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

void testComplexInt() {
    using std::cout;
    using std::boolalpha;
    
    Complex<int> c1(2, -5), c2(c1); // c2 é uma cópia de c1

    cout << "c1 = " << c1 << '\n';
    cout << "c2 = " << c2 << '\n';
    cout << "c1 + c2 = " << (c1 + c2) << '\n';
    
    c1 += c2; // c1 = c1 + c2
    cout << "c1 após c1 += c2: " << c1 << '\n';
    cout << "c1 == c2? " << boolalpha << (c1 == c2) << '\n'; // Verifica se c1 é igual a c2 após a adição
}

void testComplexDouble() {
    using std::cin;
    using std::cout;

    Complex<double> c1, c2;
    cout << "Digite c1 (real e imaginário): ";
    cin >> c1; // Usa o operador >> sobrecarregado
    cout << "Digite c2 (real e imaginário): ";
    cin >> c2; // Usa o operador >> sobrecarregado
    
    cout << "c1 = " << c1 << '\n'; // Usa o operador << sobrecarregado
    cout << "c2 = " << c2 << '\n'; // Usa o operador << sobrecarregado

    const Complex<double> c3(c1); // Cria uma cópia de c1
    cout << "Parte real de c3: " << c3.get_real() << '\n';
    cout << "Parte imaginária de c3: " << c3.get_imag() << '\n';
}

int main() {
    std::cout << "Teste 1: Template Complex com int\n";
    testComplexInt();

    std::cout << "\nTeste 2: Template Complex com double\n";
    testComplexDouble();
    
    return 0;
}


#include <iostream>
#include <cmath> // Para std::abs

template<typename T>
class Complex {
private:
    T real; // Parte real
    T imag; // Parte imaginária
    
public:
   
    // Construtores
    Complex(T r = 0, T i = 0); // Construtor padrão com valores iniciais
    Complex(const Complex& other); // Construtor de cópia

    // Getters e Setters
    T get_real() const;
    T get_imag() const;
    void set_real(T r);
    void set_imag(T i);
    
    // Operadores sobrecarregados
    Complex& operator+=(const Complex& other); // Adição e atribuição
    Complex operator+(const Complex& other) const; // Adição
    bool operator==(const Complex& other) const; // Igualdade
    
    // Funções amigas para I/O
    friend std::ostream& operator<<(std::ostream& out, const Complex& c);
    friend std::istream& operator>>(std::istream& in, Complex& c);
};

// Implementação do construtor padrão
template<typename T>
Complex<T>::Complex(T r, T i) : real(r), imag(i) {}

// Implementação do construtor de cópia
template<typename T>
Complex<T>::Complex(const Complex& other) : real(other.real), imag(other.imag) {}

// Implementação dos getters
template<typename T>
T Complex<T>::get_real() const {
    return real;
}

template<typename T>
T Complex<T>::get_imag() const {
    return imag;
}

// Implementação dos setters
template<typename T>
void Complex<T>::set_real(T r) {
    real = r;
}

template<typename T>
void Complex<T>::set_imag(T i) {
    imag = i;
}

// Implementação do operador +=
template<typename T>
Complex<T>& Complex<T>::operator+=(const Complex& other) {
    real += other.real;
    imag += other.imag;
    return *this; // Retorna uma referência para o objeto modificado
}

// Implementação do operador +
template<typename T>
Complex<T> Complex<T>::operator+(const Complex& other) const {
    // Retorna um novo objeto Complex resultante da soma
    return Complex<T>(real + other.real, imag + other.imag);
}

// Implementação do operador ==
template<typename T>
bool Complex<T>::operator==(const Complex& other) const {
    // Compara as partes real e imaginária
    return (real == other.real) && (imag == other.imag);
}

// Implementação da função amiga operator<<
template<typename T>
std::ostream& operator<<(std::ostream& out, const Complex<T>& c) {
    out << c.real; // Exibe a parte real
    if (c.imag >= 0) {
        out << " + " << c.imag << "i"; // Exibe com '+' se imaginário for positivo ou zero
    } else {
        // Exibe com '-' e o valor absoluto se imaginário for negativo
        out << " - " << std::abs(c.imag) << "i"; 
    }
    return out;
}
    
// Implementação da função amiga operator>>
template<typename T>
std::istream& operator>>(std::istream& in, Complex<T>& c) {
    // Lê a parte real e a parte imaginária do fluxo de entrada
    in >> c.real >> c.imag;
    return in;
}


### Observações sobre Templates e Funções Amigas:

Ao lidar com templates de classe e funções amigas (como `operator<<` e `operator>>`), é mais direto e menos propenso a erros declará-las e defini-las dentro da própria classe template. Isso garante que o compilador associe corretamente a função amiga ao template específico quando ele é instanciado.

Tags: C++ Programação Orientada a Objetos Polimorfismo Classes Abstratas Herança

Publicado em 7-24 22:11