Blue Flower

Il passaggio di parametri

L’operazione, con la quale vengono associate le due liste dei parametri attuali e formali, detta passaggio dei parametri, può avvenire in due modi diversi:

  • passaggio di parametri per valore, quando i valori delle variabili della funzione principale vengono ricopiati nei parametri della funzione; i cambiamenti effettuati sui parametri formali durante l’esecuzione della funzione non influenzano i valori delle variabili nella funzione chiamante (main o altra funzione).
  • passaggio di parametri per referenza (o per indirizzo), quando i parametri attuali e formali fanno riferimento alla stessa cella di memoria centrale, cioè allo stesso indirizzo di memoria; questo è il caso in cui il programmatore vuole che i cambiamenti di valore ai parametri, durante l’esecuzione della funzione, influenzino i valori delle variabili corrispondenti nella funzione chiamante.

Nel linguaggio C il passaggio di parametri per valore avviene indicando nell’intestazione della funzione il tipo e il nome di ciascun parametro formale.

Il programma, presentato in precedenza per il calcolo delle soluzioni di un’equazione di secondo grado fornisce alcuni esempi di funzioni che utilizzano il passaggio di parametri per valore.
Consideriamo ora il caso di un problema nel quale è necessario usare il passaggio di parametri per referenza.

ESEMPIO: Ordinamento crescente di tre numeri

Per disporre in ordine crescente tre numeri, si può procedere nel seguente modo: si confronta il primo numero con il secondo, scambiandoli tra loro se non sono in ordine crescente; successivamente si confronta il primo con il terzo, scambiandoli tra loro se non sono in ordine crescente, e infine il secondo con il terzo, scambiandoli tra loro se non sono in ordine crescente.
Si noti che nel programma viene (volutamente) ripetuta per tre volte la struttura if.

Programma C

/* TreNumeri.c : ordinamento crescente di tre numeri */
#include <stdio.h>
int main(void) {
/* input-output */
int a, b, c;
/* lavoro */
int temp;
printf("Tre numeri: ");
scanf("%d %d %d", &a, &b, &c);
if (a > b)
{

temp = a;
a = b;
b = temp;
}
if (a > c)
{

temp = a;
a = c;
c = temp;
}
if (b > c)
{

temp = b;
b = c;
c = temp;
}
printf("Numeri ordinati: \n");
printf("%d \n", a);
printf("%d \n", b);
printf("%d \n", c);
return 0;
}

In questo programma l’operazione di controllo e scambio del contenuto di due variabili viene eseguita più volte nel corso del programma su variabili diverse, ma usando le stesse istruzioni.
Queste istruzioni, che vengono usate tre volte, in un programma meglio organizzato possono essere convenientemente raggruppate in una sola funzione Ordina, che verrà richiamata dal main ad operare tre volte con variabili diverse.
Quindi l’uso della funzione risponde all’esigenza di poter utilizzare più volte uno stesso gruppo di istruzioni in momenti diversi del programma, e magari con dati diversi.

Vediamo come si realizza nel linguaggio C il passaggio di parametri per referenza, in modo che le modifiche apportate all’interno della funzione permangano anche dopo il ritorno al programma chiamante. Per far questo è necessario che la funzione non operi su una copia locale dei dati, ma direttamente sui dati originali.
Per attivare questo tipo di comportamento è necessario passare, anziché la variabile direttamente, il suo indirizzo, in modo che la funzione veda la locazione di memoria dove è contenuta la variabile e possa quindi modificarla.

Per passare l’indirizzo di una variabile è necessario utilizzare l’operatore &, che restituisce appunto l’indirizzo della variabile alla quale è applicato.

D’altra parte la funzione, che riceve una variabile con passaggio per indirizzo deve essere in grado di risalire, dall’indirizzo della cella di memoria, al valore in essa contenuto, tramite l’operatore * che, applicato ad una variabile di tipo indirizzo, ne restituisce il valore.
Nella scrittura del codice sia il carattere &, sia il carattere *, precedono il nome della variabile alla quale sono applicati, come si vede nel codice seguente.
Infatti la funzione Ordina, per lo scambio di due numeri che non sono in ordine crescente, fornisce un esempio di passaggio di parametri per referenza. In questo caso si vuole che lo scambio, effettuato sui parametri formali x e y, provochi lo scambio sulle corrispondenti variabili passate dal main nella chiamata alla funzione.

Programma C

