Ereditarietà e Polimorfismo
Classi base e derivate, funzioni virtuali, classi astratte e interfacce.
Ereditarietà Base
Con l'ereditarietà una classe derivata acquisisce attributi e metodi della classe base, potendo aggiungerne di nuovi o modificarne il comportamento.
#include <iostream>
#include <string>
using namespace std;
// Classe BASE
class Animale {
protected: // Accessibile alle classi derivate
string nome;
int eta;
public:
Animale(const string &n, int e) : nome(n), eta(e) {}
void respira() const { cout << nome << " sta respirando.\n"; }
// virtual: permette l'override nelle classi derivate
virtual void verso() const { cout << nome << " fa un verso generico.\n"; }
virtual void info() const {
cout << "Animale: " << nome << ", età " << eta << "\n";
}
// Distruttore SEMPRE virtuale in classi base con funzioni virtuali!
virtual ~Animale() {}
string getNome() const { return nome; }
};
// Classe DERIVATA
class Cane : public Animale {
string razza;
public:
Cane(const string &n, int e, const string &r)
: Animale(n, e), razza(r) {}
// Override di verso()
void verso() const override {
cout << nome << " (Cane) fa: Bau Bau!\n";
}
// Override di info() con chiamata al metodo base
void info() const override {
Animale::info(); // Chiama il metodo della classe base
cout << " Razza: " << razza << "\n";
}
void riporta() const { cout << nome << " riporta la pallina!\n"; }
};
class Gatto : public Animale {
public:
Gatto(const string &n, int e) : Animale(n, e) {}
void verso() const override { cout << nome << " (Gatto) fa: Miao!\n"; }
void graffia() const { cout << nome << " graffia!\n"; }
};
int main() {
Cane fido("Fido", 3, "Labrador");
Gatto luna("Luna", 2);
Animale generico("Bestia", 1);
fido.info();
fido.verso();
fido.respira(); // Ereditato da Animale
fido.riporta(); // Solo in Cane
cout << "\n";
luna.verso();
luna.graffia();
return 0;
}
Polimorfismo con Puntatori
Il vero potere del polimorfismo si vede quando usi puntatori alla classe base che puntano a oggetti di classi derivate.
#include <iostream>
#include <vector>
#include <memory> // smart pointer: unique_ptr
using namespace std;
class Forma {
public:
virtual double area() const = 0; // = 0 → funzione PURA (astratta)
virtual double perimetro() const = 0;
virtual string nome() const = 0;
void stampa() const {
cout << nome() << ": area=" << area()
<< ", perim=" << perimetro() << "\n";
}
virtual ~Forma() {}
};
// Cerchio
class Cerchio : public Forma {
double r;
public:
Cerchio(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
double perimetro() const override { return 2 * 3.14159 * r; }
string nome() const override { return "Cerchio(r=" + to_string(r) + ")"; }
};
// Rettangolo
class Rettangolo : public Forma {
double l, h;
public:
Rettangolo(double l, double h) : l(l), h(h) {}
double area() const override { return l * h; }
double perimetro() const override { return 2*(l+h); }
string nome() const override { return "Rettangolo(" + to_string(l) + "x" + to_string(h) + ")"; }
};
// Triangolo (area con formula di Erone)
class Triangolo : public Forma {
double a, b, c;
public:
Triangolo(double a, double b, double c) : a(a), b(b), c(c) {}
double perimetro() const override { return a+b+c; }
double area() const override {
double s = perimetro() / 2;
return sqrt(s*(s-a)*(s-b)*(s-c));
}
string nome() const override { return "Triangolo(" + to_string(a) + "," + to_string(b) + "," + to_string(c) + ")"; }
};
int main() {
// Vettore di smart pointer a Forma → polimorfismo!
vector<unique_ptr<Forma>> forme;
forme.push_back(make_unique<Cerchio>(5.0));
forme.push_back(make_unique<Rettangolo>(4.0, 6.0));
forme.push_back(make_unique<Triangolo>(3.0, 4.0, 5.0));
forme.push_back(make_unique<Cerchio>(2.5));
double area_totale = 0;
for (const auto &f : forme) {
f->stampa(); // Chiamata polimorfica!
area_totale += f->area();
}
cout << "\nArea totale di tutte le forme: " << area_totale << "\n";
// dynamic_cast per verificare il tipo a runtime
for (const auto &f : forme) {
if (auto *c = dynamic_cast<Cerchio*>(f.get())) {
cout << "Trovato un cerchio!\n";
}
}
return 0;
}
= 0) è astratta: non può essere istanziata direttamente. Serve come interfaccia (contratto) che le classi derivate devono rispettare.
Interfacce e Ereditarietà Multipla
#include <iostream>
#include <string>
using namespace std;
// "Interfacce" in C++ — classi con solo virtual pure
class Stampabile {
public:
virtual void stampa() const = 0;
virtual ~Stampabile() {}
};
class Serializzabile {
public:
virtual string toJSON() const = 0;
virtual ~Serializzabile() {}
};
// Ereditarietà multipla
class Prodotto : public Stampabile, public Serializzabile {
string nome;
double prezzo;
int quantita;
public:
Prodotto(const string &n, double p, int q)
: nome(n), prezzo(p), quantita(q) {}
void stampa() const override {
cout << "[" << nome << "] EUR " << prezzo << " x" << quantita << "\n";
}
string toJSON() const override {
return "{\"nome\":\"" + nome + "\","
"\"prezzo\":" + to_string(prezzo) + ","
"\"quantita\":" + to_string(quantita) + "}";
}
double valore() const { return prezzo * quantita; }
};
int main() {
Prodotto laptop("Laptop", 899.99, 3);
laptop.stampa();
cout << laptop.toJSON() << "\n";
cout << "Valore totale: " << laptop.valore() << "\n";
return 0;
}
Crea la gerarchia: Veicolo (base: targa, velocità massima) → Auto (num porte) → Moto (tipo: sportiva/touring) → Camion (portata in tonnellate). Ogni classe sovrascrive descrizione(). Usa un vettore di puntatori a Veicolo.
#include <iostream>
#include <vector>
#include <memory>
#include <string>
using namespace std;
class Veicolo {
protected:
string targa;
int vmax;
public:
Veicolo(const string &t, int v) : targa(t), vmax(v) {}
virtual void descrizione() const {
cout << "Veicolo " << targa << " (max " << vmax << " km/h)";
}
virtual ~Veicolo() {}
};
class Auto : public Veicolo {
int porte;
public:
Auto(const string &t, int v, int p) : Veicolo(t,v), porte(p) {}
void descrizione() const override {
Veicolo::descrizione();
cout << " | Auto " << porte << " porte\n";
}
};
class Moto : public Veicolo {
string tipo;
public:
Moto(const string &t, int v, const string &tipo) : Veicolo(t,v), tipo(tipo) {}
void descrizione() const override {
Veicolo::descrizione();
cout << " | Moto " << tipo << "\n";
}
};
class Camion : public Veicolo {
double portata;
public:
Camion(const string &t, int v, double p) : Veicolo(t,v), portata(p) {}
void descrizione() const override {
Veicolo::descrizione();
cout << " | Camion " << portata << "t\n";
}
};
int main() {
vector<unique_ptr<Veicolo>> parco;
parco.push_back(make_unique<Auto>("AB123CD", 180, 5));
parco.push_back(make_unique<Moto>("XZ789AB", 220, "sportiva"));
parco.push_back(make_unique<Camion>("CA001ZZ", 120, 18.5));
for (const auto &v : parco) v->descrizione();
}Crea un'interfaccia astratta Notificatore con metodo puro invia(messaggio). Implementa: EmailNotificatore, SMSNotificatore, PushNotificatore. Ogni classe simula l'invio stampando un messaggio diverso. Crea una funzione notifica_tutti() che prenda un vettore di puntatori a Notificatore.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Notificatore {
public:
virtual void invia(const string &msg) const = 0;
virtual string tipo() const = 0;
virtual ~Notificatore() {}
};
class EmailNotificatore : public Notificatore {
string email;
public:
EmailNotificatore(const string &e) : email(e) {}
void invia(const string &msg) const override {
cout << "[EMAIL → " << email << "] " << msg << "\n";
}
string tipo() const override { return "Email"; }
};
class SMSNotificatore : public Notificatore {
string numero;
public:
SMSNotificatore(const string &n) : numero(n) {}
void invia(const string &msg) const override {
cout << "[SMS → " << numero << "] " << msg.substr(0, 160) << "\n";
}
string tipo() const override { return "SMS"; }
};
class PushNotificatore : public Notificatore {
string device;
public:
PushNotificatore(const string &d) : device(d) {}
void invia(const string &msg) const override {
cout << "[PUSH → " << device << "] 🔔 " << msg << "\n";
}
string tipo() const override { return "Push"; }
};
void notifica_tutti(const vector<Notificatore*> &lista, const string &msg) {
cout << "\nInvio a " << lista.size() << " destinatari:\n";
for (const auto *n : lista) n->invia(msg);
}
int main() {
EmailNotificatore email("mario@example.com");
SMSNotificatore sms("+39 333 1234567");
PushNotificatore push("iPhone-Mario");
vector<Notificatore*> lista = {&email, &sms, &push};
notifica_tutti(lista, "Il tuo ordine è stato spedito!");
}Crea un mini-RPG: classe astratta Personaggio (nome, hp, attacco, difesa) con metodo virtuale puro abilita_speciale(). Implementa: Guerriero (colpo doppio), Mago (magia AoE), Ladro (critico). Implementa un sistema di combattimento a turni tra due personaggi scelti dall'utente.
#include <iostream>
#include <memory>
#include <cstdlib>
#include <ctime>
using namespace std;
class Personaggio {
protected:
string nome;
int hp, maxHp, attacco, difesa;
public:
Personaggio(const string &n, int hp, int atk, int def)
: nome(n), hp(hp), maxHp(hp), attacco(atk), difesa(def) {}
virtual void abilita_speciale(Personaggio &nemico) = 0;
virtual string classe() const = 0;
virtual ~Personaggio() {}
void subisci_danno(int danno) {
int reale = max(0, danno - difesa);
hp = max(0, hp - reale);
cout << " " << nome << " subisce " << reale << " danni (HP: " << hp << "/" << maxHp << ")\n";
}
bool isVivo() const { return hp > 0; }
string getNome() const { return nome; }
void attacca(Personaggio &nemico) {
cout << nome << " attacca " << nemico.nome << " per " << attacco << "!\n";
nemico.subisci_danno(attacco);
}
void stampa() const {
cout << "[" << classe() << "] " << nome
<< " HP:" << hp << "/" << maxHp
<< " ATK:" << attacco << " DEF:" << difesa << "\n";
}
};
class Guerriero : public Personaggio {
public:
Guerriero(const string &n) : Personaggio(n, 120, 18, 8) {}
string classe() const override { return "Guerriero"; }
void abilita_speciale(Personaggio &nemico) override {
cout << nome << " usa COLPO DOPPIO!\n";
nemico.subisci_danno(attacco);
nemico.subisci_danno(attacco);
}
};
class Mago : public Personaggio {
public:
Mago(const string &n) : Personaggio(n, 80, 28, 3) {}
string classe() const override { return "Mago"; }
void abilita_speciale(Personaggio &nemico) override {
int magia = attacco * 2;
cout << nome << " lancia PALLA DI FUOCO per " << magia << "!\n";
nemico.subisci_danno(magia);
}
};
class Ladro : public Personaggio {
public:
Ladro(const string &n) : Personaggio(n, 90, 22, 5) {}
string classe() const override { return "Ladro"; }
void abilita_speciale(Personaggio &nemico) override {
if (rand() % 2) {
cout << nome << " CRITICO! Danno x3!\n";
nemico.subisci_danno(attacco * 3);
} else {
cout << nome << " manca il colpo critico!\n";
}
}
};
int main() {
srand(time(0));
unique_ptr<Personaggio> p1 = make_unique<Guerriero>("Arthas");
unique_ptr<Personaggio> p2 = make_unique<Mago>("Merlin");
cout << "=== COMBATTIMENTO ===\n";
p1->stampa(); p2->stampa();
int turno = 1;
while (p1->isVivo() && p2->isVivo()) {
cout << "\n--- Turno " << turno++ << " ---\n";
if (turno % 3 == 0) { p1->abilita_speciale(*p2); }
else p1->attacca(*p2);
if (!p2->isVivo()) break;
if (turno % 4 == 0) p2->abilita_speciale(*p1);
else p2->attacca(*p1);
}
cout << "\n=== FINE ===\n";
cout << (p1->isVivo() ? p1->getNome() : p2->getNome()) << " vince!\n";
}