edutecnica

Riferimenti (reference)

     

I riferimenti o reference consentono di attribuire più nomi alla stessa variabile.

Esempi di sintassi:



double x =3.14;
double &y = x; //y reference di x
y*=y; // equivale a x=x *x

ATTENZIONE! Un reference può essere definito solo una volta, al momento della sua dichiarazione. In alcuni casi, come per esempio nella dichiarazione dei parametri di una funzione, l'inizializzazione va omessa perché avverrà al momento della chiamata.

Esempi :

double x1, x2;
v double &y; // ERRORE!
double &y = x1; // Ok
double &y = x2; // ERRORE!

Puntatori

     

Le variabili puntatore contengono gli indirizzi di memoria di altri oggetti. Si dichiarano anteponendo l'operatore * all'identificatore della variabile e la loro inizializzazione può essere rimandata.
Il tipo deve coincidere con quello dell'oggetto cui puntano.

Esempio:



double x = 3.14;
double *px;
px = &x;

L'operatore & restituisce l'indirizzo (l-value) dell'oggetto che lo segue, in questo caso la variabile x.

Operatore di dereferenziazione *

     

Questo operatore consente di accedere indirettamente all'oggetto puntato (nel caso di una semplice variabile, al suo valore o r-value).

Esempio :


double x = 3.14;
double * px = &x; // px punta a x
/* accede indirettamente a x */
*px = 2 * x; // x contiene 6.28


Esempio di utilizzo dei puntatori

     


double x, y;
double *px = &x, *py = &y;
*px = 3.14; // equivale a x = 3.14
y = *px; // equivale a y = x
py= px;

Il disegno che segue è una rappresentazione grafica del codice.



Memoria e puntatori

     

Le variabili puntatore occupano sempre lo stesso spazio di memoria, indipendentemente dalle dimensioni dell'oggetto puntato. Prima di accedere a un oggetto mediante un puntatore è bene accertarsi che contenga un indirizzo valido. Per questo motivo è sempre opportuno inizializzare tutti i puntatori al momento della loro dichiarazione. Se l'oggetto di destinazione non è prevedibile, si assegna il puntatore null (ovvero il valore 0).
Altre assegnazioni di indirizzi espliciti sono sconsigliate. Per farlo, comunque, è necessaria un'operazione di cast esplicita.

Esempi :


double *pn = 0 // puntatore null
// Sconsigliato!
int *pk = (int *)0x00FA1234;

Puntatori a costanti e puntatori costanti

     

Un puntatore a costanti si dichiara cosi:

conts double *px = &x;

Il valore di x non può essere modificato dereferenziando px:

*px = 1.41; // ERRORE!
px = &y; // Ok;

Un puntatore costante non può puntare a oggetti diversi da quello iniziale:

double *const px = &x;
px = &y; // ERRORE! px è const
*px = 1.41; // Ok

è possibile dichiarare anche puntatori costanti a costanti.
Nè il valore del puntatore né quello dela locazione di memoria cui punta possono essere modificati.

const double *const px= &x;

*px= 1.41; // ERRORE!
px = &y; // ERRORE!

Passaggio di argomenti a funzioni

     

Gli argomenti di una funzione possono essere passati con due differenti modalità:
per valore;
per riferimento.
Nel primo caso viene passato il valore dell'argomento. nel secondo caso il suo indirizzo di memoria (riferimento).
I passaggi per riferimento si possono effettuare sia mediante reference sia mediante puntatori.

Passaggio di argomenti per riferimento

     

/* Funzione che scambia due double
Versione con reference */

void Swap(double &x, double &y) {
double aux;
aux= x;
x= y;
y= aux;
}

/* Funzione che scambia due double
Versione con puntatori */

void Swap(double *x, double *y) {
double aux;
aux = *x;
*x= *y;
*y = aux;
}

Vantaggi e svantaggi del riferimento

     

Vantaggi:
maggiore velocità(soprattutto con argomento di grosse dimensioni);
possibilità di modificare gli argomenti;
minore uso di variabili globali.

Svantaggi:
argomento e parametro devono essere esattamente dello stesso tipo;
non si possono passare costanti letterali(a parte le stringhe costanti mediante puntatori);
possibilità di confusione nel codice ( non si sa cosa e dove modifica un valore).

