Gestión de Contenidos Publicables
Este segmento explora la aplicación de la herencia y el polimorfismo en C++ para gestionar diferentes tipos de medios publicables. Se demuestra cómo las clases abstractas y las funciones virtuales puras permiten crear una interfaz común para entidades diversas, como libros, películas y música, facilitando el manejo polimórfico mediante punteros.
Definiciones de Clases
A continuación, se presentan las declaraciones de las clases base y derivadas que definen la jerarquía de contenidos publicables.
#pragma once
#include <string>
#include <memory> // Para std::unique_ptr
// Clase base abstracta para cualquier tipo de medio publicable
class MedioPublicable {
public:
explicit MedioPublicable(const std::string &identificador_ = "");
virtual ~MedioPublicable() = default; // Destructor virtual para manejo polimórfico
virtual void difundir() const = 0; // Función virtual pura: publicar
virtual void consumir() const = 0; // Función virtual pura: usar/consumir
protected:
std::string identificador; // Nombre o título del medio
};
// Clase para representar un Libro
class Libro : public MedioPublicable {
public:
Libro(const std::string &titulo_, const std::string &autor_ = "");
void difundir() const override; // Implementación de difundir para Libro
void consumir() const override; // Implementación de consumir para Libro
private:
std::string autor;
};
// Clase para representar una Película
class Pelicula : public MedioPublicable {
public:
Pelicula(const std::string &titulo_, const std::string &director_ = "");
void difundir() const override; // Implementación de difundir para Pelicula
void consumir() const override; // Implementación de consumir para Pelicula
private:
std::string director;
};
// Clase para representar una Grabación Musical
class GrabacionMusical : public MedioPublicable {
public:
GrabacionMusical(const std::string &titulo_, const std::string &artista_ = "");
void difundir() const override; // Implementación de difundir para GrabacionMusical
void consumir() const override; // Implementación de consumir para GrabacionMusical
private:
std::string artista;
};
Implementación de Métodos
Las implementaciones detalladas de los constructores y las funciones virtuales para cada clase.
#include <iostream>
#include "MedioPublicable.hpp"
// Implementación de la clase base
MedioPublicable::MedioPublicable(const std::string &identificador_) : identificador(identificador_) {}
// Implementación de la clase Libro
Libro::Libro(const std::string &titulo_, const std::string &autor_) : MedioPublicable(titulo_), autor(autor_) {}
void Libro::difundir() const {
std::cout << "Publicando libro \«" << identificador << "\» por " << autor << '\n';
}
void Libro::consumir() const {
std::cout << "Leyendo libro \«" << identificador << "\» de " << autor << '\n';
}
// Implementación de la clase Pelicula
Pelicula::Pelicula(const std::string &titulo_, const std::string &director_) : MedioPublicable(titulo_), director(director_) {}
void Pelicula::difundir() const {
std::cout << "Estrenando película <" << identificador << "> dirigida por " << director << '\n';
}
void Pelicula::consumir() const {
std::cout << "Viendo película <" << identificador << "> de " << director << '\n';
}
// Implementación de la clase GrabacionMusical
GrabacionMusical::GrabacionMusical(const std::string &titulo_, const std::string &artista_) : MedioPublicable(titulo_), artista(artista_) {}
void GrabacionMusical::difundir() const {
std::cout << "Lanzando canción <" << identificador << "> interpretada por " << artista << '\n';
}
void GrabacionMusical::consumir() const {
std::cout << "Escuchando canción <" << identificador << "> de " << artista << '\n';
}
Ejemplo de Uso y Polimorfismo
Este archivo principal demuestra cómo se pueden manejar diferentes tipos de medios de forma polimórfica, tanto con punteros crudos como con punteros inteligentes.
#include <memory>
#include <iostream>
#include <vector>
#include "MedioPublicable.hpp"
// Función de prueba utilizando punteros crudos
void ejecutarPruebaPunterosCrudos() {
std::vector<MedioPublicable*> medios;
medios.push_back(new Libro("Cien años de soledad", "Gabriel García Márquez"));
medios.push_back(new Pelicula("Parasite", "Bong Joon-ho"));
medios.push_back(new GrabacionMusical("Bohemian Rhapsody", "Queen"));
for (MedioPublicable* pMedio : medios) {
pMedio->difundir();
pMedio->consumir();
std::cout << '\n';
delete pMedio; // Esencial liberar la memoria
}
}
// Función de prueba utilizando punteros inteligentes (std::unique_ptr)
void ejecutarPruebaPunterosInteligentes() {
std::vector<std::unique_ptr<MedioPublicable>> medios;
medios.push_back(std::make_unique<Libro>("Don Quijote de la Mancha", "Miguel de Cervantes"));
medios.push_back(std::make_unique<Pelicula>("El Padrino", "Francis Ford Coppola"));
medios.push_back(std::make_unique<GrabacionMusical>("Imagine", "John Lennon"));
for (const auto& pMedio : medios) {
pMedio->difundir();
pMedio->consumir();
std::cout << '\n';
}
}
// Función de prueba con instancias directas
void ejecutarPruebaInstanciasDirectas() {
Libro miLibro("Clean Code", "Robert C. Martin");
miLibro.difundir();
miLibro.consumir();
}
int main() {
std::cout << "Demostración de Polimorfismo en Tiempo de Ejecución con C++\n";
std::cout << "\n=== Prueba 1: Usando Punteros Crudos ===\n";
ejecutarPruebaPunterosCrudos();
std::cout << "\n=== Prueba 2: Usando Punteros Inteligentes (std::unique_ptr) ===\n";
ejecutarPruebaPunterosInteligentes();
std::cout << "\n=== Prueba 3: Uso Directo de Instancias ===\n";
ejecutarPruebaInstanciasDirectas();
}
Análisis de Conceptos Clave
Pregunta 1: Mecanismo de Clase Abstracta
- ¿Qué elementos en la clase
MedioPublicablela definen como abstracta?
La presencia de al menos una función virtual pura (= 0) en su declaración la convierte en una clase abstracta. Específicamente, las funcionesvirtual void difundir() const = 0;yvirtual void consumir() const = 0;son las responsables. - ¿Sería posible compilar directamente
MedioPublicable miObjeto;enmain?
No, la compilación fallaría. Las clases abstractas no pueden ser instanciadas directamente debido a sus funciones virtuales puras no implementadas.
Pregunta 2: Funciones Virtuales Puras e Herencia de Interfaz
- ¿Cuáles son las dos funciones que
Libro,PeliculayGrabacionMusicaldeben implementar obligatoriamente?
Deben implementarvoid difundir() const override;yvoid consumir() const override;. - ¿Qué sucede si se omite la palabra clave
consten la implementación de una función virtual en una clase derivada, por ejemplo,void Libro::difundir()en lugar devoid Libro::difundir() const?
Se produciría un error de compilación. La firma de la función en la clase derivada debe coincidir exactamente con la de la función virtual pura en la clase base, incluyendo calificadores comoconst. Si no coincide, el compilador interpretará que la clase derivada no ha proporcionado una implementación para la función virtual pura, haciendo que la clase derivada también sea abstracta y, por tanto, no instanciable directamente (a menos que también se declare como abstracta).
Pregunta 3: Polimorfismo en Tiempo de Ejecución y Destructores Virtuales
- En la función
ejecutarPruebaPunterosCrudos(), ¿cuál es el tipo declarado del punteropMedio?
El tipo declarado esMedioPublicable*. - ¿Por qué el destructor de
MedioPublicableestá declarado comovirtual?
Se declara comovirtualpara asegurar que, cuando se elimina un objeto derivado a través de un puntero a la clase base (como endelete pMedio;), se llame correctamente al destructor de la clase derivada. Si el destructor no fuera virtual, solo se llamaría al destructor de la clase base, lo que podría llevar a fugas de memoria o comportamiento indefinido para los recursos específicos de la clase derivada.
Estadísticas de Ventas de Obras Literarias
Esta sección ilustra el uso de la composición de clases y la sobrecarga de operadores para gestionar y presentar registros de ventas de libros. Además, se demuestra cómo ordenar colecciones de objetos utilizando funciones de comparación personalizadas.
Definiciones de Clases para Libros y Ventas
Se definen dos clases: ObraLiteraria para los detalles del libro y RegistroVentaLibro para la inofrmación de venta, que compone una ObraLiteraria.
#pragma once
#include <string>
#include <iostream>
// Clase que representa los detalles de una obra literaria
class ObraLiteraria {
public:
ObraLiteraria(const std::string &titulo_,
const std::string &autor_,
const std::string &traductor_,
const std::string &identificadorISBN_,
double costoBase_);
// Sobrecarga del operador de inserción para imprimir detalles de la obra
friend std::ostream& operator<<(std::ostream &salida, const ObraLiteraria &obra);
private:
std::string titulo; // Título del libro
std::string autor; // Autor
std::string traductor; // Traductor (puede estar vacío)
std::string identificadorISBN; // Número ISBN
double costoBase; // Precio de catálogo
};
#pragma once
#include <string>
#include <iostream>
#include "ObraLiteraria.hpp" // Incluimos la clase ObraLiteraria
// Clase que representa un registro de venta para una obra literaria
class RegistroVentaLibro {
public:
RegistroVentaLibro(const ObraLiteraria &obraAsociada_, double precioDeVenta_, int unidadesVendidas_);
int obtenerCantidadVendida() const; // Retorna la cantidad de unidades vendidas
double calcularIngresosTotales() const; // Retorna los ingresos generados por esta venta
// Sobrecarga del operador de inserción para imprimir detalles del registro de venta
friend std::ostream& operator<<(std::ostream &salida, const RegistroVentaLibro &item);
private:
ObraLiteraria obraAsociada; // Composición: el libro que se vendió
double precioDeVenta; // Precio por unidad vendida
int unidadesVendidas; // Cantidad de unidades en esta venta
};
Implementaciones de Clases
Implementación de los constructores y la sobrecarga del operador de inserción para ambas clases.
#include <iomanip>
#include <iostream>
#include <string>
#include "ObraLiteraria.hpp"
ObraLiteraria::ObraLiteraria(const std::string &titulo_,
const std::string &autor_,
const std::string &traductor_,
const std::string &identificadorISBN_,
double costoBase_)
: titulo{titulo_}, autor{autor_}, traductor{traductor_}, identificadorISBN{identificadorISBN_}, costoBase{costoBase_} {}
std::ostream& operator<<(std::ostream &salida, const ObraLiteraria &obra) {
using std::left;
using std::setw;
salida << left;
salida << setw(18) << "Título:" << obra.titulo << '\n'
<< setw(18) << "Autor:" << obra.autor << '\n'
<< setw(18) << "Traductor:" << obra.traductor << '\n'
<< setw(18) << "ISBN:" << obra.identificadorISBN << '\n'
<< setw(18) << "Precio Base:" << std::fixed << std::setprecision(2) << obra.costoBase;
return salida;
}
#include <iomanip>
#include <iostream>
#include <string>
#include "RegistroVentaLibro.hpp"
RegistroVentaLibro::RegistroVentaLibro(const ObraLiteraria &obraAsociada_,
double precioDeVenta_,
int unidadesVendidas_)
: obraAsociada{obraAsociada_}, precioDeVenta{precioDeVenta_}, unidadesVendidas{unidadesVendidas_} {}
int RegistroVentaLibro::obtenerCantidadVendida() const {
return unidadesVendidas;
}
double RegistroVentaLibro::calcularIngresosTotales() const {
return unidadesVendidas * precioDeVenta;
}
std::ostream& operator<<(std::ostream &salida, const RegistroVentaLibro &item) {
using std::left;
using std::setw;
salida << left;
salida << item.obraAsociada << '\n'
<< setw(18) << "Precio de Venta:" << std::fixed << std::setprecision(2) << item.precioDeVenta << '\n'
<< setw(18) << "Unidades Vendidas:" << item.unidadesVendidas << '\n'
<< setw(18) << "Ingresos Generados:" << std::fixed << std::setprecision(2) << item.calcularIngresosTotales();
return salida;
}
Programa Principal para Gestión de Ventas
Este programa interactúa con el usuario para recopilar datos de ventas y luego los ordena e imprime.
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "RegistroVentaLibro.hpp"
// Función de comparación para ordenar por cantidad vendida (descendente)
bool compararPorCantidadVendida(const RegistroVentaLibro &r1, const RegistroVentaLibro &r2) {
return r1.obtenerCantidadVendida() > r2.obtenerCantidadVendida();
}
void procesarVentas() {
using std::cin;
using std::cout;
using std::getline;
using std::sort;
using std::string;
using std::vector;
using std::ws; // Para limpiar el buffer de entrada
vector<RegistroVentaLibro> registrosDeVenta;
int numeroDeObras;
cout << "Ingrese el número de obras literarias a registrar: ";
cin >> numeroDeObras;
cout << "\nComenzando el registro de ventas de libros...\n";
for(int i = 0; i < numeroDeObras; ++i) {
string titulo, autor, traductor, isbn;
double precioLista;
cout << string(25, '=') << " Detalles de la Obra " << (i+1) << string(25, '=') << '\n';
cout << "Título: "; getline(cin>>ws, titulo);
cout << "Autor: "; getline(cin>>ws, autor);
cout << "Traductor (si aplica): "; getline(cin>>ws, traductor);
cout << "ISBN: "; getline(cin>>ws, isbn);
cout << "Precio de Lista: "; cin >> precioLista;
ObraLiteraria obra(titulo, autor, traductor, isbn, precioLista);
double precioVentaUnitario;
int cantidadTotalVendida;
cout << "Precio de venta unitario: "; cin >> precioVentaUnitario;
cout << "Cantidad vendida: "; cin >> cantidadTotalVendida;
RegistroVentaLibro nuevoRegistro(obra, precioVentaUnitario, cantidadTotalVendida);
registrosDeVenta.push_back(nuevoRegistro);
}
// Ordenar los registros por la cantidad vendida en orden descendente
sort(registrosDeVenta.begin(), registrosDeVenta.end(), compararPorCantidadVendida);
cout << string(20, '=') << " Resumen de Ventas de Obras Literarias " << string(20, '=') << '\n';
for(auto ®istro : registrosDeVenta) {
cout << registro << '\n';
cout << string(60, '-') << '\n';
}
}
int main() {
procesarVentas();
}
Análisis de Características Implementadas
Pregunta 1: Sobrecarga del Operador <<
-
¿Cuántas veces se ha sobrecargado el operador
<<en este conjunto de archivos, y cuáles son sus firmas?
El operador<<se ha sobrecargado dos veces:std::ostream& operator<<(std::ostream&, const ObraLiteraria&)std::ostream& operator<<(std::ostream&, const RegistroVentaLibro&)
-
Mencione una línea de código del
mainque haga uso de la sobrecarga del operador<<para imprimir un objeto.
cout << registro << '\n';(donderegistroes un objeto de tipoRegistroVentaLibro).
Pregunta 2: Estadísticas de Ventas de Libros
- Describa cómo se logra el ordenamiento descendente de los registros de venta.
Se utiliza la funciónstd::sortde la biblioteca estándar, proporcionándole los iteradores al inicio y fin del vectorregistrosDeVenta, y una función de comparación personalizadacompararPorCantidadVendida. Esta función de comparación compara los valores retornados porobtenerCantidadVendida()de dos objetosRegistroVentaLibrousando el operador>, lo que resulta en un orden descendente. - Proporcione un ejemplo de cómo se podría realizar el mismo ordenamiento usando una función lambda en lugar de una función regular.
std::sort(registrosDeVenta.begin(), registrosDeVenta.end(), [](const RegistroVentaLibro& rA, const RegistroVentaLibro& rB) { return rA.obtenerCantidadVendida() > rB.obtenerCantidadVendida(); });
Simulación de Mascotas Robóticas
Este ejercicio es una demostración concisa de polimorfismo y herencia con clases abstractas, similar al primer ejercicio pero con un dominio más simple: mascotas robóticas que pueden emitir sonidos característicos.
Clases de Mascotas Robóticas
Declaración de la clase base abstracta MascotaRobotica y sus especializaciones GatoRobot y PerroRobot.
#pragma once
#include <string>
#include <memory> // Para std::unique_ptr
// Clase base abstracta para mascotas robóticas
class MascotaRobotica {
public:
explicit MascotaRobotica(const std::string& apodo_);
virtual ~MascotaRobotica() = default; // Destructor virtual
const std::string& obtenerApodo() const { return apodo; } // Getter para el apodo
virtual std::string emitirSonido() const = 0; // Función virtual pura: el sonido de la mascota
private:
std::string apodo; // Nombre o apodo de la mascota
};
// Constructor inline
inline MascotaRobotica::MascotaRobotica(const std::string& apodo_) : apodo(apodo_) {}
// Clase para un Gato Robótico
class GatoRobot : public MascotaRobotica {
public:
// Hereda los constructores de la clase base
using MascotaRobotica::MascotaRobotica;
std::string emitirSonido() const override { return "miau guau"; } // Sonido de gato robot
};
// Clase para un Perro Robótico
class PerroRobot : public MascotaRobotica {
public:
// Hereda los constructores de la clase base
using MascotaRobotica::MascotaRobotica;
std::string emitirSonido() const override { return "guau guau"; } // Sonido de perro robot
};
Programa de Prueba para Mascotas Robóticas
Este archivo principal contiene funciones de prueba para demostrar el manejo de las mascotas robóticas utilizando punteros crudos, punteros inteligentes e instancias directas.
#include <iostream>
#include <memory>
#include <vector>
#include "MascotaRobotica.hpp"
// Prueba con punteros crudos
void pruebaConPunterosCrudos() {
std::vector<MascotaRobotica*> mascotas;
mascotas.push_back(new GatoRobot("Miko"));
mascotas.push_back(new PerroRobot("Max"));
for(MascotaRobotica *pMascota : mascotas) {
std::cout << pMascota->obtenerApodo() << " dice: " << pMascota->emitirSonido() << '\n';
delete pMascota; // Liberar memoria asignada
}
}
// Prueba con punteros inteligentes (std::unique_ptr)
void pruebaConPunterosInteligentes() {
std::vector<std::unique_ptr<MascotaRobotica>> mascotas;
mascotas.push_back(std::make_unique<GatoRobot>("Garfield"));
mascotas.push_back(std::make_unique<PerroRobot>("Pluto"));
for(auto const &pMascota : mascotas)
std::cout << pMascota->obtenerApodo() << " dice: " << pMascota->emitirSonido() << '\n';
}
// Prueba con instancias directas de las clases
void pruebaConInstanciasDirectas() {
const GatoRobot miGato("Luna");
std::cout << miGato.obtenerApodo() << " dice: " << miGato.emitirSonido() << '\n';
const PerroRobot miPerro("Buddy");
std::cout << miPerro.obtenerApodo() << " dice: " << miPerro.emitirSonido() << '\n';
}
int main() {
std::cout << "=== Pruebas de Mascotas Robóticas ===\n";
std::cout << "\n-- Prueba 1: Usando punteros crudos --\n";
pruebaConPunterosCrudos();
std::cout << "\n-- Prueba 2: Usando punteros inteligentes --\n";
pruebaConPunterosInteligentes();
std::cout << "\n-- Prueba 3: Usando instancias directas --\n";
pruebaConInstanciasDirectas();
}
Implementación de Plantilla de Clase para Números Complejos
Este apartado presenta la creación de una plantilla de clase NumeroComplejo que permite trabajar con números complejos de difreentes tipos numéricos (int, double, etc.). Demuestra la sobrecarga de operadores para aritmética (suma, asignación con suma), comparación y operaciones de entrada/salida.
Plantilla de Clase NumeroComplejo
Definición de la plantilla de clase con constructores, métodos de acceso y sobrecarga de operadores.
#pragma once
#include <iostream> // Para std::ostream y std::istream
// Plantilla de clase para representar un número complejo
template <typename T>
class NumeroComplejo {
public:
// Constructores
NumeroComplejo() = default; // Constructor por defecto
NumeroComplejo(T r, T i) : parteReal(r), parteImaginaria(i) {} // Constructor con partes real e imaginaria
NumeroComplejo(const NumeroComplejo& otro) : parteReal(otro.parteReal), parteImaginaria(otro.parteImaginaria) {} // Constructor de copia
// Métodos de acceso (getters)
T obtenerParteReal() const { return parteReal; }
T obtenerParteImaginaria() const { return parteImaginaria; }
// Sobrecarga del operador de asignación con suma (+=)
NumeroComplejo& operator+=(const NumeroComplejo& rhs) {
parteReal += rhs.parteReal;
parteImaginaria += rhs.parteImaginaria;
return *this;
}
// Sobrecarga del operador de suma binaria (+)
// Implementado como función friend para permitir conmutatividad
friend NumeroComplejo operator+(NumeroComplejo lhs, const NumeroComplejo& rhs) {
lhs += rhs; // Reutiliza el operador +=
return lhs;
}
// Sobrecarga del operador de igualdad (==)
friend bool operator==(const NumeroComplejo& a, const NumeroComplejo& b) {
return a.parteReal == b.parteReal && a.parteImaginaria == b.parteImaginaria;
}
// Sobrecarga del operador de inserción (salida <<)
friend std::ostream& operator<<(std::ostream& os, const NumeroComplejo& c) {
os << c.parteReal;
if (c.parteImaginaria >= 0) os << '+';
os << c.parteImaginaria << 'i';
return os;
}
// Sobrecarga del operador de extracción (entrada >>)
// Permite leer las partes real e imaginaria directamente
friend std::istream& operator>>(std::istream& is, NumeroComplejo& c) {
// Asume que la entrada será en formato "real imaginaria" (ej. 3.5 2.1)
return is >> c.parteReal >> c.parteImaginaria;
}
private:
T parteReal{}; // Parte real del número complejo
T parteImaginaria{}; // Parte imaginaria del número complejo
};
Programa Principal de Pruebas para NumeroComplejo
Este programa demuestra la funcionalidad de la plantilla de clase NumeroComplejo con diferentes tipos de datos y operaciones.
#include <iostream>
#include "NumeroComplejo.hpp" // Incluye la plantilla de clase
// Función para realizar la primera serie de pruebas con números complejos enteros
void realizarPruebaEnteros() {
using std::cout;
using std::boolalpha; // Para imprimir booleanos como "true" o "false"
NumeroComplejo<int> z1(5, -3); // Un número complejo (5 - 3i)
NumeroComplejo<int> z2 = z1; // Constructor de copia
cout << "z1 = " << z1 << '\n';
cout << "z2 = " << z2 << '\n';
cout << "z1 + z2 = " << z1 + z2 << '\n';
z1 += z2; // z1 ahora es z1 + z2
cout << "Después de z1 += z2, z1 = " << z1 << '\n';
cout << "¿z1 es igual a z2? " << boolalpha << (z1 == z2) << '\n';
}
// Función para realizar la segunda serie de pruebas con números complejos de doble precisión
void realizarPruebaDoubles() {
using std::cin;
using std::cout;
NumeroComplejo<double> z3, z4;
cout << "Introduzca dos números complejos (formato 'real imaginario'):\n";
cout << "Introduzca z3: ";
cin >> z3;
cout << "Introduzca z4: ";
cin >> z4;
cout << "z3 = " << z3 << '\n';
cout << "z4 = " << z4 << '\n';
const NumeroComplejo<double> z5(z3); // Constructor de copia
cout << "z5 (copia de z3). Parte real: " << z5.obtenerParteReal() << '\n';
cout << "z5 (copia de z3). Parte imaginaria: " << z5.obtenerParteImaginaria() << '\n';
}
int main() {
std::cout << "=== Pruebas de la Plantilla de Clase NumeroComplejo ===\n";
std::cout << "\n--- Prueba 1: Números Complejos Enteros ---\n";
realizarPruebaEnteros();
std::cout << "\n--- Prueba 2: Números Complejos de Doble Precisión ---\n";
realizarPruebaDoubles();
}