Programmazione Orientata agli Oggetti in C++

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: = 0 rende 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! 🚀

Torna in alto