I 4 Pilastri dell'OOP

🔒 Incapsulamento

Nascondere i dettagli interni. I dati (attributi) sono privati, accessibili solo tramite metodi pubblici.

🌳 Ereditarietà

Una classe può estendere un'altra, riutilizzando e specializzando il codice esistente.

🎭 Polimorfismo

Stesso interfaccia, comportamenti diversi. Funzioni virtuali e overriding.

🏛️ Astrazione

Modellare entità del mondo reale nascondendo la complessità implementativa.

Prima Classe C++

C++Classe Rettangolo — esempio completo
#include <iostream>
#include <string>
using namespace std;

class Rettangolo {
private:                        // Attributi privati (incapsulamento)
    double larghezza;
    double altezza;

public:                         // Interfaccia pubblica
    // Costruttore con parametri + valori di default
    Rettangolo(double l = 1.0, double a = 1.0) {
        // Validazione all'interno del costruttore
        larghezza = (l > 0) ? l : 1.0;
        altezza   = (a > 0) ? a : 1.0;
    }

    // Getters (restituiscono valori)
    double getLarghezza() const { return larghezza; }
    double getAltezza()   const { return altezza; }

    // Setters (modificano valori con validazione)
    void setLarghezza(double l) { if (l > 0) larghezza = l; }
    void setAltezza(double a)   { if (a > 0) altezza = a; }

    // Metodi di calcolo
    double area()       const { return larghezza * altezza; }
    double perimetro()  const { return 2 * (larghezza + altezza); }
    bool   eQuadrato()  const { return larghezza == altezza; }

    // Stampa le info
    void stampa() const {
        cout << "Rettangolo " << larghezza << "x" << altezza
             << " | Area: " << area()
             << " | Perim: " << perimetro()
             << (eQuadrato() ? " [Quadrato]" : "") << "\n";
    }
};

int main() {
    Rettangolo r1(5.0, 3.0);    // Costruttore con parametri
    Rettangolo r2(4.0);          // a = 1.0 (default)
    Rettangolo r3;               // 1x1 (default)
    Rettangolo quadrato(6.0, 6.0);

    r1.stampa();
    r2.stampa();
    r3.stampa();
    quadrato.stampa();

    // Accesso tramite getter/setter
    r1.setLarghezza(10.0);
    cout << "\nDopo setLarghezza(10): ";
    r1.stampa();

    return 0;
}

Costruttori, Distruttori e this

C++Costruttori multipli, lista di inizializzazione, ~Distruttore
#include <iostream>
#include <cstring>
using namespace std;

class Stringa {
private:
    char *dati;
    int   lunghezza;

public:
    // Costruttore di default
    Stringa() : dati(nullptr), lunghezza(0) {
        cout << "Costruttore default\n";
    }

    // Costruttore con parametro
    Stringa(const char *s) {
        lunghezza = strlen(s);
        dati = new char[lunghezza + 1];  // Alloca dinamicamente
        strcpy(dati, s);
        cout << "Costruttore(\"" << dati << "\")\n";
    }

    // Costruttore di copia (deep copy)
    Stringa(const Stringa &altro) {
        lunghezza = altro.lunghezza;
        dati = new char[lunghezza + 1];
        strcpy(dati, altro.dati);
        cout << "Costruttore copia(\"" << dati << "\")\n";
    }

    // Distruttore: chiamato automaticamente quando l'oggetto esce dallo scope
    ~Stringa() {
        cout << "Distruttore(\"" << (dati ? dati : "") << "\")\n";
        delete[] dati;   // Libera memoria allocata con new
    }

    // Operatore di assegnamento
    Stringa& operator=(const Stringa &altro) {
        if (this != &altro) {  // self-assignment check
            delete[] dati;
            lunghezza = altro.lunghezza;
            dati = new char[lunghezza + 1];
            strcpy(dati, altro.dati);
        }
        return *this;   // Ritorna riferimento a sé stesso
    }

    void stampa() const {
        cout << "\"" << (dati ? dati : "") << "\" (len=" << lunghezza << ")\n";
    }
};

