Le variabili sono le entità che descrivono lo stato di un programma. Come il nome stesso dice, il valore ad esse attribuito può cambiare, e ad ogni aggiornamento del valore di una variabile corrisponde un cambiamento dello stato del programma.
Di conseguenza, le variabili sono le entità che ci consentono di immagazzinare dati, siano essi generati all'interno del nostro programma, o ricavati da fonti esterne (per esempio da un file).
Come per altre entità che vedremo nel seguito (funzioni, classi, eccetera) le fasi che interessano la vita di una variabile sono diverse e ne definiscono le caratteristiche e le modalità d'uso:
- dichiarazione
- definizione
- assegnamento
- distruzione
Poichè il C++ è un linguaggio fortemente tipizzato, la dichiarazione di una variabile deve contenere il tipo ad essa associato, come mostra la sintassi seguente:
// sintassi per la dichiarazione di una variabile
<tipo> <identificatore>;
// esempi di dichiarazione
int x;
bool flag;
Con la sola dichiarazione, tuttavia, ci siamo limitati a informare il compilatore dell'esistenza di una variabile, ma il valore in essa contenuto si trova ancora in uno stato indeterminato. Qual'è infatti il valore di x
, che abbiamo dichiarato sopra? Zero, uno o altro?
In realtà, dopo la dichiarazione di una variabile di tipo primitivo, il valore ad essa associato è scelto dal compilatore. In molti casi può essere un valore preciso, ad esempio 0 nel caso del tipo intero, in altri potrebbe essere il valore attualmente immagazzinato nella locazione di memoria associata alla nostra variabile.
Pertanto, dopo la dichiarazione di una variabile e prima del suo impiego, è bene provvedere alla definizione del suo valore iniziale:
// sintassi per la dichiarazione e definizione di una variabile
<tipo> <identificatore> = <valore>;
// esempi di dichiarazione e definizione
int x = -1;
bool flag = false;
Nell'esempio precedente si mostrano esempi di definizione del valore di variabili contestuali alla loro dichiarazione. L'operatore =
è detto di assegnamento ed è usato per associare un valore ad un identificatore.
Come mostrato nel prossimo esempio, il valore assegnato può essere una costante, un'altra variabile, il risultato di una espressione nonché il valore restituito da una funzione. L'operatore di assegnamento può essere esso stesso usato in una espressione, pertanto è anche possibile assegnare lo stesso valore a molteplici variabili con un'unica riga di codice, l'unica restrizione riguarda il tipo del valore che deve corrispondere a quello della variabile così come dichiarata.
int a = 2;
int b = 2 * a;
int c = 2 + (b = 5); // sintassi corretta, ma da evitare in quanto non facilmente leggibile
// inizializzazione multipla
int x, y, z;
x = y = z = 1;
Variabili e ambito di visibilità
L'uso di una variabile è condizionato anche dal contesto in cui avviene la sua dichiarazione.
Generalmente e per lo scopo di questa lezione, intendiamo come contesto o ambito di visibilità il blocco di codice delimitato da parentesi graffe in cui una variabile è dichiarata.
Nell'esempio successivo, x
esiste soltanto fin tanto che vengono eseguite le istruzioni comprese tra le parentesi graffe { ... }
.
{
int x = 0;
char x = 0; // errore: l'identificatore x è già stato usato in questo contesto
...
}
x = 5; // errore: siamo fuori dall'ambito di visibilità di x
Un identificatore può essere usato solo una volta all'interno dello stesso blocco per la dichiarazione di una variabile, ed è per questo che la riga successiva alla prima dichiarazione di x produrrebbe un errore di compilazione.
Inoltre, ogni volta che l'esecuzione del programma giunge alla chiusura di un blocco di parentesi graffe, le variabili dichiarate al suo interno perdono visibilità; pertanto ogni tentativo di accesso alla variabile x
al di fuori del suo ambito di visibilità produrrà un errore di compilazione.
Ogni blocco delimitato da parentesi graffe genera un nuovo ambito di visibilità, e questo vale, come vedremo nel seguito, anche per l'uso di costrutti di controllo di flusso (if
, for
, while
, ...), la definizione di classi e funzioni, i namespace, eccetera.
Cosa succede quindi quando ambiti di visibilità differenti si sovrappongono?
Ogni variabile dichiarata in un blocco è visibile anche nei blocchi annidati, ma nulla vieta di riutilizzare lo stesso identificatore per dichiarare una nuova variabile in un blocco annidato. Quando ciò avviene, si verifica l'oscuramento della variabile con contesto di visibilità più ampio, come mostrato nell'esempio successivo.
{ // primo ambito di visibilità
int x = 0;
{ // secondo ambito di visibilità
cout << x; // 0
int x = 1;
cout << x; // 1
}
cout << x; // 0
}
In questo frammento di codice, vediamo due ambiti di visibilità annidati, ed in entrambi l'identificatore x
viene usato per dichiarare una variabile di tipo intero.
La variabile dichiarata nel primo ambito di visibilità è visibile anche nel contesto annidato, fin tanto che non viene oscurata dalla dichiarazione di una nuova variabile con lo stesso nome. Tuttavia, all'uscita dal blocco annidato, la variabile x
dell'ambito più ampio riacquista la sua visibilità.
Il linguaggio C++ mette a disposizione diverse parole chiave che, usate durante la dichiarazione di una variabile, consentono di alterare il suo ambito di visibilità rispetto alle regole generali che abbiamo fin qui esaminato, e che saranno oggetto di altre lezioni.
Inoltre, negli esempi visti finora, la perdita di visibilità corrisponde anche alla liberazione della memoria associata alla variabile, senza la necessità di inserire nel nostro programma istruzioni aggiuntive per questa incombenza. Quando ciò avviene si dice che la variabile è stata distrutta.
Nella prossima lezione vedremo che la distruzione di una variabile non è sempre un processo automatico, e pertanto a volte è necessario gestire in maniera esplicita la memoria usata dal nostro programma, non solo per allocare lo spazio per i dati su cui esso opera, ma anche per la liberarlo quando essa non è più necessario.