Classi, oggetti, ereditarietà e polimorfismo per codice modulare e riutilizzabile.
La Programmazione Orientata agli Oggetti (OOP) è un paradigma che permette di modellare il mondo reale attraverso entità chiamate classi e oggetti.
🎯 Vantaggi dell'OOP: Riuso del codice, incapsulamento, astrazione, manutenzione più semplice e organizzazione migliore
1. Classi e Oggetti
📌 Differenza fondamentale: Classe: Modello/template | Oggetto: Istanza concreta della classe
1.1 Struttura di una Classe
P private
Accessibile solo all'interno della classe
Protegge i dati interni
private: int eta;
U public
Accessibile da ovunque
Interfaccia pubblica della classe
public: void stampa();
R protected
Accessibile dalla classe e dalle classi derivate
Utile per l'ereditarietà
protected: string nome;
1.2 Esempio Pratico: Classe Persona
#include <iostream>
#include <string>
using namespace std;
class Persona {
private :
// Attributi privati: accessibili solo all'interno della classe
string nome;
int eta;
public :
// Costruttore parametrizzato
Persona(string n, int e) : nome(n), eta(e) { }
// Metodi pubblici: interfaccia della classe
void stampaDettagli() {
cout << "Nome: " << nome << ", Età: " << eta << endl;
}
// Getter per nome
string getNome() const {
return nome;
}
// Setter per età
void setEta(int nuovaEta) {
if (nuovaEta >= 0) {
eta = nuovaEta;
}
}
};
int main() {
// Creazione di oggetti (istanze della classe)
Persona p1("Mario Rossi", 30);
Persona p2("Anna Verdi", 25);
p1.stampaDettagli();
p2.stampaDettagli();
// Utilizzo di getter e setter
cout << "Nome di p1: " << p1.getNome() << endl;
p1.setEta(35);
p1.stampaDettagli();
return 0;
}
💡 Spiegazione: • Incapsulamento: Gli attributi sono privati e accessibili solo tramite metodi pubblici • Costruttore: Inizializza gli oggetti quando vengono creati • Getter/Setter: Controllano l'accesso ai dati privati
2. Costruttori e Distruttori
📌 Funzioni speciali: Chiamate automaticamente alla creazione e distruzione degli oggetti
2.1 Tipi di Costruttori
D Default
Nessun parametro
Valori predefiniti
Persona()
P Parametrizzato
Con parametri
Inizializzazione specifica
Persona(string n, int e)
C Copia
Copia un oggetto esistente
Creazione per copia
Persona(const Persona& p)
2.2 Esempio Completo
#include <iostream>
using namespace std;
class Punto {
private :
int x, y;
public :
// Costruttore di default
Punto() : x(0), y(0) {
cout << "Costruttore default chiamato" << endl;
}
// Costruttore parametrizzato
Punto(int xVal, int yVal) : x(xVal), y(yVal) {
cout << "Costruttore parametrizzato chiamato" << endl;
}
// Costruttore di copia
Punto(const Punto &p) : x(p.x), y(p.y) {
cout << "Costruttore di copia chiamato" << endl;
}
// Distruttore
~Punto() {
cout << "Distruttore chiamato per (" << x << ", " << y << ")" << endl;
}
void stampa() {
cout << "Punto(" << x << ", " << y << ")" << endl;
}
};
int main() {
cout << "=== CREAZIONE OGGETTI ===" << endl;
Punto p1; // Costruttore default
Punto p2(5, 10); // Costruttore parametrizzato
Punto p3 = p2; // Costruttore di copia
p1.stampa();
p2.stampa();
p3.stampa();
cout << "=== USCITA DAL MAIN ===" << endl;
// I distruttori vengono chiamati automaticamente qui
return 0;
}
3. Ereditarietà
📌 Concetto chiave: Le classi derivate ereditano attributi e metodi dalle classi base, favorendo il riuso del codice
3.1 Tipi di Ereditarietà
P public
public → public
protected → protected
Mantenimento accesso
R protected
public → protected
protected → protected
Limitazione accesso
V private
public → private
protected → private
Accesso solo in classe
3.2 Esempio: Gerarchia Animale
#include <iostream>
#include <string>
using namespace std;
// Classe base
class Animale {
protected :
string nome;
int eta;
public :
Animale(string n, int e) : nome(n), eta(e) { }
void dormi() {
cout << nome << " sta dormendo." << endl;
}
void mangia() {
cout << nome << " sta mangiando." << endl;
}
// Metodo virtuale per polimorfismo
virtual void verso() {
cout << nome << " emette un verso generico." << endl;
}
// Distruttore virtuale
virtual ~Animale() {
cout << "Distruttore Animale per " << nome << endl;
}
};
// Classe derivata - ereditarietà public
class Cane : public Animale {
private :
string razza;
public :
Cane(string n, int e, string r) : Animale(n, e), razza(r) { }
// Override del metodo verso
void verso() override {
cout << nome << " (" << razza << ") abbaia: Bau Bau!" << endl;
}
// Metodo specifico della classe Cane
void scodinzola() {
cout << nome << " sta scodinzolando!" << endl;
}
};
class Gatto : public Animale {
private :
bool peloLungo;
public :
Gatto(string n, int e, bool pelo) : Animale(n, e), peloLungo(pelo) { }
void verso() override {
cout << nome << " miagola: Miao Miao!";
cout << " (Pelo " << (peloLungo ? "lungo" : "corto") << ")" << endl;
}
void graffia() {
cout << nome << " sta graffiando!" << endl;
}
};
int main() {
Cane fido("Fido", 3, "Labrador");
Gatto whiskers("Whiskers", 2, true);
fido.dormi(); // Metodo ereditato
fido.verso(); // Metodo override
fido.scodinzola(); // Metodo specifico
cout << endl;
whiskers.mangia(); // Metodo ereditato
whiskers.verso(); // Metodo override
whiskers.graffia(); // Metodo specifico
return 0;
}
4. Polimorfismo
🎭 Cos'è il polimorfismo? "Molte forme" - lo stesso metodo si comporta diversamente a seconda del tipo dell'oggetto
4.1 Polimorfismo con Puntatori
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Forma {
protected :
string nome;
public :
Forma(string n) : nome(n) { }
// Metodo virtuale puro - rende la classe astratta
virtual double calcolaArea() const = 0;
// Metodo virtuale con implementazione di default
virtual void descrivi() const {
cout << "Forma: " << nome << endl;
}
// Distruttore virtuale
virtual ~Forma() { }
};
class Rettangolo : public Forma {
private :
double base, altezza;
public :
Rettangolo(double b, double h)
: Forma("Rettangolo"), base(b), altezza(h) { }
double calcolaArea() const override {
return base * altezza;
}
void descrivi() const override {
cout << "Rettangolo " << base << "x" << altezza
<< " - Area: " << calcolaArea() << endl;
}
};
class Cerchio : public Forma {
private :
double raggio;
public :
Cerchio(double r) : Forma("Cerchio"), raggio(r) { }
double calcolaArea() const override {
return 3.14159 * raggio * raggio;
}
void descrivi() const override {
cout << "Cerchio raggio " << raggio
<< " - Area: " << calcolaArea() << endl;
}
};
int main() {
// Array polimorfico di puntatori a Forma
vector<Forma*> forme;
forme.push_back(new Rettangolo(5, 3));
forme.push_back(new Cerchio(4));
forme.push_back(new Rettangolo(2, 8));
forme.push_back(new Cerchio(2.5));
cout << "=== DESCRIZIONE FORME ===" << endl;
for (Forma* forma : forme) {
forma->descrivi(); // Polimorfismo: chiama il metodo corretto
}
cout << "\\n=== CALCOLO AREA TOTALE ===" << endl;
double areaTotale = 0;
for (Forma* forma : forme) {
areaTotale += forma->calcolaArea(); // Polimorfismo
}
cout << "Area totale: " << areaTotale << endl;
// Pulizia memoria
for (Forma* forma : forme) {
delete forma;
}
return 0;
}
💡 Spiegazione del polimorfismo: • Metodo virtuale puro:
= 0rende la classe astratta • Binding dinamico: A runtime viene deciso quale metodo chiamare • Puntatori base: Possiamo gestire oggetti diversi con lo stesso tipo di puntatore • Distruttore virtuale: Garantisce la corretta deallocazione
Esempio Completo: Sistema Veicoli
Applicazione pratica di tutti i concetti OOP
Sistema di Gestione Veicoli
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// Classe base astratta
class Veicolo {
protected :
string marca;
int anno;
double prezzo;
public :
Veicolo(string m, int a, double p) : marca(m), anno(a), prezzo(p) { }
// Metodo virtuale puro - classe astratta
virtual void descrivi() const = 0;
// Metodo virtuale con implementazione
virtual void accelera() {
cout << marca << " sta accelerando!" << endl;
}
// Getter
double getPrezzo() const { return prezzo; }
// Distruttore virtuale
virtual ~Veicolo() { }
};
class Auto : public Veicolo {
private :
int numPorte;
string carburante;
public :
Auto(string m, int a, double p, int porte, string carb)
: Veicolo(m, a, p), numPorte(porte), carburante(carb) { }
void descrivi() const override {
cout << "Auto: " << marca << " (" << anno << ") - "
<< numPorte << " porte, " << carburante
<< " - Prezzo: " << prezzo << "€" << endl;
}
void accelera() override {
cout << marca << " accelera rapidamente! Vroom Vroom!" << endl;
}
};
class Moto : public Veicolo {
private :
int cilindrata;
bool carenata;
public :
Moto(string m, int a, double p, int cil, bool car)
: Veicolo(m, a, p), cilindrata(cil), carenata(car) { }
void descrivi() const override {
cout << "Moto: " << marca << " (" << anno << ") - "
<< cilindrata << "cc, "
<< (carenata ? "carenata" : "nuda")
<< " - Prezzo: " << prezzo << "€" << endl;
}
void accelera() override {
cout << marca << " accelera agilmente! Brum Brum!" << endl;
}
// Metodo specifico per moto
void impenna() {
cout << marca << " sta impennando! Woo!" << endl;
}
};
class Concessionario {
private :
vector<Veicolo*> veicoli;
public :
void aggiungiVeicolo(Veicolo* v) {
veicoli.push_back(v);
}
void mostraInventario() {
cout << "=== INVENTARIO CONCESSIONARIO ===" << endl;
for (Veicolo* v : veicoli) {
v->descrivi(); // Polimorfismo
}
}
double calcolaValoreInventario() {
double totale = 0;
for (Veicolo* v : veicoli) {
totale += v->getPrezzo();
}
return totale;
}
~Concessionario() {
for (Veicolo* v : veicoli) {
delete v;
}
}
};
int main() {
Concessionario concessionario;
// Aggiunta veicoli all'inventario
concessionario.aggiungiVeicolo(new Auto("Fiat", 2020, 15000, 5, "Benzina"));
concessionario.aggiungiVeicolo(new Auto("Tesla", 2022, 45000, 5, "Elettrico"));
concessionario.aggiungiVeicolo(new Moto("Yamaha", 2021, 8000, 600, true));
concessionario.aggiungiVeicolo(new Moto("Ducati", 2023, 12000, 1000, false));
// Operazioni sul concessionario
concessionario.mostraInventario();
cout << "\\nValore totale inventario: "
<< concessionario.calcolaValoreInventario() << "€" << endl;
return 0;
}
6. Best Practices OOP
Regole fondamentali:
Incapsulamento
Rendi gli attributi privati e fornisci metodi pubblici per l'accesso
Distruttori Virtuali
Usa sempre distruttori virtuali nelle classi base con metodi virtuali
Ereditarietà Pubblica
Preferisci l'ereditarietà pubblica per il polimorfismo
Composizione over Ereditarietà
Preferisci la composizione quando possibile ("has-a" vs "is-a")
RAII
Resource Acquisition Is Initialization - gestisci risorse nei costruttori/distruttori
Conclusioni
Punti chiave dell'OOP:
Classi e Oggetti
I mattoni fondamentali che racchiudono dati e funzioni in un'unica entità
Costruttori e Distruttori
Gestiscono l'inizializzazione e la pulizia automatica degli oggetti
Ereditarietà
Permette il riuso del codice e la creazione di gerarchie
Polimorfismo
Rende il codice flessibile attraverso il binding dinamico
La Programmazione Orientata agli Oggetti rappresenta un modo potente e organizzato di sviluppare software. Gli esempi forniti, dalla semplice classe Persona fino al sistema complesso di gestione veicoli, dimostrano come l'OOP possa essere applicata per risolvere problemi reali in modo modulare, manutenibile ed efficiente. Buon coding! 🚀
Esercitazioni Pratiche C++
Metti in pratica ciò che hai imparato con queste esercitazioni complete
Tutte le Esercitazioni
12 esercitazioni complete per padroneggiare il C++