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:
-
Motivo para
Publisherser uma classe abstrata:Publisheré uma classe abstrata porque contém funções virtuais puras (virtual void publish() const = 0;evirtual 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 classePublisher. Seu propósito é definir uma interface comum para as classes derivadas. -
Necessidade de implementar funções virtuais puras:
As classes derivadas (
Book,Film,Music) devem implementar as funções virtuais puraspublish()euse()herdadas dePublisherpara se tornarem classes concretas e poderem ser instanciadas. A assinatura da função deve corresponder exatamente à da classe base, incluindo o especificadoroverrideeconst.void publish() const override;void use() const override;A remoção do
constalteraria a assinatura da função, impedindo a sobreposição correta da função virtual pura e resultando em um erro de compilação. -
Ponteiros e Polimorfismo:
No loop
for(Publisher *p : publications), o tipo do ponteiropéPublisher*. Este ponteiro pode apontar para objetos de qualquer classe derivada dePublisher.- Na primeira iteração,
paponta para um objetoBook. - Na segunda iteração,
paponta para um objetoFilm. - Na terceira iteração,
paponta para um objetoMusic.
A declaração do destrutor em
Publishercomovirtual ~Publisher() = default;é crucial. Ela garante que, ao usardelete 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 dePublisherseria chamado, levando a vazamentos de memória se as classes derivadas tivessem recursos próprios para gerenciar. - Na primeira iteração,
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:
-
Sobrecarga do Operador
<<:O operador
<<é sobrecarregado duas vezes:- Uma vez para a classe
Book, permitindo que objetosBooksejam enviados diretamente para um fluxo de saída (std::ostream). - Outra vez para a classe
BookSale, que utiliza a sobrecarga do operador<<da classeBookinternamente 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 classeBookSale. - Uma vez para a classe
-
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 deget_amount()) de dois objetosBookSale. Se a quentidade do primeiro for maior que a do segundo, retornatrue. A funçãostd::sort, quando chamada comsalesRecords.begin(),salesRecords.end()ecompareByQuantity, ordena o vetorsalesRecordsem 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.