/* TreNumeri2.c : ordinamento crescente di tre numeri */
#include <stdio.h>
void Ordina (int *x, int *y) {
int temp;
if (*x > *y)
{
temp = *x;
*x = *y;
*y = temp;
}
return;
}
/* funzione principale */
int main(void)
{
/* input-output */
int a, b, c;
printf("Tre numeri: ");
scanf("%d %d %d", &a, &b, &c);
Ordina(&a, &b);
Ordina(&a, &c);
Ordina(&b, &c);
printf("Numeri ordinati: \n");
printf("%d \n", a);
printf("%d \n", b);
printf("%d \n", c);
return 0;
}

La funzione principale chiama tre volte la funzione Ordina, chiedendo di operare su tre diverse coppie di dati. Ogni chiamata causa l’esecuzione della funzione, al termine della quale la coppia di dati forniti risulta ordinata.
I parametri attuali, passati dal main alla funzione, sono gli indirizzi delle variabili. I parametri formali, sui quali opera la funzione per fare il confronto e l’eventuale scambio di valore, utilizzano l’operatore * per ottenere il valore corrispondente dell’indirizzo di memoria.
L’eventuale scambio, effettuato sui parametri formali *x e *y, provoca lo scambio sulle corrispondenti variabili identificate dagli indirizzi.
La chiamata della funzione nel main contiene, tra parentesi dopo il nome della funzione, gli 
indirizzi delle variabili che mandano i valori ai parametri:

Ordina(&a, &b);

I due esempi seguenti possono chiarire meglio la differenza tra passaggio per valore e passaggio per indirizzo.

Nel primo programma il passaggio del parametro è per valore:
/* Parametri1.c : per valore */
#include <stdio.h>
void Modifica (int y)
{
y += 2;
}
int main(void) {
int x;
x = 1;
printf("%d \n", x);
Modifica(x);
printf("%d \n", x);
return 0;
}
La variabile x passa il suo valore al parametro y: il parametro y viene raddoppiato all’interno della funzione, ma l’operazione y += 2 non influenza il valore originale di x. Sul video quindi compariranno i numeri 1 e 1, in corrispondenza delle due istruzioni printf.
Nel secondo programma il passaggio è per referenza (o indirizzo):
/* Parametri2.c : per indirizzo */
#include <stdio.h>
void Modifica (int *y)
{
*y += 2;
}
int main(void)
{
int x;

x = 1;
printf("%d \n", x);
Modifica(&x);
printf("%d \n", x);
return 0;
}

La variabile x passa il suo indirizzo al puntatore y: il valore puntato da y viene incrementato all’interno della funzione, e quello che succede alla variabile, il cui indirizzo è y, è ciò che succede alla x, perché x e y fanno riferimento allo stesso indirizzo di memoria centrale. Si dice infatti che y punta alla memoria di x. Sul video quindi compaiono i numeri 1 e 3, in corrispondenza delle due istruzioni printf.
Riassumendo, il passaggio per refenza si usa quando l’elaborazione effettuata all’interno della funzione deve produrre effetti alle variabili passate, necessari nelle successive elaborazioni del main (o della funzione chiamante). Si usa il passaggio per valore quando la funzione corrisponde ad un modulo che per svolgere il suo lavoro ha bisogno dei valori forniti dalla funzione chiamante,
per esempio funzioni che devono stampare intestazioni o righe di totali.

Per rafforzare la differenza tra parametri passati per indirizzo e per valore, vediamo anche il seguente esempio.

/* Incrementa.c : per valore e per indirizzo */
#include <stdio.h>
void Aggiungi (int x, int *y)
{
x++;
(*y)++;
printf("Nella funzione chiamata: %d %d \n", x, *y);
return;
}
int main(void)
{
int a, b;
a = 0;
b = 0;
Aggiungi (a, &b);
printf("Nella funzione principale: %d %d \n", a, b);
return 0;
}


Nel programma presentato il valore di a viene passato al parametro x per valore e l’indirizzo di b viene passato al parametro *y per referenza. Per controllare il diverso comportamento dei parametri, i valori di x e *y vengono visualizzati all’interno della funzione, mentre i valori di a e di b all’interno del main dopo che è stata eseguita la funzione Aggiungi.
Al termine dell’esecuzione, in output compaiono nell’ordine le coppie di numeri:

1 1
0 1

perché l’incremento di x (parametro per valore) all’interno della funzione non influenza il valoredi a, mentre l’incremento della variabile indirizzata da y (parametro per referenza) modifica il valore iniziale di b.