Cos'è una classe
Una classe è il progetto di un tipo personalizzato che riunisce dati (variabili membro) e comportamento (funzioni membro) in un'unica unità. Dove i tipi predefiniti come int e double descrivono singoli valori, una classe ti permette di modellare un intero concetto, come un conto bancario, un punto 2D o un giocatore, come un unico valore da passare in giro.
Hai già usato delle classi senza definirne nessuna: std::string e std::vector sono classi della libreria standard. Chiamare name.length() o v.push_back(3) significa chiamare una funzione membro su un oggetto. Ora costruirai i tuoi tipi nello stesso modo, per poi passare ai costruttori per inizializzarli.
Definire una classe e creare oggetti
La definizione di una classe elenca i suoi membri tra parentesi graffe e termina con un punto e virgola: dimenticare quel ; finale è uno degli errori più comuni tra i principianti. Ogni oggetto che crei dalla classe ha la propria copia delle variabili membro.
rex e luna sono due oggetti separati. Cambiare rex.name non tocca luna.name: ogni oggetto contiene i propri dati. Raggiungi un membro con l'operatore punto . su un oggetto (oppure -> quando hai un puntatore a un oggetto).
public e private
Di default, tutto ciò che sta in una class è private: solo le funzioni membro della classe stessa possono toccarlo. L'etichetta public: apre i membri al codice esterno. Questa divisione è il cuore dell'incapsulamento: esponi un'interfaccia sicura e nascondi i dati interni in modo che non possano finire in uno stato non valido.
Dato che count è private, nessun chiamante può infilarci un valore negativo o senza senso: deve passare da increment(). Il const dopo la lista dei parametri di value() promette che la funzione non modificherà l'oggetto, il che ti permette di chiamarla su oggetti const e comunica l'intenzione a chi legge. Consulta gli specificatori di accesso per sapere tutto su public, private e protected.
Dichiarazione e definizione dei metodi
Per classi piccole va benissimo definire i metodi inline (come sopra). Per quelle più grandi è comune dichiarare i metodi dentro la classe e definirne il corpo all'esterno, usando la sintassi di scope ClassName::method. In questo modo la definizione della classe resta leggibile come riassunto dell'interfaccia.
Il prefisso Rectangle:: dice al compilatore a quale classe appartiene la funzione. Dentro una definizione di questo tipo continui a riferirti ai membri con il loro nome semplice (width, area()): il compilatore sa che appartengono all'oggetto su cui è stato chiamato il metodo.
Il puntatore this
Dentro qualsiasi funzione membro non statica, this è un puntatore all'oggetto su cui è stata invocata la funzione. Di solito non ti serve, perché il nome semplice di un membro si riferisce già all'oggetto corrente. Diventa utile quando un parametro nasconde un membro, cioè quando hanno lo stesso nome, e devi distinguerli.
Senza this->, scrivere x = x; dentro setX assegnerebbe il parametro a se stesso lasciando il membro invariato: un bug silenzioso. this->x elimina l'ambiguità. Molti progetti evitano del tutto il problema dando ai parametri nomi diversi (per esempio setX(int newX)), ma nel codice reale vedrai this-> di continuo.
Errori comuni
Alcune trappole delle classi colpiscono ripetutamente:
- Dimenticare il punto e virgola finale. La definizione di una classe termina con
};. Se togli il;ottieni una valanga di errori confusi che puntano a ciò che viene dopo la classe, non alla classe stessa. - Dimenticare di inizializzare i membri. I membri di tipo predefinito come
intedoublenon vengono azzerati automaticamente. Leggere un membro non inizializzato è comportamento indefinito. Dai ai membri dei valori predefiniti (int count = 0;) o inizializzali in un costruttore. - Accedere ai membri private dall'esterno.
c.count = 5;su un membro private è un errore di compilazione: passa invece da un metodo pubblico. È l'incapsulamento che funziona come previsto. - Confondere la classe con un oggetto.
Dog.bark();è sbagliato:Dogè il tipo. I metodi si chiamano sugli oggetti:rex.bark();.
// Membro non inizializzato, leggere 'age' è comportamento indefinito:
class Cat {
public:
int age; // nessun valore predefinito
};
Cat c;
std::cout << c.age; // valore spazzatura, non 0
Prossimo: Costruttori
Impostare a mano ogni membro dopo aver creato un oggetto, con rex.name = ...; rex.age = ...;, è noioso e facile da dimenticare, ed è proprio così che ti ritrovi con membri non inizializzati. La prossima pagina tratta i costruttori: funzioni speciali che vengono eseguite automaticamente quando un oggetto viene creato, e che ti permettono di garantire che ogni oggetto parta in uno stato valido con una sintassi pulita su una riga come Dog rex("Rex", 4);.
Domande frequenti
Qual è la differenza tra una classe e un oggetto in C++?
Una classe è il progetto: descrive quali dati (variabili membro) e quale comportamento (funzioni membro) ha un tipo. Un oggetto è un'istanza concreta costruita a partire da quel progetto. class Dog { ... }; definisce il tipo una volta; Dog rex; crea un vero Dog che puoi usare. Una classe può produrre molti oggetti indipendenti.
Qual è la differenza tra class e struct in C++?
Tecnicamente solo il livello di accesso predefinito: i membri di una class sono private di default, mentre quelli di una struct sono public di default. Entrambe possono avere funzioni membro, costruttori ed ereditarietà. Per convenzione, struct si usa per semplici raggruppamenti di dati e class per tipi con un comportamento e delle invarianti da proteggere.
A cosa serve il puntatore this in una classe C++?
Dentro una funzione membro, this è un puntatore all'oggetto su cui è stata chiamata la funzione. Usalo per distinguere un parametro da un membro (this->x = x;) o per restituire l'oggetto corrente. Raramente ti serve per il normale accesso ai membri: scrivere x si riferisce già alla x dell'oggetto corrente.