Vai al contenuto

La programmazione orientata agli oggetti

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! 🚀

Esercitazioni Pratiche C++

Metti in pratica ciò che hai imparato con queste esercitazioni complete

Tutte le Esercitazioni

📊 Variabili e Tipi di Dato

➕ Operatori Matematici e Logici in C++

🔀 Strutture di controllo condizionali

🔄 I cicli in C++

🗃️ C++ gli array

⚙️ Le funzioni in C++

📈 Ordinamento dei Dati

🏗️ Le struct in C++

🎯 I puntatori

🧩 La Programmazione Orientata agli Oggetti (OOP)

📁 La gestione dei file in C++

💪 Esercizi C++ Raccolta Completa

12 esercitazioni complete per padroneggiare il C++