gl/ripetizioni

Classi e oggetti in quarta informatica: la verifica di OOP

Classi, costruttori, incapsulamento e metodi in C++: come affrontare la prima verifica di programmazione a oggetti in quarta all'istituto tecnico informatico.

di Gaetano Livornese

  • #informatica
  • #quarta superiore
  • #tecnico
  • #programmazione a oggetti
  • #classi e oggetti
  • #c++

Fine settembre 2026, e in quarta all’istituto tecnico, indirizzo Informatica e telecomunicazioni, l’anno di Informatica è ripartito da un punto preciso: la programmazione a oggetti. Dopo tre anni passati a scrivere programmi come sequenze di istruzioni su variabili sparse, in quarta si cambia modo di pensare — e il cambio non è indolore. La prima verifica, che segue il formato tipico di Informatica in questo indirizzo (prova pratica al PC più scritto teorico), arriva fra cinque e otto settimane da adesso, quindi fra fine ottobre e novembre.

Se in terza il tuo scoglio era cicli e array — ne parlo in Cicli annidati e array in terza informatica — qui lo scoglio è diverso: non è più “far girare il programma”, è organizzare i dati e le operazioni dentro un oggetto invece che lasciarli sparsi in giro.

Perché si passa agli oggetti

Fino a terza, un programma che gestisce, per esempio, un conto corrente probabilmente aveva un saldo in una variabile e delle funzioni che la modificavano passandola come parametro. Funziona, ma ha un problema che si vede appena il programma cresce: niente impedisce a un pezzo di codice lontano di modificare il saldo direttamente, magari portandolo sotto zero senza controllo, perché la variabile è “in giro” e chiunque ci arriva.

La programmazione a oggetti risponde a questo: invece di avere dati e funzioni separati, li incapsula insieme in un’unica unità — la classe — e decide esplicitamente cosa può essere toccato dall’esterno e cosa no. Non è un vezzo sintattico: è un modo diverso di organizzare la responsabilità del codice, ed è per questo che la verifica di OOP insiste tanto su dove metti public e dove metti private, non solo sul fatto che il programma “funzioni”.

Classe e oggetto: la differenza che va detta bene

Una classe è un progetto, una descrizione: dice quali attributi (dati) e quali metodi (funzioni) avrà ogni oggetto costruito a partire da lei. Un oggetto è un’istanza concreta di quella classe, con i suoi valori specifici. La classe ContoCorrente descrive che ogni conto ha un saldo e un titolare; l’oggetto c1 è un conto specifico, con saldo 500 e titolare “Rossi”.

class ContoCorrente {
  private:
    double saldo;
    std::string titolare;

  public:
    ContoCorrente(std::string t, double s) {
      titolare = t;
      saldo = s;
    }

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

    double getSaldo() {
      return saldo;
    }
};

ContoCorrente c1("Rossi", 500.0); crea un oggetto: da qui in poi c1 è un conto reale, con i suoi dati, distinto da un eventuale c2.

Attributi e metodi, public e private

Gli attributi sono i dati della classe (saldo, titolare); i metodi sono le sue funzioni (deposita, getSaldo). La keyword private rende un membro accessibile solo dall’interno della classe stessa; public lo rende accessibile anche dall’esterno.

L’incapsulamento è la pratica di tenere gli attributi private e di esporre solo i metodi necessari per leggerli o modificarli in modo controllato. Non è una regola stilistica: è quello che impedisce a main() di scrivere c1.saldo = -1000; direttamente, bypassando ogni controllo. Se saldo è private, quella riga semplicemente non compila.

Getter e setter

Un getter è un metodo che restituisce il valore di un attributo privato (getSaldo() sopra); un setter è un metodo che lo modifica, di solito con un controllo:

void setSaldo(double s) {
  if (s >= 0) {
    saldo = s;
  }
}

Il punto del setter non è “essere un getter al contrario”: è il posto dove metti la validazione. Un attributo private con un setter che controlla è più sicuro di un attributo public senza controlli — questo è esattamente il tipo di domanda teorica che una verifica di OOP fa: “perché usare un setter invece di rendere l’attributo public?”.

