Riepilogo
Questa lezione studia per quali vie i dati entrano in una funzione e ne escono i risultati. Per impostazione predefinita, C++ passa gli argomenti per valore: il parametro è una copia, e nulla di ciò che la funzione gli fa raggiunge l’originale. Un riferimento, invece, è un secondo nome per una cella che esiste già, e permette alla funzione di modificare la variabile di chi la chiama; il riferimento costante permette di leggere un dato grande senza copiarlo e senza poterlo alterare. Per restituire più risultati, la libreria offre std::pair e std::tuple, che si ricevono con i binding strutturati. Seguono l’ambito di visibilità di un nome (dove si vede) e la durata di una variabile (quando esiste), con lo shadowing di un nome e le variabili static locali, e chiude la lezione il riferimento che sopravvive alla sua variabile. Il concetto che unifica tutto è il flusso di dati tra le parti di un programma: copiare o condividere, e la visibilità dei nomi come meccanismo di isolamento.
Obiettivi di apprendimento
- Prevedere l’effetto di una funzione sui suoi argomenti a seconda che siano passati per valore o per riferimento.
- Scegliere il modo di passaggio di ciascun parametro (valore per i tipi piccoli, riferimento costante per i dati grandi, riferimento per ciò che si modifica) e giustificare la scelta.
- Restituire più risultati con
std::pairostd::tuplee riceverli con i binding strutturati. - Determinare l’ambito e la durata di ciascuna variabile di un programma, comprese le variabili
staticlocali e quelle oscurate da un’altra. - Riconoscere il riferimento a una variabile locale o a un valore temporaneo che non esiste più, con l’avviso del compilatore che lo annuncia, e correggerlo.
Passaggio per valore
Il parametro è una copia
La lezione Funzioni ha mostrato che ogni chiamata crea un frame nello stack, con i parametri e le variabili locali della funzione, e che il frame scompare al ritorno. Resta da precisare quale relazione abbia un parametro con l’argomento che lo inizializza. La risposta predefinita di C++ è la più semplice: il parametro è una variabile locale nuova, che si inizializza con una copia del valore dell’argomento. Se è così, una funzione che modifica i suoi parametri modifica le sue copie, e le variabili di chi la chiama non cambiano. Il programma seguente lo mette alla prova con una funzione che tenta di scambiare due interi.
#include <print>// Tenta di scambiare a e b; scambia solo le loro copie.void swapValues(int a, int b){ const int saved{a}; a = b; b = saved; std::println("Dentro: a = {}, b = {}", a, b);}int main(){ int x{1}; int y{2}; swapValues(x, y); std::println("Dopo: x = {}, y = {}", x, y);}Dentro: a = 2, b = 1Dopo: x = 1, y = 2Dentro la funzione, lo scambio avviene correttamente; dopo la chiamata, x e y conservano i loro valori. Il compilatore non avvisa, perché non c’è nulla da segnalare: il codice scambia due variabili locali che scompaiono al termine. La figura segue i frame dello stack durante la chiamata.
Il frame di main contiene x e y; la chiamata crea sopra di esso il frame di swapValues, con celle proprie.
Dimostrazione
Prima della chiamata, il frame di
mainha due celle:xcon 1 eycon 2.La chiamata crea il frame di
swapValues. I suoi parametriaebsono celle nuove, e in esse si copiano i valori degli argomenti: 1 e 2. Da questo momento non resta alcun legame traaex, salvo il fatto che sono partiti dallo stesso valore.La variabile locale
savedsi crea nello stesso frame e conserva una copia dia.Le due assegnazioni scambiano
aeb. Le celle che cambiano stanno tutte nel frame in alto; quelle dimainnon vengono toccate.Al ritorno, il frame di
swapValuesscompare per intero, con lo scambio che conteneva.xeyvalgono ancora 1 e 2.
Ciò che la copia protegge
Il passaggio per valore non è un difetto, ma la regola che rende ragionevoli le funzioni. Primo, l’argomento può essere qualsiasi espressione, non solo una variabile: swapValues(x + 1, 7) è valida, perché il parametro si inizializza con un valore, da dovunque provenga. Secondo, chi chiama sa dalla dichiarazione, senza leggere il corpo, che le sue variabili non cambieranno. Terzo, dentro la funzione il parametro è una variabile locale in più, che può servire da variabile di lavoro senza conseguenze all’esterno.
#include <print>// Numero di cifre decimali di n.// Precondizione: n >= 0.// Postcondizione: restituisce il numero di cifre di n (1 per lo 0).int digitCount(int n){ int count{1}; while (n >= 10) { n = n / 10; ++count; } return count;}int main(){ const int population{48'210}; std::println("{} ha {} cifre", population, digitCount(population));}48210 ha 5 cifreLa funzione distrugge il suo parametro dividendolo per 10 finché ha una sola cifra; poiché è una copia, population resta intatta, e può essere const. Ne segue una regola di lettura che vale per tutto il corso: se un parametro non ha il segno & che si studia nella sezione seguente, la funzione non può modificare la variabile di chi la chiama, qualunque cosa faccia con esso.
Caricamento del contenuto…