Perché esistono le costanti
Una costante è un valore che prometti di non cambiare mai dopo averlo impostato. Marcare qualcosa come const fa due cose insieme: documenta la tua intenzione per chiunque legga il codice e permette al compilatore di farla rispettare, perché qualsiasi riga che provi a modificare il valore diventa un errore di compilazione invece di un bug silenzioso a runtime.
Mentre la parola chiave auto lasciava al compilatore il compito di dedurre il tipo di una variabile, const limita ciò che puoi fare con quella variabile. Le due si combinano liberamente: const auto limit = 100; è un int di sola lettura.
Dichiarare un valore const
Metti const prima del tipo. Una variabile const deve essere inizializzata sulla stessa riga, perché non esiste un momento successivo in cui ti sia permesso assegnarle un valore.
Togli il commento all'assegnazione e il programma non compila: il compilatore segnala "assignment of read-only variable". È proprio questo il punto: l'errore viene individuato prima ancora che il programma venga eseguito.
Un'abitudine comune da principianti ereditata dal C è #define MAX_USERS 100. Evitala. Una macro è una sostituzione di testo alla cieca, senza tipo e senza rispetto per lo scope, quindi non si può ispezionare in un debugger e produce messaggi di errore confusi. Una variabile const (o constexpr) viene controllata nel tipo e ha uno scope come qualsiasi altra.
const e constexpr
Entrambe le parole chiave ti danno un valore che non può cambiare, ma rispondono a domande diverse. const dice "questo non cambia mai dopo essere stato impostato". constexpr dice qualcosa di più forte, "questo si può calcolare in fase di compilazione", e tutto ciò che è constexpr è automaticamente anche const.
La regola pratica: usa constexpr ogni volta che il valore è un letterale fisso o un calcolo che il compilatore può fare (dimensioni di array, lunghezze di buffer, etichette switch, argomenti template). Usa un semplice const quando il valore viene deciso a runtime ma non deve cambiare dopo, come una copia const di un argomento di funzione.
Da C++20 esiste anche consteval, che si usa sulle funzioni che devono essere eseguite in fase di compilazione:
consteval int square(int x) { return x * x; }
constexpr int area = square(8); // calcolato durante la compilazione
Una funzione constexpr può essere eseguita in fase di compilazione; una funzione consteval deve esserlo sempre, altrimenti è un errore.
Puntatori e const: leggi da destra a sinistra
È qui che const manda in confusione, perché la parola chiave può stare su entrambi i lati dell'* e i due significati sono opposti. Il trucco è leggere la dichiarazione da destra a sinistra.
Leggi int* const p2 da destra a sinistra: "p2 è un puntatore const a int". Leggi const int* p1 come "p1 è un puntatore a const int". Se sbagli, perderai parecchio tempo confuso da un errore che dice che non puoi modificare qualcosa che pensavi fosse modificabile.
Un'insidia pratica: non prendere mai l'indirizzo di un const per poi eliminare il const con un cast e modificare l'oggetto sottostante. Farlo è comportamento indefinito se l'oggetto originale era davvero const, e il compilatore è libero di presumere che il valore non cambi mai: la tua "scrittura" potrebbe semplicemente essere ignorata.
Riferimenti const come parametri di funzione
L'uso quotidiano più comune di const è passare oggetti grandi per riferimento senza copiarli. Un parametro const& evita la copia e promette che la funzione non modificherà l'argomento del chiamante.
Il passaggio per const& è la scelta predefinita per qualsiasi parametro più grande di un paio di byte (stringhe, vector, le tue classi). Nota che permette alla funzione di accettare anche un temporaneo come "Grace": un semplice riferimento non const non può legarsi a un temporaneo, quindi togliere il const qui farebbe rifiutare la seconda chiamata.
Funzioni membro const
Quando scrivi una classe, marca con un const finale ogni metodo che non modifica l'oggetto. È questo che rende il metodo chiamabile su istanze const e su parametri const&: senza, non puoi leggere il tuo stesso oggetto attraverso un riferimento const.
La disciplina di marcare come const i metodi di sola lettura si chiama const correctness. Impostala bene fin da subito: aggiungere const a un metodo che avrebbe dovuto esserlo è facile, ma introdurre const più tardi in un codebase grande è doloroso, perché ogni chiamante che passa da un riferimento const ne dipende.
Prossimo: Operatori
Ora che puoi bloccare i tuoi valori con const, il passo successivo è farci qualcosa. La pagina sugli operatori tratta gli operatori aritmetici, di confronto, logici e di assegnazione, comprese le insidie della divisione intera, la precedenza degli operatori e il modo in cui const interagisce con gli operatori di assegnazione che non ti è permesso usare.
Domande frequenti
Qual è la differenza tra const e constexpr in C++?
const significa che il valore non può cambiare dopo l'inizializzazione, ma può essere calcolato a runtime. constexpr è più forte: garantisce che il valore possa essere calcolato in fase di compilazione, quindi si può usare dove serve una costante nota al compilatore (dimensioni di array, argomenti template, etichette switch). Ogni oggetto constexpr è anche const, ma non ogni oggetto const è constexpr.
Come si dichiara una costante in C++?
Metti const prima del tipo e assegna un valore: const int maxUsers = 100;. Una variabile const deve essere inizializzata quando viene dichiarata, perché non potrai mai assegnarle un valore in seguito. Per le costanti note in fase di compilazione preferisci constexpr int maxUsers = 100;. Evita la vecchia macro in stile C #define: non ha tipo e ignora lo scope.
Cosa significa un puntatore const in C++?
Dipende da dove si trova const. const int* p è un puntatore a const: puoi far puntare p altrove, ma non puoi modificare *p. int* const p è un puntatore const: puoi modificare *p, ma non puoi far puntare p altrove. Leggi la dichiarazione da destra a sinistra: int* const è "puntatore const a int".