Dispensa: basi della programmazione orientata agli oggetti in C++

Avatar utente

Personale scolastico

0

1. Che cos’è la OOP

La programmazione orientata agli oggetti, o OOP, è un modo di progettare programmi basato sul concetto di oggetto.

Un oggetto rappresenta qualcosa del mondo reale o del problema che vogliamo risolvere: uno studente, un conto corrente, un’automobile, un quadrato, un libro, un prodotto.

Ogni oggetto possiede:

  • attributi, cioè i suoi dati;
  • metodi, cioè le azioni che può compiere.

Esempio:

Un oggetto Studente può avere:

nome
cognome
eta
media

e può compiere azioni come:

stampaDati()
calcolaEsito()
modificaMedia()

2. Classe e oggetto

Una classe è un modello, uno stampo, una descrizione generale.

Un oggetto è un’istanza concreta di quella classe.

Esempio:

class Studente {
    // descrizione generale di uno studente
};

Da questa classe posso creare oggetti diversi:

Studente s1;
Studente s2;

La classe è come il progetto di una casa; l’oggetto è la casa costruita.


3. Primo esempio: classe Quadrato

#include <iostream>
using namespace std;

class Quadrato {
private:
    double lato;

public:
    void setLato(double l) {
        lato = l;
    }

    double getLato() {
        return lato;
    }

    double calcolaArea() {
        return lato * lato;
    }

    double calcolaPerimetro() {
        return lato * 4;
    }
};

int main() {
    Quadrato q;

    q.setLato(5);

    cout << "Lato: " << q.getLato() << endl;
    cout << "Area: " << q.calcolaArea() << endl;
    cout << "Perimetro: " << q.calcolaPerimetro() << endl;

    return 0;
}

4. Attributi

Gli attributi sono le variabili interne di una classe.

Nel caso del quadrato:

private:
    double lato;

L’attributo lato rappresenta la misura del lato del quadrato.

Di solito gli attributi vengono dichiarati private, cioè non accessibili direttamente dall’esterno.


5. Metodi

I metodi sono le funzioni appartenenti a una classe.

Esempio:

double calcolaArea() {
    return lato * lato;
}

Questo metodo usa l’attributo lato per calcolare l’area.


6. Incapsulamento

L’incapsulamento consiste nel proteggere i dati interni di una classe.

Per questo motivo si usano spesso:

private:

per nascondere gli attributi;

public:

per rendere disponibili i metodi.

Esempio:

class Persona {
private:
    string nome;
    int eta;

public:
    void setNome(string n) {
        nome = n;
    }

    string getNome() {
        return nome;
    }

    void setEta(int e) {
        if (e >= 0) {
            eta = e;
        }
    }

    int getEta() {
        return eta;
    }
};

Il vantaggio è che posso controllare i valori inseriti. Per esempio, l’età non può essere negativa.


7. Getter e setter

I setter servono per modificare gli attributi.

void setEta(int e) {
    eta = e;
}

I getter servono per leggere gli attributi.

int getEta() {
    return eta;
}

In questo modo non accediamo direttamente ai dati, ma passiamo attraverso metodi controllati.


8. Costruttore

Il costruttore è un metodo speciale che viene chiamato automaticamente quando nasce un oggetto.

Serve per inizializzare gli attributi.

Esempio:

class Quadrato {
private:
    double lato;

public:
    Quadrato(double l) {
        lato = l;
    }

    double calcolaArea() {
        return lato * lato;
    }
};

Uso:

Quadrato q(5);

Il valore 5 viene passato al costruttore e assegnato al lato.


9. Costruttore di default

Un costruttore senza parametri si chiama costruttore di default.

class Quadrato {
private:
    double lato;

public:
    Quadrato() {
        lato = 1;
    }

    Quadrato(double l) {
        lato = l;
    }

    double calcolaArea() {
        return lato * lato;
    }
};

Uso:

Quadrato q1;
Quadrato q2(5);

q1 avrà lato 1, mentre q2 avrà lato 5.


10. Esempio completo: classe Studente

#include <iostream>
using namespace std;

class Studente {
private:
    string nome;
    string cognome;
    double media;

public:
    Studente(string n, string c, double m) {
        nome = n;
        cognome = c;
        media = m;
    }

    void stampaDati() {
        cout << "Nome: " << nome << endl;
        cout << "Cognome: " << cognome << endl;
        cout << "Media: " << media << endl;
    }

    bool promosso() {
        return media >= 6;
    }
};

int main() {
    Studente s("Mario", "Rossi", 7.5);

    s.stampaDati();

    if (s.promosso()) {
        cout << "Studente promosso" << endl;
    } else {
        cout << "Studente non promosso" << endl;
    }

    return 0;
}

11. Astrazione

L’astrazione consiste nel rappresentare solo gli aspetti importanti di un oggetto, ignorando i dettagli inutili.

Per esempio, se devo gestire un registro elettronico, per uno studente mi interessano nome, cognome, voti e assenze. Non mi interessa il colore degli occhi.

La classe deve contenere solo ciò che serve al problema.


12. Ereditarietà

L’ereditarietà permette di creare una classe partendo da un’altra classe già esistente.

Esempio:

class Persona {
protected:
    string nome;
    string cognome;

public:
    Persona(string n, string c) {
        nome = n;
        cognome = c;
    }

    void stampaPersona() {
        cout << nome << " " << cognome << endl;
    }
};

Ora creo una classe Studente che eredita da Persona:

class Studente : public Persona {
private:
    double media;

public:
    Studente(string n, string c, double m) : Persona(n, c) {
        media = m;
    }