int main() {
    cout << "--- Creazione ---\n";
    Stringa s1("Hello");
    Stringa s2("World");

    cout << "\n--- Copia ---\n";
    Stringa s3 = s1;          // Costruttore di copia
    Stringa s4;
    s4 = s2;                   // Operatore di assegnamento

    cout << "\n--- Stampa ---\n";
    s1.stampa(); s2.stampa(); s3.stampa(); s4.stampa();

    cout << "\n--- Fine scope (distruttori) ---\n";
    return 0;
}
Regola del Tre (Rule of Three) Se definisci uno tra: distruttore, costruttore di copia, operatore di assegnamento — dovresti definirli tutti e tre. In C++11 si estende alla Rule of Five (aggiunge move constructor e move assignment).

Overloading degli Operatori

C++Classe Vettore2D con operatori
#include <iostream>
#include <cmath>
using namespace std;

class Vec2D {
public:
    double x, y;

    Vec2D(double x = 0, double y = 0) : x(x), y(y) {}

    // Operatori aritmetici
    Vec2D operator+(const Vec2D &v) const { return {x+v.x, y+v.y}; }
    Vec2D operator-(const Vec2D &v) const { return {x-v.x, y-v.y}; }
    Vec2D operator*(double s)       const { return {x*s, y*s}; }

    // Prodotto scalare
    double operator*(const Vec2D &v) const { return x*v.x + y*v.y; }

    // Operatori di uguaglianza
    bool operator==(const Vec2D &v) const { return x==v.x && y==v.y; }
    bool operator!=(const Vec2D &v) const { return !(*this == v); }

    // Lunghezza del vettore
    double norma() const { return sqrt(x*x + y*y); }

    // Operatore di output (friend per accedere a privati se necessario)
    friend ostream& operator<<(ostream &os, const Vec2D &v) {
        return os << "(" << v.x << ", " << v.y << ")";
    }
};

int main() {
    Vec2D a(3, 4), b(1, 2);

    cout << "a = " << a << "\n";
    cout << "b = " << b << "\n";
    cout << "a + b = " << (a + b) << "\n";
    cout << "a - b = " << (a - b) << "\n";
    cout << "a * 2 = " << (a * 2) << "\n";
    cout << "a · b = " << (a * b) << " (prodotto scalare)\n";
    cout << "|a| = " << a.norma() << "\n";
    cout << "a == b? " << (a == b ? "Si" : "No") << "\n";

    return 0;
}

Membri static e const

C++static e const nei metodi
#include <iostream>
using namespace std;

class Contatore {
private:
    int id;
    static int totale;      // Condiviso da TUTTI gli oggetti della classe

public:
    Contatore() {
        totale++;
        id = totale;
        cout << "Oggetto #" << id << " creato. Totale: " << totale << "\n";
    }

    ~Contatore() {
        cout << "Oggetto #" << id << " distrutto.\n";
        totale--;
    }

    int getId() const { return id; }            // const: non modifica l'oggetto
    static int getTotale() { return totale; }   // static: non ha this
};

int Contatore::totale = 0;  // Inizializzazione fuori dalla classe

int main() {
    cout << "Inizio. Totale: " << Contatore::getTotale() << "\n";
    {
        Contatore a, b, c;
        cout << "Dentro il blocco. Totale: " << Contatore::getTotale() << "\n";
    }
    cout << "Fine blocco. Totale: " << Contatore::getTotale() << "\n";
    return 0;
}
🏋️ Esercizi
Applica la programmazione orientata agli oggetti.
1
Classe Frazione
FacileClassi / overloading

Crea una classe Frazione con numeratore e denominatore. Implementa: costruttore con riduzione ai minimi termini (usando MCD), operatori +, -, *, /, ==, e operator<< per stamparla come "a/b".

C++
#include <iostream>
using namespace std;

int mcd(int a, int b) { return b == 0 ? a : mcd(b, a % b); }

class Frazione {
    int num, den;
    void riduci() {
        if (den < 0) { num = -num; den = -den; }
        int g = mcd(abs(num), den);
        num /= g; den /= g;
    }
public:
    Frazione(int n=0, int d=1) : num(n), den(d) { riduci(); }

    Frazione operator+(const Frazione &f) const { return {num*f.den + f.num*den, den*f.den}; }
    Frazione operator-(const Frazione &f) const { return {num*f.den - f.num*den, den*f.den}; }
    Frazione operator*(const Frazione &f) const { return {num*f.num, den*f.den}; }
    Frazione operator/(const Frazione &f) const { return {num*f.den, den*f.num}; }
    bool operator==(const Frazione &f) const { return num==f.num && den==f.den; }

