Riferimenti in C++
- Un riferimento in C++ è un alias per una variabile esistente, permettendo di accedere e modificare direttamente il valore originale.
- I riferimenti devono essere inizializzati al momento della dichiarazione e non possono essere nulli.
- Una volta che un riferimento è stato legato a una variabile, non può essere riassegnato a un'altra variabile.
- I riferimenti sono utili per passare grandi strutture o classi alle funzioni senza copiare i dati, migliorando l'efficienza del programma.
- I riferimenti possono essere utilizzati per implementare funzioni che modificano i valori dei parametri passati.
- I riferimenti non sono oggetti e non occupano spazio di memoria aggiuntivo.
Riferimenti
Un riferimento definisce un nome alternativo per un oggetto, un alias in tutto e per tutto. Un tipo riferimento "si riferisce a" un altro tipo. Definiamo un tipo riferimento scrivendo un dichiaratore della forma &d
, dove d
è il nome che viene dichiarato:
tipo &d = inizializzatore;
Ad esempio:
// Variabile originale
int valore_int = 1024;
// rif_valore è un riferimento a valore_int
int &rif_valore = valore_int;
// ERRORE:
// Un riferimento deve essere inizializzato
// al momento della definizione
int &rif_valore2;
Ordinariamente, quando inizializziamo una variabile, il valore dell'inizializzatore viene copiato nell'oggetto che stiamo creando.
Quando definiamo un riferimento, invece di copiare il valore dell'inizializzatore, leghiamo il riferimento al suo inizializzatore. Una volta inizializzato, un riferimento rimane legato al suo oggetto iniziale. Non c'è modo di rilegare un riferimento per riferirsi a un oggetto diverso. Poiché non c'è modo di rilegare un riferimento, i riferimenti devono essere inizializzati.
Riferimenti l-value
Lo Standard C++11 ha introdotto un nuovo tipo di riferimento: un "riferimento rvalue", che tratteremo nelle prossime lezioni.
Questi riferimenti sono principalmente destinati all'uso all'interno delle classi. Tecnicamente parlando, quando usiamo il termine riferimento, intendiamo "riferimento lvalue".
Riferimenti come Alias
Un riferimento non è un oggetto. Invece, un riferimento è solo un altro nome per un oggetto già esistente.
Dopo che un riferimento è stato definito, tutte le operazioni su quel riferimento sono in realtà operazioni sull'oggetto a cui il riferimento è legato:
// assegna 2 all'oggetto a cui rif_valore si riferisce, cioè a valore_int
rif_valore = 2;
// Uguale a:
// ii = valore_int
int ii = rif_valore;
Quando assegniamo a un riferimento, stiamo assegnando all'oggetto a cui il riferimento è legato. Quando recuperiamo il valore di un riferimento, stiamo realmente recuperando il valore dell'oggetto a cui il riferimento è legato. Similmente, quando usiamo un riferimento come inizializzatore, stiamo realmente usando l'oggetto a cui il riferimento è legato:
// VALIDO:
// rif_valore3 è legato all'oggetto a cui
// rif_valore è legato, cioè a valore_int
int &rif_valore3 = rif_valore;
// inizializza i dal valore nell'oggetto a cui rif_valore è legato
// VALIDO: inizializza i allo stesso valore di valore_int
int i = rif_valore;
Poiché i riferimenti non sono oggetti, non possiamo definire un riferimento a un riferimento.
Definizioni di Riferimenti
Possiamo definire più riferimenti in una singola definizione. Ogni identificatore che è un riferimento deve essere preceduto dal simbolo &:
// i e i2 sono entrambi int
int i = 1024, i2 = 2048;
// r è un riferimento legato a i; r2 è un int
int &r = i, r2 = i2;
// i3 è un int; ri è un riferimento legato a i3
int i3 = 1024, &ri = i3;
// sia r3 che r4 sono riferimenti
int &r3 = i3, &r4 = i2;
Con due eccezioni che tratteremo nelle prossime lezioni, il tipo di un riferimento e l'oggetto a cui il riferimento si riferisce devono corrispondere esattamente.
Inoltre, per ragioni che esploreremo, un riferimento può essere legato solo a un oggetto, non a un letterale o al risultato di un'espressione più generale:
// ERRORE: l'inizializzatore deve essere un oggetto
int &rif_valore4 = 10;
double valore_double = 3.14;
// ERRORE: i tipi non corrispondono
int &rif_valore5 = valore_double;
Per ricordarsi di questo dettaglio, basta pensare che un riferimento è semplicemente un altro nome per un oggetto esistente. Ma un oggetto, ossia una variabile, come abbiamo detto nella lezione precedente, corrisponde a un'area di memoria specifica. Un riferimento, essendo solo un altro nome per quell'oggetto, non può esistere senza quell'oggetto.
Esercizi
Data la seguente definizione:
int valore_int = 1.01;
Quali delle seguenti definizioni sono valide?
-
Definizione 1:
int &rval1 = 1.01;
Questa definizione non è valida perché un riferimento deve essere legato a un oggetto, e
1.01
è un letterale, non un oggetto. -
Definizione 2:
int &rval2 = valore_int;
Questa definizione è valida.
rval2
è un riferimento legato all'oggettovalore_int
. Inoltre, i tipi corrispondono (int
). -
Definizione 3:
int &rval3;
Questa definizione non è valida perché un riferimento deve essere inizializzato al momento della definizione.
Dato il seguente codice:
int i, &r1 = i;
double d, &r2 = d;
Quali, tra le seguenti assegnazioni sono valide?
-
Assegnazione (a):
r2 = 3.14159;
Questa assegnazione è valida.
r2
è un riferimento legato ad
, che è di tipodouble
. Assegnare un valoredouble
ad
tramite il riferimentor2
è corretto. -
Assegnazione (b):
r2 = r1;
Questa assegnazione è valida. Tuttavia, nel momento in cui il valore riferito da
r1
, che è intero, viene assegnato ar2
il valore verrà automaticamente convertito in undouble
. -
Assegnazione (c):
i = r2;
Questa assegnazione è valida.
r2
è un riferimento legato ad
, che è di tipodouble
. Assegnare il valore did
ai
comporta una conversione dadouble
aint
, che è consentita in C++. -
Assegnazione (d):
r1 = d;
Questa assegnazione non è valida.
r1
è un riferimento legato ai
, che è di tipoint
, mentred
è di tipodouble
. Quindir1
non può riferirsi ad
.
Esaminiamo il seguente codice. Cosa stampa il programma?
int i, &ri = i;
i = 5;
ri = 10;
std::cout << i << " " << ri << std::endl;
In questo codice, ri
si riferisce ad i
. Dopodiché, ad i
viene assegnato il valore 5. Quindi i
vale 5 e ri
che ad esso si riferisce varrà anch'esso 5.
Subito dopo, però, a ri
viene assegnato il valore 10. Ma poiché ri
si riferisce a i
, quest'ultimo verrà modificato e conterrà il valore 10.
Quando il programma stampa infine i valori di i
e ri
il risultato sarà:
10 10