    void stampaStudente() {
        stampaPersona();
        cout << "Media: " << media << endl;
    }
};

Uso:

Studente s("Luca", "Bianchi", 8);
s.stampaStudente();

La classe Studente riutilizza attributi e metodi della classe Persona.


13. Polimorfismo

Il polimorfismo permette a metodi con lo stesso nome di comportarsi in modo diverso.

Un primo esempio semplice è l’overloading, cioè più metodi con lo stesso nome ma parametri diversi.

class Calcolatrice {
public:
    int somma(int a, int b) {
        return a + b;
    }

    double somma(double a, double b) {
        return a + b;
    }

    int somma(int a, int b, int c) {
        return a + b + c;
    }
};

Uso:

Calcolatrice c;

cout << c.somma(2, 3) << endl;
cout << c.somma(2.5, 3.1) << endl;
cout << c.somma(1, 2, 3) << endl;

Il compilatore sceglie automaticamente quale metodo chiamare.


14. I quattro pilastri della OOP

I concetti fondamentali della programmazione orientata agli oggetti sono:

  1. Incapsulamento
    Proteggere i dati interni della classe.
  2. Astrazione
    Rappresentare solo gli aspetti importanti del problema.
  3. Ereditarietà
    Creare nuove classi riutilizzando classi esistenti.
  4. Polimorfismo
    Usare lo stesso nome per comportamenti diversi.

15. Esempio completo: classe ContoCorrente

#include <iostream>
using namespace std;

class ContoCorrente {
private:
    string intestatario;
    double saldo;

public:
    ContoCorrente(string nome, double saldoIniziale) {
        intestatario = nome;

        if (saldoIniziale >= 0) {
            saldo = saldoIniziale;
        } else {
            saldo = 0;
        }
    }

    void deposita(double importo) {
        if (importo > 0) {
            saldo += importo;
        }
    }

    void preleva(double importo) {
        if (importo > 0 && importo <= saldo) {
            saldo -= importo;
        } else {
            cout << "Operazione non consentita" << endl;
        }
    }

    double getSaldo() {
        return saldo;
    }

    void stampa() {
        cout << "Intestatario: " << intestatario << endl;
        cout << "Saldo: " << saldo << " euro" << endl;
    }
};

int main() {
    ContoCorrente conto("Mario Rossi", 1000);

    conto.stampa();

    conto.deposita(200);
    conto.preleva(150);

    cout << "Saldo finale: " << conto.getSaldo() << " euro" << endl;

    return 0;
}

16. Errori comuni

Errore 1: accedere direttamente ad attributi privati

q.lato = 5;

Se lato è private, questa istruzione non è permessa.

Bisogna usare un setter:

q.setLato(5);

Errore 2: dimenticare il punto e virgola dopo la classe

Sbagliato:

class Quadrato {
};

Corretto:

class Quadrato {
};

Attenzione: dopo la parentesi graffa finale della classe serve sempre il punto e virgola.


Errore 3: non inizializzare gli attributi

Se non assegniamo un valore a una variabile, essa può contenere un valore casuale.

Meglio usare sempre un costruttore.


17. Esercizi

Esercizio 1

Creare una classe Rettangolo con:

  • attributi base e altezza;
  • costruttore;
  • metodi calcolaArea() e calcolaPerimetro().

Esercizio 2

Creare una classe Libro con:

  • titolo;
  • autore;
  • numero di pagine;
  • metodo stampaDati().

Esercizio 3

Creare una classe Automobile con:

  • marca;
  • modello;
  • velocità;
  • metodo accelera();
  • metodo frena();
  • metodo stampaVelocita().

Esercizio 4

Creare una classe Prodotto con:

  • nome;
  • prezzo;
  • quantità;
  • metodo calcolaTotale().

Esercizio 5

Creare una classe Studente con:

  • nome;
  • cognome;
  • tre voti;
  • metodo per calcolare la media;
  • metodo per stabilire se lo studente è promosso.

18. Mini-verifica

  1. Che cos’è una classe?
  2. Che cos’è un oggetto?
  3. Qual è la differenza tra attributi e metodi?
  4. A cosa serve private?
  5. A cosa serve public?
  6. Che cosa sono getter e setter?
  7. A cosa serve un costruttore?
  8. Che cos’è l’incapsulamento?
  9. Che cos’è l’ereditarietà?
  10. Che cos’è il polimorfismo?

19. Soluzione guidata esercizio Rettangolo

#include <iostream>
using namespace std;

class Rettangolo {
private:
    double base;
    double altezza;

public:
    Rettangolo(double b, double h) {
        base = b;
        altezza = h;
    }

    double calcolaArea() {
        return base * altezza;
    }

    double calcolaPerimetro() {
        return 2 * (base + altezza);
    }

    void stampa() {
        cout << "Base: " << base << endl;
        cout << "Altezza: " << altezza << endl;
        cout << "Area: " << calcolaArea() << endl;
        cout << "Perimetro: " << calcolaPerimetro() << endl;
    }
};

int main() {
    Rettangolo r(5, 3);

    r.stampa();

    return 0;
}

20. Conclusione

La programmazione orientata agli oggetti permette di organizzare meglio il codice, rendendolo più chiaro, riutilizzabile e vicino al modo in cui descriviamo i problemi reali.

In C++, una classe raccoglie dati e funzioni in un’unica struttura. Gli oggetti creati da una classe possono interagire tra loro e rappresentare elementi concreti del programma.

I concetti principali da ricordare sono:

  • classe;
  • oggetto;
  • attributi;
  • metodi;
  • incapsulamento;
  • costruttori;
  • ereditarietà;
  • polimorfismo.