    friend ostream& operator<<(ostream &os, const Frazione &f) {
        return os << f.num << "/" << f.den;
    }
};

int main() {
    Frazione a(1, 2), b(1, 3);
    cout << a << " + " << b << " = " << (a+b) << "\n";
    cout << a << " - " << b << " = " << (a-b) << "\n";
    cout << a << " * " << b << " = " << (a*b) << "\n";
    cout << a << " / " << b << " = " << (a/b) << "\n";
    cout << "1/2 == 2/4? " << (Frazione(1,2)==Frazione(2,4) ? "Si" : "No") << "\n";
}
2
Classe Pila (Stack) templata
MedioTemplate / classi

Crea una classe Stack<T> generica con: push(val), pop(), top(), isEmpty(), size(). Usala con int, double e string nel main. Lancia un'eccezione se si fa pop su uno stack vuoto.

C++
#include <iostream>
#include <vector>
#include <stdexcept>
#include <string>
using namespace std;

template <typename T>
class Stack {
    vector<T> dati;
public:
    void push(const T &val) { dati.push_back(val); }

    T pop() {
        if (isEmpty()) throw runtime_error("Stack vuoto!");
        T top = dati.back();
        dati.pop_back();
        return top;
    }

    T& top() {
        if (isEmpty()) throw runtime_error("Stack vuoto!");
        return dati.back();
    }

    bool isEmpty() const { return dati.empty(); }
    int  size()    const { return dati.size(); }
};

int main() {
    Stack<int> si;
    si.push(10); si.push(20); si.push(30);
    while (!si.isEmpty()) cout << si.pop() << " ";
    cout << "\n";

    Stack<string> ss;
    ss.push("ciao"); ss.push("mondo");
    cout << "Top: " << ss.top() << "\n";

    try { Stack<int> vuoto; vuoto.pop(); }
    catch (const exception &e) { cout << "Eccezione: " << e.what() << "\n"; }
}
3
Conto bancario
DifficileOOP completo

Crea la classe ContoBancario con: IBAN (generato automaticamente), titolare, saldo. Metodi: deposita, preleva (con eccezione se saldo insufficiente), trasferisci (da conto a conto), storico (vettore di stringhe con le ultime 10 operazioni). Usa static per il contatore IBAN.

C++
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <stdexcept>
#include <iomanip>
using namespace std;

class ContoBancario {
    static int contatore;
    string iban, titolare;
    double saldo;
    vector<string> movimenti;

    void aggiungi_movimento(const string &desc) {
        if (movimenti.size() >= 10) movimenti.erase(movimenti.begin());
        movimenti.push_back(desc);
    }

public:
    ContoBancario(const string &tit, double saldo_iniz = 0)
        : titolare(tit), saldo(saldo_iniz) {
        iban = "IT" + to_string(++contatore);
        aggiungi_movimento("Apertura conto: +" + to_string(saldo_iniz));
    }

    void deposita(double importo) {
        if (importo <= 0) throw invalid_argument("Importo non valido");
        saldo += importo;
        aggiungi_movimento("Deposito: +" + to_string(importo));
    }

    void preleva(double importo) {
        if (importo <= 0) throw invalid_argument("Importo non valido");
        if (importo > saldo) throw runtime_error("Fondi insufficienti");
        saldo -= importo;
        aggiungi_movimento("Prelievo: -" + to_string(importo));
    }

    void trasferisci(ContoBancario &dest, double importo) {
        preleva(importo);
        dest.deposita(importo);
        aggiungi_movimento("Bonifico a " + dest.iban + ": -" + to_string(importo));
    }

    void stampa() const {
        cout << "\n=== " << iban << " | " << titolare
             << " | Saldo: " << fixed << setprecision(2) << saldo << " EUR ===\n";
        for (const auto &m : movimenti) cout << "  " << m << "\n";
    }

    double getSaldo() const { return saldo; }
};

int ContoBancario::contatore = 0;

int main() {
    ContoBancario c1("Mario Rossi",  1000.0);
    ContoBancario c2("Anna Bianchi", 500.0);

    c1.deposita(250.0);
    c1.preleva(100.0);
    c1.trasferisci(c2, 300.0);

    c1.stampa();
    c2.stampa();

    try { c1.preleva(5000.0); }
    catch (const exception &e) { cout << "\nErrore: " << e.what() << "\n"; }
}