Aritmetica dei puntatori

     

I puntatori hanno una propria aritmetica, in cui le sole operazioni consentite sono l’addizione, la sottrazione,l’incremento e il decremento unario e tutti i tipi di confronto.
Se un puntatore p1 si riferisce al primo elemento di un vettore di interi,vett[], allora l’istruzione:

p1 = p1+3;

non incrementa di 3 il valore contenuto in p1, ma fa puntare p1 all’indirizzo del 3° intero successivo a quello puntato in precedenza, ovvero a vett[2].
Se p2 è un altro puntatore a interi che punta a un elemento dello stesso vettore vett,allora l’espressione :

p2-p1

darà come risultato il numero di interi compresi tra p1 e p2.

Vettori e puntatori

     

Il nome di un vettore è anche l'indirizzo costante del suo primo elemento. Da questa definizione segue che :

vett ≡ &vett[0]
&vett[i] ≡ vett+i

per cui

vett[i] ≡ *(vett+i)

Esempi:

int k[10], *pk;
pk = k; // indir.primo elem.
//Equivale a k[3] = 7
*(pk + 3) = 7;
pk = &k[5];
pk +=4; // punta a 4 elem. dopo
*pk = 0;// equivale a k[9] = 0

Funzione con vettori come parametri

     

A una funzione è possibile passare dei vettori come parametri. Tuttavia, nel corrispondente parametro formale non viene fatta la copia di tutti i suoi elementi, ma solo quella dell'indirizzo iniziale.
Per l'equivalenza di notazione, all'interno della funzione il parametro formale può essere trattato come un vettore o come un puntatore, indifferentemente.

Esempio:

int Minimo(int vett[], int n){
int min = *vett; // min = vett[0] for(unsigned i = 1; i < n; i++)
if(vett[i] < min) min = *(vett + i);
return min;
}

Funzioni che restituiscono vettori

     

Una funzione non può ritornare un vettore, ma un suo puntatore al primo elemento si. In questi casi occorre fare attenzione che l'indirizzo restituito si riferisca a una locazione di memoria valida, non a una variabile automatica della funzione. Queste variabili, infatti, sono rimosse della memoria al termine dell'esecuzione della funzione, per cui il puntatore restituito punterebbe a un valore non più significativo (dangling pointer). Un esempio di questo tipo di errore è il seguente:

int *Moltiplica(int n[10], int k){
int m[10];
for (short i = 0; i < 10; i++)m[i] = n[i] * k;
return m; // ERRORE! m locale
}

La seguente funzione restituisce il primo nome (in ordine alfabetico) che compare nella tabella di ingresso t, ovvero ritorna un puntatore a un vettore di char (stringa).

//Ritorna il puntatore a una stringa
char *Primo(char t[][20], int n){
char min[20];
int rigaMin = 0;

strcpy(min, t[0]);
for (int r = 1; r< n; r++)
   if(strcmp(min, t[r]) > 0) {
      strcpy(min, t[r]);
      rigaMin = r;
   }//fine if
return t[rigaMin]; // Ok
}

Attenzione a non restituire min, è un dangling pointer (stesso errore dell'esempio precedente). t[rigaMin], invece, rappresenta l'indirizzo della stringa cercata e fa riferimento alla tabella passata come argomento.

Passaggio di strutture a funzioni

     

Quando occorre passare a una funzione dati aggregati di grandi dimensioni è molto più efficiente farlo per riferimento anziché per valore.
Se una struttura viene passata mediante un reference, la sintassi per riferirsi ai suoi membri impiega semplicemente l'operatore punto '.'.
Se il passaggio avviene tramite un puntatore, invece, occorre usare l'operatore freccia '->'.

Esempi :

//Passaggio con reference
double Tot(Prod &art, double iva){
double t;
t = art.pz * (1 + iva / 100);
return t;
}

//Passaggio con puntatori
double Tot(Prod *art; double iva){
double t;
t = art->pz * (1 + iva / 100);
return t;
}

Funzioni che ritornano strutture

     

Quando una funzione deve ritornare dati aggregati di grandi dimensioni è conveniente farlo tramite riferimento e non per valore. Anche in questi casi, però, occorre fare attenzione a non ritornare reference che si riferiscono a variabili locali o dangling pointer.