Il costruttore, in tutte le sue forme

Il costruttore è un metodo speciale che ha lo stesso nome della classe, non ha tipo di ritorno (nemmeno void), e viene chiamato automaticamente quando si crea un oggetto. Esistono più forme:

Costruttore di default (nessun parametro):

ContoCorrente() {
  saldo = 0.0;
  titolare = "";
}

Costruttore con parametri (quello visto sopra), che permette di inizializzare l’oggetto con valori specifici al momento della creazione.

Lista di inizializzazione, una sintassi alternativa per inizializzare gli attributi, spesso richiesta per gli attributi const o per efficienza:

ContoCorrente(std::string t, double s) : titolare(t), saldo(s) {
  // corpo vuoto, o controlli aggiuntivi qui
}

La lista di inizializzazione (dopo i due punti, prima delle graffe) inizializza gli attributi direttamente, invece di assegnarli dentro il corpo del costruttore. Per una verifica di quarta, sapere che esiste e riconoscerla in un frammento di codice basta; il motivo profondo (evitare una doppia costruzione per certi tipi) di solito arriva più avanti.

Una classe può avere anche un distruttore, chiamato automaticamente quando l’oggetto viene distrutto — utile soprattutto quando la classe gestisce risorse (memoria allocata dinamicamente, file aperti):

~ContoCorrente() {
  // pulizia, se serve
}

Ha lo stesso nome della classe preceduto da ~, nessun parametro, nessun tipo di ritorno.

this

Dentro un metodo, this è un puntatore all’oggetto corrente su cui il metodo è stato chiamato. Serve soprattutto quando un parametro ha lo stesso nome di un attributo:

void setTitolare(std::string titolare) {
  this->titolare = titolare;
}

Senza this->, titolare = titolare; assegnerebbe il parametro a se stesso, lasciando l’attributo della classe invariato — un errore di ombreggiamento (shadowing) che spesso non dà nessun errore di compilazione.

Array di oggetti

Un array può contenere oggetti, non solo tipi primitivi:

ContoCorrente conti[3] = {
  ContoCorrente("Rossi", 500.0),
  ContoCorrente("Bianchi", 1200.0),
  ContoCorrente("Verdi", 300.0)
};

for (int i = 0; i < 3; i++) {
  std::cout << conti[i].getSaldo() << std::endl;
}

Si accede a ogni oggetto dell’array con l’indice, e ai suoi membri pubblici con il punto, esattamente come per un oggetto singolo: conti[i].getSaldo(). È un caso che unisce due argomenti dell’anno scorso e di questo: cicli e array da un lato, classi dall’altro — ed è per questo che compare spesso nella prova pratica.

Un primo sguardo all’ereditarietà

L’ereditarietà permette di definire una classe a partire da un’altra, ereditandone attributi e metodi e aggiungendone di propri o modificandoli. L’idea, per ora, basta che sia chiara a livello di concetto: una classe ContoRisparmio potrebbe ereditare da ContoCorrente e aggiungere un tasso di interesse. La sintassi e i dettagli (protected, override dei metodi, costruttori della classe base) sono argomento del blocco successivo del programma — qui conta solo aver capito perché servirebbe: evitare di riscrivere da zero una classe che assomiglia a un’altra già esistente.

Un’osservazione sul linguaggio

Il C++ è uno dei linguaggi a oggetti più diffusi nelle scuole, ma non l’unico: alcune classi usano un linguaggio diverso per introdurre l’OOP. Cambiano la sintassi e alcuni dettagli (per esempio la gestione della memoria), ma i concetti — classe, oggetto, incapsulamento, costruttore, ereditarietà — sono gli stessi ovunque. Se il tuo corso usa un altro linguaggio, questo post ti serve comunque per il ragionamento; la sintassi degli esempi va adattata.

Esercizi nel formato della verifica

1. Classe ContoCorrente con validazione nel setter. Scrivi una classe con attributi privati saldo e titolare, un costruttore con parametri, un metodo deposita(double importo) che rifiuta importi negativi, e un setter per il saldo che non permette valori sotto zero.

