Basi di programmazione C++
Introduzione alla programmazione C/C++
Appunti di programmazione C e C++ su GNU/Linux
Indice
Classi
Il C++ è nato per far fronte alle limitazioni di rappresentabilità delle informazioni del C. Abbiamo già incontrato alcuni costrutti particolari che ci consentono di estendere i tipi fondamentali del C costruendone di nuovi, attraverso enumerati, strutture e unioni. Invece, attraverso le classi, che possiamo considerare come un'estensione delle strutture, è possibile costruire e raccogliere non solo i tipi personalizzati costruiti sino ad ora, ma anche le funzioni che operano su di essi, ponendo particolare attenzione a mantenere sempre la consistenza delle informazioni che raccogliamo.
Funzioni membro
Iniziamo con una classe semplicissima del tutto identica alle strutture che abbiamo utilizzato fino a ora, a parte per un paio di parole chiavi:
#include <iostream> using namespace std; class Partecipante /* la parola chiave class indica che si tratta di una classe e non di una semplice struttura */ { public: /* il significato della parola chiave public sarà chiaro tra un momento */ short id; short eta; int punti; }; int main() { Partecipante alice; /* alice è detto istanza o oggetto della classe Partecipante */ alice.id = 12; alice.eta = 16; alice.punti = 0; }
Come si può vedere, ogni funzione che ha accesso all'oggetto alice è libera di modificarne il suo contenuto. Se introduciamo un po' di regole in questa "gara di Partecipanti", questo comportamento diviene indesiderato, scomodo e persino dannoso.
Ad esempio, stabiliamo che a questa gara possono partecipare solo persone maggiorenni. Con la nostra funzione main abbiamo assegnato ad alice 16 anni, e va contro le regole della gara. Per evitare questo, possiamo scrivere una funzione membro, cioè una funzione interna alla classe, che si preoccupa di aggiornare il valore dell'età solo se il partecipante soddisfa i requisiti. Per far questo, tuttavia, dovremo anche nascondere la variabile eta per evitare che la funzione venga semplicemente ignorata da chi usa la nostra classe.
Nell'esempio che segue:
- viene divisa la classe in due macrosezioni, una pubblica (public:), accessibile dall'esterno della classe, e una privata (private:), accessibile solo dall'interno.
- nella sezione pubblica, viene aggiunta una funzione membro per aggiornare il valore dell'età tenendo conto dei vincoli precedentemente imposti. Se l'età non soddisfa i requisiti, la funzione ritorna false, altrimenti true.
- nella sezione privata, viene spostata la variabile eta in modo che non possa essere modificata se non dall'interno della classe.
Si noti che l'accesso ad una funzione membro avviene per mezzo dell'operatore . (punto) ed è identico all'acceso a una qualunque variabile della struttura.
#include <iostream> using namespace std; class Partecipante { private: short eta; public: short id; int punti; bool inserisciEta(short n) { if (n < 18) return false; eta = n; return true; } }; int main() { Partecipante alice; alice.id = 12; if (! alice.inserisciEta(16)) cout << "Errore!" << endl; alice.punti = 0; }
Per esercizio, si modifichi il precedente programma rendendolo interattivo, cioè effettuando le opportune letture dell'input da tastiera, finché non sono soddisfatti tutti i requisiti. Si faccia uso di funzioni membro e ci si affidi ad esse per stabilire la correttezza dei dati. Si aggiunga anche che il punteggio di partenza di ciascun partecipante non può essere inferiore a -50 o superiore a 200.
Costruttore
Il costruttore di una classe è una funzione speciale che viene chiamata automaticamente ogni volta che viene costruita e definita un'istanza della classe. Esso è utile per obbligare l'inserimento di valori iniziali o per inizializzarli con un contenuto standard.
La funzione costruttore ha lo stesso nome della classe, e non restituisce nessun valore, nemmeno void.
#include <iostream> using namespace std; class Partecipante { public: static short num; Partecipante(short, int); void print(void); private: short eta; short id; int punti; bool attivo; }; short Partecipante::num = 0; Partecipante::Partecipante(short et, int p = 0) { attivo = true; if (et < 18) attivo = false; else eta = et; if (p < -50 || p > 200) attivo = false; else punti = p; id = num++; } void Partecipante::print(void) { cout << id << "\t" << eta << "\t" << punti << "\t" << attivo << endl; } int main() { Partecipante alice(21); Partecipante barbara(17, 85); Partecipante carlo(27, -4); alice.print(); barbara.print(); carlo.print(); }
Anche se non viene descritta esplicitamente una funzione costruttore, il compilatore si occupa comunque di compilare un costruttore di default che, semplicemente, alloca lo spazio necessario a contenere le variabili sullo stack.
Distruttore
Contrapposto al costruttore, troviamo il distruttore, funzione che viene chiamata al termine del ciclo di vita dell'oggetto. Il distruttore di default si occupa semplicemente di deallocare la memoria delle variabili membro sullo stack, ma è utile personalizzarlo, ad esempio, quando, facendo uso dell'allocazione dinamica della memoria, si debbano rilasciare delle celle nello heap.
La funzione distruttore non restituisce nessun valore, nemmeno void, e possiede lo stesso nome della classe, preceduto da ˜ (tilde).Segue un esempio: lo si compili, si verifichi il suo funzionamento e si provi a modificarlo per esercizio. In particolare, si provi a controllare il programma facendo uso dell'analizzatore di memoria valgrind (vedere il capitolo sui puntatori): se non si utilizza un distruttore personalizzato, la memoria allocata dinamicamente non viene liberata.
/* Bozza di una classe Scaffale, per rappresentare uno scaffale a dimensione finita in cui è possibile memorizzare i codici dei libri */ class Scaffale { private: short max; short used; int* codice; public: Scaffale(int); ~Scaffale(void); }; Scaffale::Scaffale(int n) { if (n >= 0) max = n; else max = 20; codice = new int[max]; used = 0; } Scaffale::~Scaffale(void) /* deallocazione del vettore con i codici dei libri */ { delete[] codice; } int main() { Scaffale a(10); return 0; }