class ContoCorrente {
  private:
    double saldo;
    std::string titolare;

  public:
    ContoCorrente(std::string t, double s) : titolare(t), saldo(s) {}

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

    void setSaldo(double s) {
      if (s >= 0) {
        saldo = s;
      }
    }

    double getSaldo() {
      return saldo;
    }
};

2. Classe Rettangolo con area e perimetro.

class Rettangolo {
  private:
    double base;
    double altezza;

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

    double area() {
      return base * altezza;
    }

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

Con base = 6.0 e altezza = 4.0: area , perimetro .

3. Trova l’errore. Cosa c’è di sbagliato qui?

class Punto {
  private:
    int x, y;

  public:
    Punto(int a, int b) {
      x = a;
      y = b;
    }
};

int main() {
  Punto p(3, 4);
  std::cout << p.x << std::endl;
  return 0;
}

Soluzione: p.x prova ad accedere a un attributo private da fuori la classe — non compila. Serve un getter, per esempio int getX() { return x; }, e chiamare p.getX(). Un secondo errore, più insidioso, che compare spesso nello stesso tipo di esercizio: un “costruttore” scritto con un tipo di ritorno (void Punto(int a, int b) { ... }) non è affatto un costruttore per il compilatore, è un metodo normale che non viene mai chiamato automaticamente.

4. Array di oggetti con ricerca del massimo. Data una classe Studente con attributo privato voto e getter getVoto(), scrivi il codice che, dato un array di 5 oggetti Studente, trova e stampa il voto più alto.

Studente classe[5] = { /* ... oggetti già creati ... */ };
double massimo = classe[0].getVoto();

for (int i = 1; i < 5; i++) {
  if (classe[i].getVoto() > massimo) {
    massimo = classe[i].getVoto();
  }
}

std::cout << massimo << std::endl;

Il ciclo parte da i = 1 perché classe[0] è già il valore iniziale di confronto — un dettaglio piccolo che una verifica controlla volentieri.

Errori tipici che costano punti

  • Accedere a un membro private dal main() o da fuori la classe. È l’errore più comune, ed è anche il punto su cui l’incapsulamento va davvero capito, non solo ricordato come regola.
  • Costruttore senza il tipo giusto (o con un tipo di ritorno per sbaglio): il compilatore lo tratta come un metodo qualunque, e l’oggetto viene creato senza inizializzazione corretta.
  • Dimenticare this-> quando un parametro del costruttore o di un setter ha lo stesso nome di un attributo, finendo per assegnare una variabile a se stessa.
  • Passare un array di oggetti a una funzione aspettandosi che resti invariato: come per gli array di tipi primitivi, in C++ un array passato come parametro non viene copiato di default.

Come capire se sei pronto

Prova a scrivere da zero, senza guardare esempi, una classe con almeno un attributo privato, un costruttore con parametri, un getter e un setter con validazione — e a spiegare ad alta voce perché quell’attributo è private e non public. Se la spiegazione ti viene naturale, e sai leggere un frammento di codice con array di oggetti dicendo esattamente cosa contiene ogni elemento, sei pronto per la prova pratica.

Su OOP e C++ non c’è ancora una simulazione dedicata nella categoria simulazioni per il liceo. Se vuoi ripassare le basi del linguaggio prima di arrivare qui, il post di terza su cicli annidati e array è il prerequisito diretto; se invece l’anno prossimo ti aspettano i puntatori, Puntatori e memoria in C spiegati bene è scritto per l’esame universitario ma il modello mentale della memoria vale anche qui.

Se il passaggio dal procedurale agli oggetti ti sembra ancora un cambio di sintassi più che un cambio di modo di pensare, la pagina di informatica spiega come lo affrontiamo a lezione, e la diagnosi gratuita è un buon punto di partenza prima della verifica.

Continua a leggere

Post correlati.

Vuoi applicare quello che hai letto?

Parliamone 30 minuti gratis: guardiamo insieme dove sei bloccato e ti dico onestamente come posso aiutarti.

Oppure scrivimi su WhatsApp.